Creating local user accounts with pycreateuserpkg
As part of setting up new Macs, you may want to add one or more local user accounts with a pre-determined password to those Macs. The reasons for this may include the following:
- Setting up a local administrator account
- Setting up a “loaner” user account for a pool of loaner laptops
- Setting up a local user account that automatically logs at startup for a library kiosk
- Setting up a generic “student” account for use in a school’s computer lab
Previously, it was possible to use the venerable CreateUserPkg utility to accomplish this goal, but the password scheme used by CreateUserPkg stopped working on macOS High Sierra. An alternative tool which works on macOS High Sierra is pycreateuserpkg, a Python script written by Greg Neagle which generates packages that create local user accounts when installed. For more information, see below the jump.
Using pycreateuserpkg:
1. Download pycreateuserpkg from GitHub using the link below:
https://github.com/gregneagle/pycreateuserpkg
2. Decide on the settings you want for the new user account, using the options available below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Required User Options: | |
-n NAME, –name=NAME: User shortname. REQUIRED. | |
-u UID, –uid=UID: User uid. REQUIRED. | |
-p PASSWORD, –password=PASSWORD: User password. REQUIRED. | |
Note: If the password option is not selected, you will be prompted for a password for | |
the account during the package creation process. | |
Required Package Options: | |
-V VERSION, –version=VERSION: Package version number. REQUIRED. | |
-i IDENTIFIER, –identifier=IDENTIFIER: Package identifier. REQUIRED. | |
Optional User Options: | |
-f FULLNAME, –fullname=FULLNAME: User full name. Optional. | |
-g GID, –gid=GID: User GID, otherwise known as a group identifier. Optional. | |
-H HOME, –home=HOME: Path to user home directory. Optional. | |
-s SHELL, –shell=SHELL: User shell path. Optional. | |
-a, –admin: User account should be added to admin group. | |
-A, –autologin: User account should automatically login. |
As an example, let’s create an installer package which installs a local user account with the following characteristics:
Account’s shortname: kiosk
Account’s displayed name: Kiosk
UID: 604
Account shell: /bin/bash
Account should have admin rights: No
Account should auto-login: Yes
Since we also need to provide version and identifier information for the installer package, we’ll use the following characteristics:
Version number: 1.0
Package identifier: com.github.kiosk_user_setup
To create the installer package which installs the previously-defined local account, make sure you have a copy of pycreateuserpkg available, then use the command shown below:
/path/to/createuserpkg --name="kiosk" --uid=604 --fullname="Kiosk" --shell="/bin/bash" --version=1.0 --identifier="com.github.kiosk_user_setup" "Create Kiosk User Account.pkg" --autologin
Since we have not defined what the account’s password will be as part of the command above, we will be prompted to enter the password then enter it again to verify:
That should generate a new installer package named Create Kiosk User Account.pkg.
Testing pycreateuserpkg-generated installers
Once the package has been built, test it by taking the pycreateuserpkg-generated installer package and install it on a Mac which does not have the local account set up on it. The end result should be that the local account is set up on the Mac and configured with the desired settings and account rights.
To give some additional examples, let’s create a local administrator account with the following characteristics:
Account’s shortname: admin
Account’s displayed name: Administrator
UID: 600
Account shell: /bin/bash
Account should have admin rights: Yes
Account should auto-login: No
Since we also need to provide version and identifier information for the installer package, we’ll use the following characteristics:
Version number: 1.0
Package identifier: com.github.admin_user_setup
To create the installer package which installs the previously-defined local account, make sure you have a copy of pycreateuserpkg available, then use the command shown below:
/path/to/createuserpkg --name="admin" --uid=600 --fullname="Administrator" --shell="/bin/bash" --admin --version=1.0 --identifier="com.github.admin_user_setup" "Create Local Admin User Account.pkg"
When the package is installed, an account like this should now appear in the Users & Groups preferences in System Preferences.
Finally, let’s create a standard user local account with the following characteristics:
Account’s shortname: standarduser
Account’s displayed name: Standard User
UID: 603
Account shell: /bin/bash
Account should have admin rights: No
Account should auto-login: No
Since we also need to provide version and identifier information for the installer package, we’ll use the following characteristics:
Version number: 1.0
Package identifier: com.github.standard_user_user_setup
To create the installer package which installs the previously-defined local account, make sure you have a copy of pycreateuserpkg available, then use the command shown below:
/path/to/createuserpkg --name="standarduser" --uid=603 --fullname="Standard User" --shell="/bin/bash" --version=1.0 --identifier="com.github.standard_user_user_setup" "Create Standard User User Account.pkg"
When the package is installed, an account like this should now appear in the Users & Groups preferences in System Preferences.
How pycreateuserpkg works
pycreateuserpkg works by creating a plist file for the specified local user account, which allows the account information to work on 10.8.x and later. The user’s account information is written to a plist file and stored in the directory listed below:
/private/var/db/dslocal/nodes/Default/users
An example account plist is shown below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>ShadowHashData</key> | |
<array> | |
<data> | |
YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhUc2Fs | |
dFdlbnRyb3B5Wml0ZXJhdGlvbnNPECAN9JFNQar3istN4qimB4Tp0j1uGmV5 | |
rEfe9qUoLohPOU8QgEiRp6xYOrYgjZfmAhVlPzNReytc906hcOH2ZbDcOtW8 | |
0c/ElK3n4Y0mkKd4UGJxC4/aYvknVVaEAaEVedCcvhub00HzHir16l7gYNbU | |
/QWrEo0JnIt93OIm2/Sgfm49arOqgqXM4r6msfu2uF6F4jAlTYYXytd8mgWC | |
MejFXD/nEcEDCAsiKS42QWTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA | |
AAAAAOo= | |
</data> | |
</array> | |
<key>_writers_UserCertificate</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>_writers_hint</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>_writers_jpegphoto</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>_writers_passwd</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>_writers_picture</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>_writers_realname</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>authentication_authority</key> | |
<array> | |
<string>;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2></string> | |
</array> | |
<key>generateduid</key> | |
<array> | |
<string>CB18DA2D-D70A-429D-9E76-413C5D0A54A2</string> | |
</array> | |
<key>gid</key> | |
<array> | |
<string>20</string> | |
</array> | |
<key>home</key> | |
<array> | |
<string>/Users/username</string> | |
</array> | |
<key>name</key> | |
<array> | |
<string>username</string> | |
</array> | |
<key>passwd</key> | |
<array> | |
<string>********</string> | |
</array> | |
<key>realname</key> | |
<array> | |
<string>User Name</string> | |
</array> | |
<key>shell</key> | |
<array> | |
<string>/bin/bash</string> | |
</array> | |
<key>uid</key> | |
<array> | |
<string>602</string> | |
</array> | |
</dict> | |
</plist> |
If the auto-login option is selected, an additional /etc/kcpassword file is also installed to facilitate the auto-login process.
If the auto-login option is not selected, the /etc/kcpassword file will not be installed.
Once the necessary file(s) are created by pycreateuserpkg, the utility then generates an installer package and post-installation script to install the account’s plist and the /etc/kcpassword file (if needed) into their proper places. An example postinstall script from a pycreateuserpkg-generated installer package is shown below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# postinstall for local account install | |
PlistArrayAdd() { | |
# Add $value to $array_name in $plist_path, creating if necessary | |
local plist_path="$1" | |
local array_name="$2" | |
local value="$3" | |
local old_values | |
local item | |
old_values=$(/usr/libexec/PlistBuddy -c "Print :$array_name" "$plist_path" 2>/dev/null) | |
if [[ $? == 1 ]]; then | |
# Array doesn't exist, create it | |
/usr/libexec/PlistBuddy -c "Add :$array_name array" "$plist_path" | |
else | |
# Array already exists, check if array already contains value | |
IFS=$'\012' | |
for item in $old_values; do | |
unset IFS | |
if [[ "$item" =~ ^\ *$value$ ]]; then | |
# Array already contains value | |
return 0 | |
fi | |
done | |
unset IFS | |
fi | |
# Add item to array | |
/usr/libexec/PlistBuddy -c "Add :$array_name: string \"$value\"" "$plist_path" | |
} | |
ACCOUNT_TYPE=ADMIN # Used by read_package.py. | |
PlistArrayAdd "$3/private/var/db/dslocal/nodes/Default/groups/admin.plist" users "username" && \ | |
PlistArrayAdd "$3/private/var/db/dslocal/nodes/Default/groups/admin.plist" groupmembers "CB18DA2D-D70A-429D-9E76-413C5D0A54A2" | |
if [ "$3" == "/" ]; then | |
# we're operating on the boot volume | |
# kill local directory service so it will see our local | |
# file changes — it will automatically restart | |
/usr/bin/killall DirectoryService 2>/dev/null || /usr/bin/killall opendirectoryd 2>/dev/null | |
fi | |
exit 0 |
Note: The postinstall script may have different functionality, depending on which options are selected. For example, this is the postinstall script generated for an installer package which installs a standard user account which is set to auto-login:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# postinstall for local account install | |
if [ "$3" == "/" ] ; then | |
# work around path issue with 'defaults' | |
/usr/bin/defaults write "/Library/Preferences/com.apple.loginwindow" autoLoginUser "kiosk" | |
else | |
/usr/bin/defaults write "$3/Library/Preferences/com.apple.loginwindow" autoLoginUser "kiosk" | |
fi | |
/bin/chmod 644 "$3/Library/Preferences/com.apple.loginwindow.plist" | |
if [ "$3" == "/" ]; then | |
# we're operating on the boot volume | |
# kill local directory service so it will see our local | |
# file changes — it will automatically restart | |
/usr/bin/killall DirectoryService 2>/dev/null || /usr/bin/killall opendirectoryd 2>/dev/null | |
fi | |
exit 0 |
Once the pycreateuserpkg-generated package is installed, the account’s file(s) are put into the necessary places and the postinstall script handles any necessary preparation needed for the account to work properly.
I’ve tried using this but the computer doesn’t recognize the password I’ve set after package creation.
I’ve create 5 pkgs with five different user names and UIDs. When I install the 3rd pkg, it adds the 3rd account but the first account is removed but the home folder remains. The account is gone from the GUI and when viewing users with dscl. When I run the fourth pkg, then the 2nd account disappears. (Only tested with Sierra 10.12.6)
It might be related to not waiting long enough for the DirectoryService to start again. Has anyone else seen this issue?
Did you ever find a solution to that issue? I am trying to setup something with multiple admin accounts now and having the same issue.
This worked perfectly for me. I was wondering, though, is there a way to modify this to instead prepare the Mac to automatically log in to a AD account? I know we would have to send a binding command a head of the account creation PKG, but it seems like some of this script should be reusable for that.
Thoughts or advice? This is just beyond my skill set to decipher.
This is a great script, especially now with Thin Imaging upon us. Question, is there a way to customize the User Library, in a post install? Say I have a Library for an admin user I want to use.
Are you aware of any issues with this software regarding users with uids below 500 (say 499) and Mojave? I’ve always created a hidden admin with uid 499, but it no longer works when installed on a fresh copy of Mojave. Same install package with a uid above 500 works just fine. I’m thinking there are some new uid restrictions with Mojave, but still not sure.
Hey, I need to test that, I wasn’t aware of this issue. I have a Mac that I upgraded to Mojave that has a hidden admin at 489 user id, which works, but I haven’t tried on a straight install. I will try and reply back.
Thanks
Definitely curious to see what you find out. Same thing with me, users below 500 that were upgraded to Mojave work fine, but not fresh installs. The package installs fine on a fresh copy, but when I go to authenticate as an admin it just shakes at me.
Did some more testing and uid below 500 doesn’t seem to be an issue. Not sure what was causing me issues previously, but I’ve gotten it working now.
I’m trying to run this on Mojave but the –autlogin doesn’t seem to work. Was just wondering if it still works or broke with 10.14.1?
Having a problem with saving the file to my desktop, I get an error that says Must provide exactly one file name. Also, what exactly should the identifier be?
Saving the script file?
yeah, this is what I”m running, /Users/name/Downloads/pycreateuserpkg-master/createuserpkg –name=”admin” –uid=600 –fullname=”admin” –shell=”/bin/bash” –admin –version=1.0 –identifier=”com.name.admin_user” “create_admin_user.pkg” /Users/name/Desktop/admin.pkg
Must provide exactly one filename!
figured this out
How did you figure it out? What was the solution? Why do people do this? Figure out their problem then tell the whole world they fixed it but withhold the actual solution. smh.
Is there a way to select on which volume you want the account to be created?
With the original createpkguser installer you could select an external volumes so an account could be created on a Target booted system
doh! i just found the change install location button, it’s been a long day