Converting AD Mobile Accounts to Local

lisacherie
Contributor II

With the increasing trend to stop binding macs to AD, I put together a script to demote mobile accounts to local.

To re-iterate - this one is risky. Please test it carefully if you decide to try it out.

If you find additional attributes that your fleet needed - would be interested to hear.

#!/usr/bin/perl -w

#  mobiletolocal.sh
#  Created by LD
#  Appears safe to run whilst logged in - user keeps existing password :)

my $userlist = `dscl . list /Users`;

chomp $userlist;

my @excludedusers = split(" ","root daemon nobody _amavisd _appleevents _appowner _appserver _ard _assetcache _astris _atsserver _avbdeviced _calendar _ces _clamav _coreaudiod _coremediaiod _cvmsroot _cvs _cyrus _devdocs _devicemgr _displaypolicyd _distnote _dovecot _dovenull _dpaudio _eppc _ftp _gamecontrollerd _geod _iconservices _installassistant _installer _jabber _kadmin_admin _kadmin_changepw _krb_anonymous _krb_changepw _krb_kadmin _krb_kerberos _krb_krbtgt _krbfast _krbtgt _launchservicesd _lda _locationd _lp _mailman _mbsetupuser _mcxalr _mdnsresponder _mysql _netbios _netstatistics _networkd _nsurlsessiond _nsurlstoraged _ondemand _postfix _postgres _qtss _sandbox _screensaver _scsd _securityagent _serialnumberd _softwareupdate _spotlight _sshd _svn _taskgated _teamsserver _timezone _tokend _trustevaluationagent _unknown _update_sharing _usbmuxd _uucp _warmd _webauthserver _windowserver _www _wwwproxy _xserverdocs");

