Skip to main content
Question

Change local user account(s) with SwiftDialog UI

  • February 7, 2025
  • 1 reply
  • 3 views

Forum|alt.badge.img+9

Back at again with another script that has been useful in our company.  Before I took over as Mac admin, the users were creating their IDs with their employee# vs their first.last name.  As such we have a mixture of systems with incorrect UserID, so I borrowed this script (author unknown) from a very very old JAMF post and thought I would make it more user friendly by adding some Swift Dialog "flare" to it.  

The user that you are wanting to change cannot be logged in for this, so what I do is scope it to an admin ID that is present on all of the computers, and I walk the user through how to login to the admin ID (the password changes after they login using LAPS, so no worries there) and then I instruct them on how to run the script.  It only takes a few seconds to run, and will restart the computer once it is done.  Here is the script

#!/bin/zsh ###################################################################################################### # # Gobal "Common" variables (do not change these!) # ###################################################################################################### export PATH=/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin LOGGED_IN_USER=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' ) USER_DIR=$( dscl . -read /Users/${LOGGED_IN_USER} NFSHomeDirectory | awk '{ print $2 }' ) # Swift Dialog version requirements SW_DIALOG="/usr/local/bin/dialog" [[ -e "${SW_DIALOG}" ]] && SD_VERSION=$( ${SW_DIALOG} --version) || SD_VERSION="0.0.0" MIN_SD_REQUIRED_VERSION="2.4.0" DIALOG_INSTALL_POLICY="install_SwiftDialog" SUPPORT_FILE_INSTALL_POLICY="install_SymFiles" ################################################### # # App Specfic variables (Feel free to change these) # ################################################### SUPPORT_DIR="/Library/Application Support/GiantEagle" SD_BANNER_IMAGE="${SUPPORT_DIR}/SupportFiles/GE_SD_BannerImage.png" LOG_DIR="${SUPPORT_DIR}/logs" LOG_FILE="${LOG_DIR}/AppDelete.log" LOG_STAMP=$(echo $(/bin/date +%Y%m%d)) ICON_FILES="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/" STOP_ICON="${ICON_FILES}AlertStopIcon.icns" GROUP_ICON="${ICON_FILES}GroupIcon.icns" SUCCESS_ICON="${ICON_FILES}Toolbarinfo.icns" JSON_OPTIONS=$(mktemp /var/tmp/MigrateAccount.XXXXX) chmod 666 "${JSON_OPTIONS}" DIALOG_COMMAND_FILE=$(mktemp /var/tmp/MigrateAccount.XXXXX) chmod 666 "${DIALOG_COMMAND_FILE}" BANNER_TEXT_PADDING=" " #5 spaces to accomodate for icon offset SD_INFO_BOX_MSG="" SD_WINDOW_TITLE="${BANNER_TEXT_PADDING}Migrate Account" SD_DIALOG_GREETING=$((){print Good ${argv[2+($1>11)+($1>18)]}} ${(%):-%D{%H}} morning afternoon evening) USERS_ON_SYSTEM=$( dscl . ls /Users | grep -v '_' | grep -v 'root' | grep -v 'daemon'| grep -v 'nobody'| grep -v $LOGGED_IN_USER | tr '\\n' ',' ) USERS_ON_SYSTEM=${USERS_ON_SYSTEM:0:-1} # remove the last , from the list so it doesn't create a blank SD entry ################################################## # # Passed in variables # ################################################# JAMF_LOGGED_IN_USER=$3 # Passed in by JAMF automatically SD_FIRST_NAME="${(C)JAMF_LOGGED_IN_USER%%.*}" #################################################################################################### # # Functions # #################################################################################################### function create_log_directory () { # Ensure that the log directory and the log files exist. If they # do not then create them and set the permissions. # # RETURN: None # If the log directory doesnt exist - create it and set the permissions [[ ! -d "${LOG_DIR}" ]] && /bin/mkdir -p "${LOG_DIR}" /bin/chmod 755 "${LOG_DIR}" # If the log file does not exist - create it and set the permissions [[ ! -f "${LOG_FILE}" ]] && /usr/bin/touch "${LOG_FILE}" /bin/chmod 644 "${LOG_FILE}" } function logMe () { # Basic two pronged logging function that will log like this: # # 20231204 12:00:00: Some message here # # This function logs both to STDOUT/STDERR and a file # The log file is set by the $LOG_FILE variable. # # RETURN: None echo "${1}" 1>&2 echo "$(/bin/date '+%Y-%m-%d %H:%M:%S'): ${1}" | tee -a "${LOG_FILE}" } function check_swift_dialog_install () { # Check to make sure that Swift Dialog is installed and functioning correctly # Will install process if missing or corrupted # # RETURN: None logMe "Ensuring that swiftDialog version is installed..." if [[ ! -x "${SW_DIALOG}" ]]; then logMe "Swift Dialog is missing or corrupted - Installing from JAMF" install_swift_dialog SD_VERSION=$( ${SW_DIALOG} --version) fi if ! is-at-least "${MIN_SD_REQUIRED_VERSION}" "${SD_VERSION}"; then logMe "Swift Dialog is outdated - Installing version '${MIN_SD_REQUIRED_VERSION}' from JAMF..." install_swift_dialog else logMe "Swift Dialog is currently running: ${SD_VERSION}" fi } function install_swift_dialog () { # Install Swift dialog From JAMF # PARMS Expected: DIALOG_INSTALL_POLICY - policy trigger from JAMF # # RETURN: None /usr/local/bin/jamf policy -trigger ${DIALOG_INSTALL_POLICY} } function check_support_files () { [[ ! -e "${SD_BANNER_IMAGE}" ]] && /usr/local/bin/jamf policy -trigger ${SUPPORT_FILE_INSTALL_POLICY} } function update_display_list () { # setopt -s nocasematch # This function updates the Swift Dialog list display with easy to implement parameter passing... # The Swift Dialog native structure is very strict with the command structure...this routine makes # it easier to implement # # Param list # # $1 - Action to be done ("Create", "Add", "Change", "Clear", "Info", "Show", "Done", "Update") # ${2} - Affected item (2nd field in JSON Blob listitem entry) # ${3} - Icon status "wait, success, fail, error, pending or progress" # ${4} - Status Text # $5 - Progress Text (shown below progress bar) # $6 - Progress amount # increment - increments the progress by one # reset - resets the progress bar to 0 # complete - maxes out the progress bar # If an integer value is sent, this will move the progress bar to that value of steps # the GLOB :l converts any inconing parameter into lowercase case "${1:l}" in "create" | "show" ) # Display the Dialog prompt eval "dialog --jsonfile ${JSON_OPTIONS}" & sleep .2 ;; "add" ) # Add an item to the list # # $2 name of item # $3 Icon status "wait, success, fail, error, pending or progress" # $4 Optional status text /bin/echo "listitem: add, title: ${2}, status: ${3}, statustext: ${4}" >> "${DIALOG_COMMAND_FILE}" ;; "buttonaction" ) # Change button 1 action /bin/echo 'button1action: "'${2}'"' >> "${DIALOG_COMMAND_FILE}" ;; "buttonchange" ) # change text of button 1 /bin/echo "button1text: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "buttondisable" ) # disable button 1 /bin/echo "button1: disable" >> "${DIALOG_COMMAND_FILE}" ;; "buttonenable" ) # Enable button 1 /bin/echo "button1: enable" >> "${DIALOG_COMMAND_FILE}" ;; "change" ) # Change the listitem Status # Increment the progress bar by static amount ($6) # Display the progress bar text ($5) /bin/echo "listitem: title: ${2}, status: ${3}, statustext: ${4}" >> "${DIALOG_COMMAND_FILE}" if [[ ! -z $5 ]]; then /bin/echo "progresstext: $5" >> "${DIALOG_COMMAND_FILE}" /bin/echo "progress: $6" >> "${DIALOG_COMMAND_FILE}" fi ;; "clear" ) # Clear the list and show an optional message /bin/echo "list: clear" >> "${DIALOG_COMMAND_FILE}" /bin/echo "message: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "delete" ) # Delete item from list /bin/echo "listitem: delete, title: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "destroy" ) # Kill the progress bar and clean up /bin/echo "quit:" >> "${DIALOG_COMMAND_FILE}" ;; "done" ) # Complete the progress bar and clean up /bin/echo "progress: complete" >> "${DIALOG_COMMAND_FILE}" /bin/echo "progresstext: $5" >> "${DIALOG_COMMAND_FILE}" ;; "icon" ) # set / clear the icon, pass <nil> if you want to clear the icon [[ -z ${2} ]] && /bin/echo "icon: none" >> "${DIALOG_COMMAND_FILE}" || /bin/echo "icon: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "image" ) # Display an image and show an optional message /bin/echo "image: ${2}" >> "${DIALOG_COMMAND_FILE}" [[ ! -z ${3} ]] && /bin/echo "progresstext: $5" >> "${DIALOG_COMMAND_FILE}" ;; "infobox" ) # Show text message /bin/echo "infobox: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "infotext" ) # Show text message /bin/echo "infotext: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "show" ) # Activate the dialog box /bin/echo "activate:" >> $"${DIALOG_COMMAND_FILE}" ;; "title" ) # Set / Clear the title, pass <nil> to clear the title [[ -z ${2} ]] && /bin/echo "title: none:" >> "${DIALOG_COMMAND_FILE}" || /bin/echo "title: ${2}" >> "${DIALOG_COMMAND_FILE}" ;; "progress" ) # Increment the progress bar by static amount ($6) # Display the progress bar text ($5) /bin/echo "progress: ${6}" >> "${DIALOG_COMMAND_FILE}" /bin/echo "progresstext: ${5}" >> "${DIALOG_COMMAND_FILE}" ;; esac } function cleanup_and_exit () { # # Expect Parmaters # $1 = Good, Fail, Restart # ExitOptions=$1 [[ -f ${JSON_OPTIONS} ]] && /bin/rm -rf ${JSON_OPTIONS} [[ -f ${TMP_FILE_STORAGE} ]] && /bin/rm -rf ${TMP_FILE_STORAGE} [[ -f ${DIALOG_COMMAND_FILE} ]] && /bin/rm -rf ${DIALOG_COMMAND_FILE} if [[ $ExitOptions == "Good" ]]; then exit 0 elif [[ $ExitOptions == "Fail" ]]; then exit 1 elif [[ $ExitOptions == "Restart" ]]; then update_display_list "image" $SUCCESS_ICON "" "" "Successfully transfered ${oldUser} to ${newUser}. The system needs to restart to finish the process." logMe "Restarting computer after successful rename" osascript -e 'tell application "System Events" to restart' fi } function create_welcome_dialog () { DialogBody=( --message "Please enter the following information below. During this process, the data from the old user will be moved to the new user" --ontop --icon "${GROUP_ICON}" --overlayicon computer --bannerimage "${SD_BANNER_IMAGE}" --bannertitle "${SD_WINDOW_TITLE}" --textfield "Enter the name of the NEW user,required" --selecttitle "Select user to migrate FROM",required --selectvalues "${USERS_ON_SYSTEM}" --width 800 --ignorednd --moveable --quitkey 0 --button1text "OK" --button2text "Cancel" ) # Example of appending items to the display array # [[ ! -z "${SD_IMAGE_TO_DISPLAY}" ]] && DialogBody+=(--height 520 --image "${SD_IMAGE_TO_DISPLAY}") returnval=$("${SW_DIALOG}" "${DialogBody[@]}" 2>/dev/null) [[ "$?" == "2" ]] && cleanup_and_exit "Good" oldUser=$(echo $returnval | grep "SelectedOption" | awk '{print $3}' | tr -d '"' | xargs ) newUser=$(echo $returnval | grep "NEW" | awk -F ":" '{print $2}' | xargs ) } function create_workflow_dialog () { # Have to use the advanced JSON featuers in SwiftDialog since we are going to modify list items along the way echo '{ "icon" : "'${GROUP_ICON}'", "bannerimage" : "'${SD_BANNER_IMAGE}'", "bannertitle" : "'${SD_WINDOW_TITLE}'", "message" : "Proceeding to migrate **'${oldUser}'** to **'${newUser}'**. Doing some validity checks....", "titlefont" : "shadow=1", "button1text" : "OK", "ontop" : "true", "movable" : "true", "commandfile" : "'${DIALOG_COMMAND_FILE}'", "height" : "500", "listitem" : [ { "title" : "Verify Account Info", "status" : "pending", "statustext" : "Pending" }, { "title" : "Retrieve Display Name", "status" : "pending", "statustext" : "Pending" }, { "title" : "Update Display Name", "status" : "pending", "statustext" : "Pending" }, { "title" : "Update NFS Home Directory", "status" : "pending", "statustext" : "Pending" }, { "title" : "Move Files to New User", "status" : "pending", "statustext" : "Pending" }, { "title" : "Verify Rename", "status" : "pending", "statustext" : "Pending" }, ]}' > ${JSON_OPTIONS} } function test_root_user () { # Ensures that script is run as ROOT if [[ "${UID}" != 0 ]]; then MainDialogBody=( --message "In order for this script to function properly, it must be run as an admin user!" --ontop --icon "$STOP_ICON" --overlayicon computer --bannerimage "${SD_BANNER_IMAGE}" --bannertitle "${SD_WINDOW_TITLE}" --button1text "OK" ) # Example of appending items to the display array # [[ ! -z "${SD_IMAGE_TO_DISPLAY}" ]] && MainDialogBody+=(--height 520 --image "${SD_IMAGE_TO_DISPLAY}") "${SW_DIALOG}" "${MainDialogBody[@]}" 2>/dev/null cleanup_and_exit "Fail" fi } function show_err_msg () { # Parameters Expected # # $1 - List item to change # $2 - Message to display # $3 - Status to show # $4 - Icon to show update_display_list "icon" $4 update_display_list "change" "$1" "$3" update_display_list "infotext" "$2" wait } function verify_account_info () { # # This function will perform various validity checks to ensure it is safe to proceed # # # Test #1 - Verify that the accounts differ # update_display_list "change" "Verify Account Info" "wait" if [[ "${oldUser}" == "${newUser}" ]]; then show_err_msg "Verify Account Info" "New user is the same as the old user. No account info will be changed at this time." "fail" $STOP_ICON logMe "ERROR: New user is the same as the old one...no account info changed" cleanup_and_exit "Fail" fi # # Test #2 - Make sure there is not an existing user account already # readonly existingUsers=($(dscl . -list /Users | grep -Ev "^_|com.*|root|nobody|daemon|\\/" | cut -d, -f1 | sed 's/CN=//g')) if [[ " ${existingUsers[@]} " =~ " ${newUser} " ]]; then show_err_msg "Verify Account Info" "The account '${newUser}' is already present on this system. Cannot create the new account at this time." "fail" $STOP_ICON logMe "ERROR: New user account '${newUser}' is already present on this computer...no account info changed" cleanup_and_exit "Fail" fi # # Test #3 - Check to see if new account folder already exists # readonly existingHomeFolders=($(ls /Users)) # Ensure existing home folder is not in use if [[ " ${existingHomeFolders[@]} " =~ " ${newUser} " ]]; then show_err_msg "Verify Account Info" "The home folder for '${newUser}' is already in use on this system. Cannot create the new account at this time." "fail" $STOP_ICON logMe "ERROR: The home folder for '${newUser}' is already present on this computer...no account info changed" cleanup_and_exit "Fail" fi # # Test #4 - logout user if they are already logged in # loginCheck=$(ps -Ajc | grep ${oldUser} | grep loginwindow | awk '{print $2}') # Logs out user if they are logged in timeoutCounter='0' while [[ "${loginCheck}" ]]; do update_display_list "infotext" "${oldUser} account logged in. Logging user off to complete username update." sudo launchctl bootout gui/$(id -u ${oldUser}) sleep 5 loginCheck=$(ps -Ajc | grep ${oldUser} | grep loginwindow | awk '{print $2}') timeoutCounter=$((${timeoutCounter} + 1)) if [[ ${timeoutCounter} -eq 4 ]]; then show_err_msg "Verify Account Info" "Timeout unable to log out ${oldUser} account." "fail" $STOP_ICON logMe "ERROR: Timeout waiting for $oldUser to logout...no account info changed" cleanup_and_exit "Fail" fi done update_display_list "change" "Verify Account Info" "success" } function perform_migration () { ################# # Perform the actual migration, but perform validity checks along the way ################# # # Captures current "RealName" this is the displayName and format it correctly update_display_list "change" "Retrieve Display Name" "wait" fullRealName=$(dscl . -read /Users/${oldUser} RealName) readonly origRealName=$(echo ${fullRealName} | cut -d' ' -f2-) logMe "Full RealName is: "$origRealName update_display_list "change" "Retrieve Display Name" "success" # Step #1 # Updates "RealName" (human readable) to new username update_display_list "change" "Update Display Name" "wait" sudo dscl . -change "/Users/${oldUser}" RealName "${origRealName}" "${newUser}" logMe "Migrate Realname from $oldUser to $newUser" # # Validity Check #1 # Verify that the folder name could be changed # if [[ $? -ne 0 ]]; then show_err_msg "Update Display Name" "Could not rename the user's RealName in dscl. - err=$?" "fail" $STOP_ICON logMe "Could not rename the user's RealName in dscl. - err=$?" logMe "Reverting RealName changes" sudo dscl . -change "/Users/${oldUser}" RealName "${origRealName}" "${origRealName}" cleanup_and_exit "Fail" else logMe "Migration of Realname succesful" update_display_list "change" "Update Display Name" "success" fi # Step #2 # Captures current NFS home directory update_display_list "change" "Update NFS Home Directory" "wait" readonly origHomeDir=$(dscl . -read "/Users/${oldUser}" NFSHomeDirectory | awk '{print $2}' -) logMe "Old User $oldUser Home Directory is: $origHomeDir" # # Validity Check #2 # Verify that the original users folder was found # if [[ -z "${origHomeDir}" ]]; then show_err_msg "Update NFS Home Directory" "Cannot obtain the original home directory name, is the oldUserName correct?" "fail" $STOP_ICON logMe "Cannot obtain the original home directory name, is the oldUserName correct?" logMe "Reverting RealName changes" sudo dscl . -change "/Users/${oldUser}" RealName "${newUser}" "${origRealName}" cleanup_and_exit "Fail" fi # Step #3 # Updates NFS home directory sudo dscl . -change "/Users/${oldUser}" NFSHomeDirectory "${origHomeDir}" "/Users/${newUser}" logMe "Migration of User $oldUser NFS Home Directory is: $origHomeDir" # # Validity Check #3 # Verify that the Home Folder could be changed # if [[ $? -ne 0 ]]; then show_err_msg "Update NFS Home Directory" "Could not rename the user's home directory pointer, aborting further changes! - err="$? "fail" $STOP_ICON logMe "Could not rename the user's home directory pointer, aborting further changes! - err=$?" logMe "Reverting Home Directory changes" sudo dscl . -change "/Users/${oldUser}" NFSHomeDirectory "/Users/${newUser}" "${origHomeDir}" logMe "Reverting RealName changes" sudo dscl . -change "/Users/${oldUser}" RealName "${newUser}" "${origRealName}" cleanup_and_exit "Fail" else logMe "Migration of NFS Home Directory succesful" update_display_list "change" "Update NFS Home Directory" "success" fi # Step #4 # Move data to new home folder update_display_list "change" "Move Files to New User" "wait" mv "${origHomeDir}" "/Users/${newUser}" logMe "Move data from $origHomeDir to: /Users/${newUser}" # # Validity Check #4 # Verify that the data could be moved # if [[ $? -ne 0 ]]; then show_err_msg "Move Files to New User" "Could not rename the user's home directory in /Users" "fail" $STOP_ICON logMe "Could not rename the user's home directory in /Users" logMe "Reverting Home Directory changes" mv "/Users/${newUser}" "${origHomeDir}" sudo dscl . -change "/Users/${oldUser}" NFSHomeDirectory "/Users/${newUser}" "${origHomeDir}" logMe "Reverting RealName changes" sudo dscl . -change "/Users/${oldUser}" RealName "${newUser}" "${origRealName}" cleanup_and_exit "Fail" else logMe "Migration of Data successful" update_display_list "change" "Move Files to New User" "success" fi # Step #5 # Actual username change update_display_list "change" "Verify Rename" "wait" sudo dscl . -change "/Users/${oldUser}" RecordName "${oldUser}" "${newUser}" # # Validity Check #4 # Verify that the rename was successful # if [[ $? -ne 0 ]]; then show_err_msg "Verify Rename" "Could not rename the user's RecordName in dscl - the user should still be able to login, but with user name ${oldUser}" "fail" $STOP_ICON logMe "Could not rename the user's RecordName in dscl - the user should still be able to login, but with user name ${oldUser}" logMe "Reverting username change" sudo dscl . -change "/Users/${oldUser}" RecordName "${newUser}" "${oldUser}" logMe "Reverting Home Directory changes" mv "/Users/${newUser}" "${origHomeDir}" sudo dscl . -change "/Users/${oldUser}" NFSHomeDirectory "/Users/${newUser}" "${origHomeDir}" logMe "Reverting RealName changes" sudo dscl . -change "/Users/${oldUser}" RealName "${newUser}" "${origRealName}" cleanup_and_exit "Fail" else # Everything was successful to this point, so update the display and restart the system update_display_list "change" "Verify Rename" "success" update_display_list "icon" ${SUCCESS_ICON} update_display_list "infotext" "Successfuly migrated old user: $oldUser to new user: $newUser" logMe "Verification of Data successfull. System restart in order" update_display_list "buttonchange" "Restart" wait cleanup_and_exit "Restart" fi } ############################## # # Main Script Start # ############################## declare newUser declare oldUser declare fullRealName declare origRealName declare origHomeDir autoload 'is-at-least' check_swift_dialog_install check_support_files test_root_user create_welcome_dialog create_workflow_dialog update_display_list "create" verify_account_info perform_migration # Technically, the script should never get this far if everything went OK cleanup_and_exit "Good"

 

and a sample screenshot of what the user will see when they run it:

 and some error checking:

 And what the user will see if everything goes OK:

Once the script has successfully run, the computer will restart and then the "new" (migrated) user can login. 

As always, test, test, test!  If anyone sees any issues with the main logic (the heavy lifting of this is in the '

perform_migration' function), please let me know..
 
Enjoy!

 

 

1 reply

Forum|alt.badge.img+12
  • Valued Contributor
  • 100 replies
  • February 11, 2025

I commend you on all the Swift ui you've been posting.  Nice work!


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings