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.