my @userslist = split("
", $userlist);
my @users;
my $result = "";

foreach my $u (@userslist) {

   my $match = 0;

   foreach my $e (@excludedusers) {
      if ("$u" eq "$e") {
         $match = 1;
      }
   }

   if ($match == 0) {
      push(@users, $u);
    }
}

my $dsconfigad = `/usr/sbin/dsconfigad -show`;
chomp $dsconfigad;

unless ($dsconfigad eq "") {
   printf "Removing AD bind
";
   system "/usr/sbin/dsconfigad -remove -force -u none -p none";
}

foreach my $username (@users) {

   printf "Username is: $username
";
   my $accounttype = `/usr/bin/dscl . -read /Users/$username OriginalNodeName`;

   unless ($accounttype =~ //Active Directory//) {
       printf "Next: $username is not a mobile account
";
       next;
   }

   printf "$username has an AD mobile account - converting to local
";

   #Get Hash of existing password

   my $shadowhash = `/usr/bin/dscl . -read /Users/$username AuthenticationAuthority | grep " ;ShadowHash;HASHLIST:<"`;
   chomp $shadowhash;

   #printf "AuthenticationAuthority is: $shadowhash
";

   system "/usr/bin/dscl . -delete /users/$username cached_groups";
   system "/usr/bin/dscl . -delete /users/$username SMBPrimaryGroupSID";
   system "/usr/bin/dscl . -delete /users/$username OriginalAuthenticationAuthority";
   system "/usr/bin/dscl . -delete /users/$username OriginalNodeName";
   system "/usr/bin/dscl . -delete /users/$username AuthenticationAuthority";
   system "/usr/bin/dscl . -create /users/$username AuthenticationAuthority '$shadowhash'";
   system "/usr/bin/dscl . -delete /users/$username SMBSID";
   system "/usr/bin/dscl . -delete /users/$username SMBScriptPath";
   system "/usr/bin/dscl . -delete /users/$username SMBPasswordLastSet";
   system "/usr/bin/dscl . -delete /users/$username SMBGroupRID";
   system "/usr/bin/dscl . -delete /users/$username PrimaryNTDomain";
   system "/usr/bin/dscl . -delete /users/$username AppleMetaRecordName";
   system "/usr/bin/dscl . -delete /users/$username PrimaryNTDomain";
   system "/usr/bin/dscl . -delete /users/$username MCXSettings";
   system "/usr/bin/dscl . -delete /users/$username MCXFlags";
   system "/usr/bin/dscl . -delete /users/$username AppleMetaRecordName";
   system "/usr/bin/dscl . -delete /users/$username dsAttrTypeNative:cached_auth_policy";
   system "/usr/bin/dscl . -delete /Users/$username dsAttrTypeStandard:CopyTimestamp";
   system "/usr/sbin/dseditgroup -o edit -a $username -t user admin";

}

exit 0;
58 REPLIES 58

lisacherie
Contributor II

There were a couple of people who helped me with this. Not sure whether to publicly call them out - who gave advice on how to deploy it. I was scared. Who also helped with figuring out which attributes needed modification.

chrisx
New Contributor

Does this also work with OpenLDAP?

lisacherie
Contributor II

You could possibly use the same approach - though I suspect some of the attributes to change will be different.

Compare dscl output against user accounts.

lisacherie
Contributor II

@rtrouton Has worked his magic, and made an interactive version with some extra features.

https://derflounder.wordpress.com/2016/12/21/migrating-ad-mobile-accounts-to-local-user-accounts/

mbezzo
Contributor III

Nice! Thank you both!

perrinbw
New Contributor II

@lisacherie THANK YOU! This is a huge life saver for me. Deployed it to a our test group of Enterprise Connect users (all logged in and using their systems) and had zero issues. Will continue using it as we migrate more folks to Enterprise Connect.

ant89
Contributor

@perrinbw are you deploying the script via jamf? if so, how do you get the terminal to pop up so i can choose the options?

Thanks

lisacherie
Contributor II

@ant89 There are two versions of the script.

The version I shared does not require interaction, or Rich's version does. Try out each and see which method you prefer. The silent version suited the workflow I had as the process was invisible and non disruptive to end users.

Please test it out before trying - each AD is a bit different and there are macOS dot releases since then.

ant89
Contributor

@lisacherie thank you! the script you provided works in our environment. QQ - Anyway to determine which laptops have mobile accounts? I've created an EA that looks for accounts that have a UID over 1000. but your script does not change the UID. If this script was ran, i have no way of determining if it is still a mobile account or not. Any ideas?

lisacherie
Contributor II

@ant89 try using this to determine which Macs need to be in scope.

https://www.jamf.com/jamf-nation/discussions/22292/mobile-and-local-accounts

lisacherie
Contributor II

-

ant89
Contributor

@lisacherie awsome. thank you!

chris_kemp
Contributor III

Just started testing this myself - first test was perfect, so that's very encouraging. Thank you @lisacherie !

MacFly
New Contributor

So, this is probably going to sound dumb, but has anyone used this with High Sierra? With Apple Script editor it is not running and having issues. Most of this is over my head, but I would love to test this out! Any pointers would be great!

lisacherie
Contributor II

It did work with early builds of High Sierra - but you will need to test to ensure there are no differences with your environment and it still works with the current builds.

To run the script you need to use terminal and the following command:

sudo ./scriptname.sh

Good luck - hope it works out for you.

antoinekinch
New Contributor III

@lisacherie has anyone gotten this or @rtrouton 's version of the script to work using JAMF Helper's Prompt Message feature? It would be nice to deploy this to machines that need AD accounts converted to local that are provisioned with DEP. Thus preventing future keychain and FV2 password sync problems.

lisacherie
Contributor II

The version I wrote was designed to run silently and convert all accounts in one policy run, regardless of whether the user is active at the time. This is a sanitized version some specific lines to that environment were removed, however this version also worked.

I used it successfully with my previous employer, however each AD is different, and there are many OS updates since then, so please test before deployment.

antoinekinch
New Contributor III

Thank you @lisacherie I am rebuilding a machine to test the script on now.

lostradamus
New Contributor II

@lisacherie I was able to use the script to unbind a mac and convert the account from a mobile account to a local account, however I've noticed that I cannot change the password on the local account now. Have you seen this behaviour before?

lisacherie
Contributor II

@rtrouton has updated the version of this he wrote. Please take care to obtain the new version: https://derflounder.wordpress.com/2018/06/16/updated-migrateadmobileaccounttolocalaccount-script-now...

Note the perl version is using a string within a string for the “system” function to execute the dscl command rather than as a command in bash.

At first look the perl version may not be impacted due to the way strings are handled/quoted.

More testing is needed to confirm.

ethan_delavan
New Contributor

I also had success with this code. It prompts for a desired user to convert via calling a system event. Then if it's not an AD account, it exits. If so, it follows the steps that @lisacherie outlines above. No unbinding from AD in this script. I decided to call that separately so that I can unbind with AD credentials and do these separately as needed. Here's that script:

#!/bin/bash
#Freddie Cox for Knox County Schools
#Edited by Justin Ellis
#Arranged by Ethan Delavan for Jamf environment
#2012
#
#Ask user for the target user to convert
netname=`/usr/bin/osascript <<EOT
tell application "System Events"
      activate
      set netname to text returned of (display dialog "Please Input Target User Name" default answer "first.last" with icon 1)
end tell
EOT`

#Test to see if the target account is currently logged in.
if [[ "$netname" = `python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'` ]]; then
#Exit if target user is currently logged in
/usr/bin/osascript <<EOT
  tell application "System Events"
    activate
    display dialog "$netname is logged in, and we shouldn't mess with it now. Log in as admin and try again." with icon 2 buttons {"Cancel"} default button 1 with hidden answer
end tell
EOT
exit 1

fi


  accounttype=`/usr/bin/dscl . -read /Users/"$netname" AuthenticationAuthority | head -2 | awk -F'/' '{print $2}' | tr -d '
'`

        if [[ "$accounttype" = "Active Directory" ]]; then
            mobileusercheck=`/usr/bin/dscl . -read /Users/"$netname" AuthenticationAuthority | head -2 | awk -F'/' '{print $1}' | tr -d '
' | sed 's/^[^:]*: //' | sed s/;/""/g`
            if [[ "$mobileusercheck" = "LocalCachedUser" ]]; then
               #/usr/bin/printf "$netname has an AD mobile account.
Converting to a local account with the same username and UID.
"
/usr/bin/osascript <<EOT
tell application "System Events"
  activate
    display dialog "$netname is a Local Cached User via AD. Click to begin conversion" with icon 1 buttons {"Continue"} default button 1 with hidden answer
end tell
EOT
            else
               #/usr/bin/printf "The $netname account is not a AD mobile account
"
/usr/bin/osascript <<EOT
tell application "System Events"
  activate
    display dialog "The $netname account is not a Local Cashed User." with icon 2 buttons {"Cancel"} default button 1 with hidden answer
end tell
EOT
        exit 1
            fi
        else
            #/usr/bin/printf "The $netname account is not a AD mobile account
"
/usr/bin/osascript <<EOT
  tell application "System Events"
    activate
        display dialog "The $netname account is not an Active Directory account." with icon 2 buttons {"Cancel"} default button 1 with hidden answer
end tell
EOT
      exit 1
        fi
#Now do the deeds
# Preserve the account password by backing up password hash
            shadowhash=$(/usr/bin/dscl -plist . -read /Users/$netname AuthenticationAuthority | xmllint --xpath 'string(//string[contains(text(),"ShadowHash")])' -)
# Remove the account attributes that identify it as an Active Directory mobile account
            /usr/bin/dscl . -delete /users/$netname cached_groups
            /usr/bin/dscl . -delete /users/$netname cached_auth_policy
            /usr/bin/dscl . -delete /users/$netname CopyTimestamp
            /usr/bin/dscl . -delete /users/$netname AltSecurityIdentities
            /usr/bin/dscl . -delete /users/$netname SMBPrimaryGroupSID
            /usr/bin/dscl . -delete /users/$netname OriginalAuthenticationAuthority
            /usr/bin/dscl . -delete /users/$netname OriginalNodeName
            /usr/bin/dscl . -delete /users/$netname AuthenticationAuthority
            /usr/bin/dscl . -create /users/$netname AuthenticationAuthority "${shadowhash}"
            /usr/bin/dscl . -delete /users/$netname SMBSID
            /usr/bin/dscl . -delete /users/$netname SMBScriptPath
            /usr/bin/dscl . -delete /users/$netname SMBPasswordLastSet
            /usr/bin/dscl . -delete /users/$netname SMBGroupRID
            /usr/bin/dscl . -delete /users/$netname PrimaryNTDomain
            /usr/bin/dscl . -delete /users/$netname AppleMetaRecordName
            /usr/bin/dscl . -delete /users/$netname PrimaryNTDomain
            /usr/bin/dscl . -delete /users/$netname MCXSettings
            /usr/bin/dscl . -delete /users/$netname MCXFlags
# Refresh Directory Services
            if [[ ${osvers} -ge 7 ]]; then
                /usr/bin/killall opendirectoryd
            else
                /usr/bin/killall DirectoryService
            fi
#Pause
            sleep 20
#Test for success
            accounttype=`/usr/bin/dscl . -read /Users/"$netname" AuthenticationAuthority | head -2 | awk -F'/' '{print $2}' | tr -d '
'`
            if [[ "$accounttype" = "Active Directory" ]]; then
               /usr/bin/printf "Something went wrong with the conversion process.
The $netname account is still an AD mobile account.
"
/usr/bin/osascript <<EOT
tell application "System Events"
      activate
      display dialog "Something went wrong with the conversion process. The $netname account is still an AD mobile account." with icon 2 buttons {"Cancel"} default button 1 with hidden answer
end tell
EOT
               exit 1
             else
               /usr/bin/printf "Conversion process was successful.
The $netname account is now a local account.
"
            fi
#Update home folder permissions
            homedir=`/usr/bin/dscl . -read /Users/"$netname" NFSHomeDirectory  | awk '{print $2}'`
            if [[ "$homedir" != "" ]]; then
               /bin/echo "Home directory location: $homedir"
               /bin/echo "Updating home folder permissions for the $netname account"
               /usr/sbin/chown -R "$netname" "$homedir"
            fi
# Add user to the staff group on the Mac
            /bin/echo "Adding $netname to the staff group on this Mac."
            /usr/sbin/dseditgroup -o edit -a "$netname" -t user staff
#Echo user info
            /bin/echo "Displaying user and group information for the $netname account"
            /usr/bin/id $netname
exit 0

lisacherie
Contributor II

Thanks for sharing your script too.

The script in the OP still works last tried 10.13.6 if looking for a silent way to convert all accounts via policy on the Mac in one go.

If wanting to keep the AD bind - can comment out the few rows which remove it.

joelee
New Contributor

@ethan.delavan Does your script with work with Macs that are on Mojave?

jameson
Contributor II

For Mojave you can also use nomad login if script above is not working

CardFlightIT
New Contributor

This doesn't work for mobile Open Ldap accounts. I get "The user is not an AD user" error. Any chance this could work with not just AD?

leslie
Contributor II
Contributor II

@CardFlightIT - Afraid I don't have any Open LDAP accounts to test against, but if you're interested in testing here is another mobile to local project: https://github.com/BIG-RAT/mobile_to_local

hquinte
New Contributor

Hi @lisacherie

Thanks for that script. I made it work with MacOS 10.13, but when I try it for the latest version of Mojave, for some reason the password of the user is not being stored, because when I try to login after a restart after the script ran, it doesn't allow me to login with the same password. I haven't been able to figure it out, but do you know if maybe the password hash storage path has been changed or if there is something I should modified for this to happen?

I am newbie in scripting so a help could be very useful here.

Thanks,
Hugo Quinte

lisacherie
Contributor II

I haven't actually needed to use this script since 10.13.something... it was working at that point.

Since I no longer have access to an active directory environment, I'd do a dscl output prior to the change, and then afterwards. Also compare dscl fields with a local user (that was never an AD mobile account). That will help see what fields might now be different. If you have that output, I can look quickly - however not much I can do without an AD environment.

arepko
New Contributor III

Just wondering if anyone has gotten any variant of this to work with Catalina!

leslie
Contributor II
Contributor II

@arepko - Was the following not converting accounts?
https://github.com/BIG-RAT/mobile_to_local

arepko
New Contributor III

@leslie It didn't do anything, though I was referring to the original script. I'll give that link (and app) you just linked a go!

arepko
New Contributor III

@leslie The App seems to work like a champ! Thanks!

joshuaaclark
Contributor
@rtrouton Has worked his magic, and made an interactive version with some extra features. https://derflounder.wordpress.com/2016/12/21/migrating-ad-mobile-accounts-to-local-user-accounts/

Can confirm that the Der Flounder script works with 10.14 and 10.15

TomDay
Release Candidate Programs Tester

@leslie This was an amazing find, THANK YOU! https://github.com/BIG-RAT/mobile_to_local

MikaelDez
Contributor

Running the Der Flounder script, I get this output:

Running mobileToLocalAccount.sh Version 1.4 **
This machine is bound to Active Directory.
Do you want to unbind this Mac from AD?
1) Yes
2) No

? 1

AD binding has been removed.

Select a user to convert or select FINISHED:
1) mdesmar5
2) FINISHED

? 1

mdesmar5 has an AD mobile account.
Converting to a local account with the same username and UID.
warning: failed to load external entity "–xpath"
warning: failed to load external entity "string(//string[contains(text(),"Kerberosv5")])"
warning: failed to load external entity "–"
warning: failed to load external entity "–xpath"
warning: failed to load external entity "string(//string[contains(text(),"LocalCachedUser")])"
warning: failed to load external entity "–"

Any ideas?

mwu1876
Contributor

Update: I worked it out. I got the latest version from GitHub and modified it for the logged on user and then made it a self service policy. Works great now. Great script!

https://github.com/rtrouton/rtrouton_scripts/blob/master/rtrouton_scripts/migrate_ad_mobile_account_to_local_account/MigrateADMobileAccounttoLocalAccount.command

MikaelDez
Contributor

I tested the OP script on Big Sur 11.2.3 in my VM, which worked and converted from mobile to local and removed the AD bind, but it removed the user from Filevault and the password is changed. I can reset using my other local account, just FYI! I'm trying to work through it, if anyone has an idea on how to prevent that I'd appreciate it, until then it's still a great script that needs a little hands on work.

Did you ever figure out what was causing this.

Just ran it on 12.5.  Completely removed the user account from System Preferences > Accounts but the profile directory still existed in /Users.  It also changed the password and could not sign back in.

It did unbind the machine...