Mastering macOS: Streamline Admin User Creation with Efficient Scripting

Key takeaways

  • Automates admin user creation: Streamlines and standardizes the process of adding new admin users on Mac OS.
  • Enhanced efficiency and security: Reduces manual errors and ensures consistent security practices in user creation.
  • Robust argument parsing: Handles user input effectively, including missing arguments and help requests.
  • Unique user ID allocation: Smartly generates unique IDs, crucial for Mac OS user management.
  • Comprehensive user setup: Sets up necessary user attributes like shell, home directory, and group memberships.
  • Error handling and root privileges: Incorporates error checks and requires root access, enhancing security and reliability.
  • Scalable for MSPs and IT professionals: Ideal for managing multiple systems, saving time and effort in large-scale deployments.
  • Compared to manual methods: Offers a more scalable and error-proof alternative to manual user creation methods.
  • Security considerations: Important to secure the script itself, given its powerful administrative capabilities.
  • Integration with management tools: Can be effectively integrated into IT management platforms like NinjaOne for enhanced control and monitoring.

Creating user accounts is a fundamental task in systems administration, ensuring secure and organized access to resources and services. On Mac OS, this process takes on added nuances due to its unique operating system architecture and security protocols. Automating this process not only streamlines administrative tasks but also enhances the security and efficiency of IT operations.

Background

In the world of IT, particularly for Managed Service Providers (MSPs) and IT professionals, efficiency and security are paramount. The script under discussion is a bash script specifically designed for Mac OS, intended to automate the creation of a new administrator user. This tool is crucial because it standardizes user creation, reducing human error and securing the system by ensuring that all necessary steps and permissions are correctly implemented.

The script:

#!/usr/bin/env bash
#
# Description: Create a new admin user for Mac.
# By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
# Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
# Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
# Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
# Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
# Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
# Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
# EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).

# Usage: [-u|--user <arg>] [-p|--pass <arg>] [-h|--help]
# -u, --user: User Name for new user account. (no default)
# -p, --pass: Password for new user account. (no default)
# -h, --help: Prints help

# # When called, the process ends.
# Args:
# 	$1: The exit message (print to stderr)
# 	$2: The exit code (default is 1)
# if env var _PRINT_HELP is set to 'yes', the usage is print to stderr (prior to $1)
# Example:
# 	test -f "$_arg_infile" || _PRINT_HELP=yes die "Can't continue, have to supply file as an argument, got '$_arg_infile'" 4
die() {
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

# Function that evaluates whether a value passed to it begins by a character
# that is a short option of an argument the script knows about.
# This is required in order to support getopts-like short options grouping.
begins_with_short_option() {
    local first_option all_short_options='uph'
    first_option="${1:0:1}"
    test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}

getHiddenUserUid() {
    local __UniqueIDs
    __UniqueIDs=$(dscl . -list /Users UniqueID | awk '{print $2}' | sort -ugr)

    local __NewUID
    for __NewUID in $__UniqueIDs; do
        if [[ $__NewUID -lt 499 ]]; then
            break
        fi
    done

    echo $((__NewUID + 1))
}

_arg_user=
_arg_pass=

# Function that prints general usage of the script.
# This is useful if users asks for it, or if there is an argument parsing error (unexpected / spurious arguments)
# and it makes sense to remind the user how the script is supposed to be called.
print_help() {
    printf '%s\n' "Create a new admin user."
    printf 'Usage: %s [-u|--user <arg>] [-p|--pass <arg>] [-h|--help]\n' "$0"
    printf '\t%s\n' "-u, --user: User Name for new user account. (no default)"
    printf '\t%s\n' "-p, --pass: Password for new user account. (no default)"
    printf '\t%s\n' "-h, --help: Prints help"
}

# The parsing of the command-line
parse_commandline() {
    while test $# -gt 0; do
        _key="$1"
        case "$_key" in
        # We support whitespace as a delimiter between option argument and its value.
        # Therefore, we expect the --user or -u value.
        # so we watch for --user and -u.
        # Since we know that we got the long or short option,
        # we just reach out for the next argument to get the value.
        -u | --user)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_user="$2"
            shift
            ;;
        # We support the = as a delimiter between option argument and its value.
        # Therefore, we expect --user=value, so we watch for --user=*
        # For whatever we get, we strip '--user=' using the ${var##--user=} notation
        # to get the argument value
        --user=*)
            _arg_user="${_key##--user=}"
            ;;
        # We support getopts-style short arguments grouping,
        # so as -u accepts value, we allow it to be appended to it, so we watch for -u*
        # and we strip the leading -u from the argument string using the ${var##-u} notation.
        -u*)
            _arg_user="${_key##-u}"
            ;;
        # See the comment of option '--user' to see what's going on here - principle is the same.
        -p | --pass)
            test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
            _arg_pass="$2"
            shift
            ;;
        # See the comment of option '--user=' to see what's going on here - principle is the same.
        --pass=*)
            _arg_pass="${_key##--pass=}"
            ;;
        # See the comment of option '-u' to see what's going on here - principle is the same.
        -p*)
            _arg_pass="${_key##-p}"
            ;;
        # The help argument doesn't accept a value,
        # we expect the --help or -h, so we watch for them.
        -h | --help)
            print_help
            exit 0
            ;;
        # We support getopts-style short arguments clustering,
        # so as -h doesn't accept value, other short options may be appended to it, so we watch for -h*.
        # After stripping the leading -h from the argument, we have to make sure
        # that the first character that follows corresponds to a short option.
        -h*)
            print_help
            exit 0
            ;;
        *)
            _PRINT_HELP=yes die "FATAL ERROR: Got an unexpected argument '$1'" 1
            ;;
        esac
        shift
    done
}

parse_commandline "$@"

if [[ -n "${username}" ]]; then
    _arg_user=$username
fi
if [[ -n "${password}" ]]; then
    _arg_pass=$password
fi

if [[ -z "${_arg_user}" ]]; then
    die "FATAL ERROR: User Name is required. '$_arg_user'" 1
fi

if [[ -z "${_arg_pass}" ]]; then
    die "FATAL ERROR: Password is required. '$_arg_pass'" 1
fi

UniqueID=$(getHiddenUserUid)
if [ "$(id -u)" -eq 0 ]; then
    if dscl . -create /Users/"$_arg_user"; then
        dscl . -create /Users/"$_arg_user" UserShell /bin/bash
        dscl . -create /Users/"$_arg_user" RealName "$_arg_user"
        dscl . -create /Users/"$_arg_user" UniqueID "$UniqueID"
        dscl . -create /Users/"$_arg_user" PrimaryGroupID 20
        dscl . -create /Users/"$_arg_user" NFSHomeDirectory /Users/"$_arg_user"
        dscl . -passwd /Users/"$_arg_user" "$_arg_pass"
        dscl . -append /Groups/admin GroupMembership "$_arg_user"
        dscl . -append /Groups/_lpadmin GroupMembership "$_arg_user"
        dscl . -append /Groups/_appserveradm GroupMembership "$_arg_user"
        dscl . -append /Groups/_appserverusr GroupMembership "$_arg_user"
        createhomedir -c 2>&1 | grep -v "shell-init"
    else
        echo "ERROR: Failed to create user. Already exists?"
        exit 1
    fi
else
    echo "Only root may add a user to the system."
    exit 2
fi

 

Access 300+ scripts in the NinjaOne Dojo

Get Access

Detailed breakdown

The script operates in several distinct phases:

  • Argument parsing: It begins by parsing arguments provided during its invocation. These arguments include the username and password for the new account, which are crucial for account creation. The script elegantly handles missing arguments, errors, or requests for help, guiding the user appropriately.
  • User ID generation: A unique user ID is generated for the new user. Mac OS requires each user to have a unique identifier, and this script smartly identifies an unused ID by analyzing existing ones.
  • Account creation: Once the username, password, and unique ID are set, the script proceeds to create the user. It assigns a shell (/bin/bash), sets a real name, user ID, and primary group ID, and specifies a home directory. Importantly, it adds the user to several administrative groups, granting necessary permissions.
  • Home directory initialization: The script then initializes the home directory for the new user, an essential step for user account functionality.
  • Error handling and permissions: It includes robust error handling, ensuring that the script exits with appropriate messages and statuses when encountering issues. Notably, the script requires root privileges, reflecting its administrative nature.

Potential use cases

Imagine an MSP tasked with setting up multiple Mac OS systems for a new corporate client. Traditionally, this would require manually creating user accounts on each machine, a time-consuming and error-prone process. With this script, the MSP can quickly, uniformly, and securely set up administrator accounts across all machines, ensuring consistency and saving considerable time.

Comparisons

Traditional methods of adding users on Mac OS involve using the System Preferences interface or employing basic terminal commands. While functional for individual cases, these methods lack scalability and are prone to inconsistency. This script automates the process, ensuring each account is created following best practices and with the exact permissions required.

FAQs

  • How secure is this script for creating admin users?
    • The script is designed with security in mind, requiring root access and handling sensitive information like passwords carefully.
  • Can the script handle multiple user creations in one go?
    • As written, it creates one user per invocation. However, it can be modified or looped in a larger script to create multiple users.

Implications

Automated scripts, while efficient, carry implications for IT security. If misused or accessed by unauthorized users, they could be a vector for creating unauthorized access points. Thus, securing the script itself is as important as securing the system it operates on.

Recommendations

  • Always run such scripts with the highest security standards in mind.
  • Regularly audit and update the script to ensure compatibility with the latest Mac OS versions.
  • Limit access to the script to authorized personnel only.

Final thoughts

Incorporating tools like NinjaOne can augment the utility of scripts like this. NinjaOne provides a platform for managing IT operations, where scripts can be deployed, monitored, and managed efficiently. Integrating automated scripts into a broader management framework like NinjaOne can significantly enhance an organization’s IT efficiency and security posture, especially when dealing with diverse and complex environments such as multiple Mac OS systems.

Next Steps

Building an efficient and effective IT team requires a centralized solution that acts as your core service deliver tool. NinjaOne enables IT teams to monitor, manage, secure, and support all their devices, wherever they are, without the need for complex on-premises infrastructure.

Learn more about NinjaOne Remote Script Deployment, check out a live tour, or start your free trial of the NinjaOne platform.

Categories:

You might also like

×

See NinjaOne in action!

By submitting this form, I accept NinjaOne's privacy policy.

NinjaOne Terms & Conditions

By clicking the “I Accept” button below, you indicate your acceptance of the following legal terms as well as our Terms of Use:

  • Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms.
  • Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party.
  • Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library belonging to or under the control of any other software provider.
  • Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations.
  • Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks.
  • Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script.
  • EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).