The Parallels Management Agent shell script—InstallAgentUnattend.sh—is used to automate the Parallels Mac Management Agent installation.
This works great for on site computers, or with machines connected to VPN.
What needs more research, is line 247 in the script: pma_agent_registrator (this file is responsible for the registration portion of the setup process). I would like to figure out how to pass the web enrollment URL (example: https://DMZ.DOMAIN.COM/ParallelsMacManagement.Enrollment) to automatically enroll off site, non-VPN machines.
But…for the connected machines, the script downloads the DMG, mounts the DMG, installs the agent, enrolls the Mac, and dismounts the DMG. Honestly, it’s all pretty cool. If you decide to embed the credentials, I do recommend creating a PKG.
When the script runs, you should see this at the end:
If you have any issues, such as KDC and KRBv5 errors, check to make sure ports 8760 and 8761 are open (check your AV and firewalls)
How to test port: terminal > telnet 11.11.11.11 8760 (where the IP is your internal proxy server)
http://kb.parallels.com/en/124318 http://kb.parallels.com/en/122879 http://kb.parallels.com/en/124306
Script
#!/usr/bin/env bash ################################################################ # # Parallels Mac Management for SCCM # Mac Client Unattended Installation Script # Tested: 8/21/2019, High Sierra, Mojave # ################################################################ # Administrator Settings # PMA Agent installer image download URL PMA_AGENT_DMG_DOWNLOAD_URL=http://www.YOURWEBSITE/pma_agent.dmg # Dedicated PMA Agent registration user credentials # to authenticate with Active Directory export PMA_AGENT_REGISTRATION_USERNAME=YourUsername export PMA_AGENT_REGISTRATION_PASSWORD=YourPassword export PMA_AGENT_REGISTRATION_DOMAIN=YourDomain.com ################################################################ PMA_AGENT_DMG_LOCAL_FILENAME=/tmp/pma_agent.$RANDOM.dmg clear && printf '\e[3J' VER_PRODUCTNAME_STR="Parallels Mac Management for Microsoft SCCM" MAGT_INSTALL_DIR="/Library/Parallels" MAGT_PLIST_ID="com.parallels.pma.agent" MAGT_LAUNCHDAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launchdaemon.plist" MAGT_LAUNCHAGENT_PLISTFILE="/Library/LaunchAgents/com.parallels.pma.agent.launchagent.plist" MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launch.appindex.daemon.plist" MAGT_LAUNCH_CEP_DAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launchcep.plist" MAGT_UNATTENDED_INSTALLATION_FLAG_FILE="/tmp/pma_agent.installing.unattended" if [ $PARALLELS_INTERNAL ]; then ALLOW_UNTRUSTED_FLAG="-allowUntrusted" fi Cleanup() { hdiutil detach "/Volumes/$VER_PRODUCTNAME_STR" rm -f $PMA_AGENT_DMG_LOCAL_FILENAME rm -f $MAGT_UNATTENDED_INSTALLATION_FLAG_FILE } Log() { echo "$@" } LogError() { echo >&2 "Error: $@" } IsSystemAtLeast() { if [ -z "$SYSTEM_VERSION" ]; then local sysVerPlist="/System/Library/CoreServices/SystemVersion.plist" SYSTEM_VERSION=$(/usr/libexec/PlistBuddy -c "Print :ProductVersion" "$sysVerPlist") fi local minVer=$(printf "%04d%04d%04d%04d" $(echo "$1" | tr '.' '\n' | head -n 4)) local curVer=$(printf "%04d%04d%04d%04d" $(echo "$SYSTEM_VERSION" | tr '.' '\n' | head -n 4)) if [ $minVer -le $curVer ]; then echo YES return 0 else echo '' return 1 fi } GetFinderPid() { local userName="$1" local output local rv output=$(ps -xo user,pid,comm -u "$userName" | grep -E "^.*/Finder.app/.*/Finder(?:\s+.*)*$" | head -n 1) test "$output" || { echo "Cannot find Finder for ${userName}."; return 1; } output=$(echo "$output" | tr -s ' ' | cut -d ' ' -f 2) test "$output" || { echo "Cannot get Finder PID for ${userName}."; return 1; } echo "$output" return 0 } LaunchctlLoad() { local jobPlist="$1" local userName="$2" local bootstrapPid local jobLabel local output local userID jobLabel=$(/usr/libexec/PlistBuddy -c "Print :Label" "$jobPlist" 2>&1) (( $? )) && { LogError "Cannot read job label from $jobPlist"; LogError "$jobLabel"; return 1; } (( $EUID )) && { LogError "You must be root to perform this operation."; return 1; } if [ -z "$userName" ]; then Log "Loading $jobLabel for system..." else Log "Loading $jobLabel for user '$userName'..." fi test "$DRY_RUN" && return 0 if [ $(IsSystemAtLeast "10.11") ]; then if [ -z "$userName" ]; then output=$(launchctl bootstrap system "$jobPlist" 2>&1) else userID=$(id -u "$userName" 2>&1) (( $? )) && { LogError "$userID"; return 1; } output=$(launchctl bootstrap gui/$userID "$jobPlist" 2>&1) fi else if [ -z "$userName" ]; then output=$(launchctl load "$jobPlist" 2>&1) else bootstrapPid=$(GetFinderPid $userName) (( $? )) && { LogError "$bootstrapPid"; return 1; } output=$(launchctl bsexec "$bootstrapPid" sudo -u "$userName" launchctl load "$jobPlist" 2>&1) fi fi (( $? )) && { LogError "$output"; return 1; } Log "Job $jobLabel loaded successfully" return 0 } LaunchctlUnload() { local jobPlist="$1" local userName="$2" local bootstrapPid local jobLabel local output local userID jobLabel=$(/usr/libexec/PlistBuddy -c "Print :Label" "$jobPlist" 2>&1) (( $? )) && { LogError "Cannot read job label from $jobPlist"; LogError "$jobLabel"; return 1; } (( $EUID )) && { LogError "You must be root to perform this operation."; return 1; } if [ -z "$userName" ]; then Log "Unloading $jobLabel for system..." else Log "Unloading $jobLabel for user '$userName'..." fi test "$DRY_RUN" && return 0 if [ $(IsSystemAtLeast "10.11") ]; then if [ -z "$userName" ]; then output=$(launchctl bootout system "$jobPlist" 2>&1) else userID=$(id -u "$userName" 2>&1) (( $? )) && { LogError "$userID"; return 1; } output=$(launchctl bootout gui/$userID "$jobPlist" 2>&1) fi else if [ -z "$userName" ]; then output=$(launchctl unload "$jobPlist" 2>&1) else bootstrapPid=$(GetFinderPid $userName) (( $? )) && { LogError "$bootstrapPid"; return 1; } output=$(launchctl bsexec "$bootstrapPid" sudo -u "$userName" launchctl unload "$jobPlist" 2>&1) fi fi (( $? )) && { LogError "$output"; return 1; } Log "Job $jobLabel unloaded successfully" return 0 } StopAgents() { local users users=$(ps -xao user,comm | grep -E "^.*/pma_agent.app/.*/pma_agent_ui(?:\s+.*)*$" | tr -s ' ' | cut -d ' ' -f 1) for user in $users do LaunchctlUnload "$MAGT_LAUNCHAGENT_PLISTFILE" "$user" done } StartAgents() { local users users=$(ps -xao user,comm | grep -E "^.*/Finder.app/.*/Finder$" | grep -v grep | awk '{print $1}') for user in $users do LaunchctlLoad "$MAGT_LAUNCHAGENT_PLISTFILE" "$user" done } ############################################################################### # Main ############################################################################### trap Cleanup EXIT trap "exit 1" SIGHUP SIGINT SIGTERM SIGQUIT # Create flag file and fill with random content echo $PMA_AGENT_DMG_LOCAL_FILENAME > $MAGT_UNATTENDED_INSTALLATION_FLAG_FILE if [ $? -ne 0 ]; then LogError "Cannot create flag file '$MAGT_UNATTENDED_INSTALLATION_FLAG_FILE'" exit 1 fi Log "Downloading Mac Client installation image to $PMA_AGENT_DMG_LOCAL_FILENAME..." curl -# -o "$PMA_AGENT_DMG_LOCAL_FILENAME" "$PMA_AGENT_DMG_DOWNLOAD_URL" || exit 1 Log "Installing Mac Client..." hdiutil attach "$PMA_AGENT_DMG_LOCAL_FILENAME" || exit 1 installer -verbose -pkg "/Volumes/$VER_PRODUCTNAME_STR/$VER_PRODUCTNAME_STR.pkg" -target / ${ALLOW_UNTRUSTED_FLAG} || exit 1 Log "Waiting for postinstall script completion..." while [ -n "$(ps aux | grep "$VER_PRODUCTNAME_STR" | grep postinstall | grep -v grep)" ]; do sleep 0.1; done Log "Stop components..." test -f "$MAGT_LAUNCHAGENT_PLISTFILE" && StopAgents test -f "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" test -f "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" test -f "$MAGT_LAUNCHDAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCHDAEMON_PLISTFILE" Log "Register Mac Client..." "$MAGT_INSTALL_DIR/pma_agent.app/Contents/MacOS/pma_agent_registrator" || exit 1 Log "Start components..." LaunchctlLoad "$MAGT_LAUNCHDAEMON_PLISTFILE" || { Log "STOP"; exit 1; } LaunchctlLoad "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" LaunchctlLoad "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" StartAgents Log "Mac Client successfully installed"
Notes
Parallels Mac Management Technical Documentation
Some Install Paths:
Agent Cache:
Library > Caches > com.parallels.pma.agent
Keychain Access:
Library > Keychains > pmm-client.keychain
1 plist in Library > LaunchAgents
— com.parallels.pma.agent.launchagent.plist
3 plist in Library > LaunchDaemons
— com.parallels.pma.agent.appindex.daemon.plist
— com.parallels.pma.agent.launchcep.plist
— com.parallels.pma.agent.launchdaemon.plist
6 files in Library > Preferences
— com.parallels.pma.agent.cert.pem
— com.parallels.pma.agent.pkey.pem
— com.parallels.pma.agent.pki.cert.pem
— com.parallels.pma.agent.pki.pkey.pem
— com.parallels.pma.agent.plist {this data shows up in System Preferences > Parallels}
— com.parallels.pma.agent.sccmproxy.cert.pem
PMA Settings Location:
/Library/Application Support/Parallels/PMA_Agent
PMA APP Location:
/Library/Parallels/pma_agent.app
— Contents
—— MacOS
——— authhelper
——— pma_agent
——— pma_agent_registrator
——— pma_agent_ui
——— pma_agent_uninstaller
——— pma_crash_monitor
——— pma_fdehelper
——— pma_forwarding
——— pma_installer_helper
——— pma_report_tool
——— pmm_app_portal
——— pmm_appindex_agent
——— pmm_cep_service
——— pmmctl
Scripted unattend, by passing values into parameters:
sudo ./InstallAgentUnattended.sh http://dmz.domain.com/pma_agent.dmg UserName UserPassword UserDomain
Get Policies: /Library/Parallels/pma_agent.app/Contents/MacOS
pmmctl get-policies
Scan Updates: /Library/Parallels/pma_agent.app/Contents/MacOS
pmmctl scan-updates
Send Inventory: /Library/Parallels/pma_agent.app/Contents/MacOS
pmmctl report-hv-inventory
Uninstall PMA by APP: /Library/Parallels/pma_agent.app/Contents/MacOS/
pma_agent_uninstaller.app
or
Uninstall PMA by Shell Script:
sudo /bin/bash –с /Library/Parallels/pma_agent.app/Contents/MacOS/pma_agent_uninstaller.app/Contents/Resources/UninstallAgentScript.sh
or
#! /bin/bash VER_SHORTPRODUCTNAME_STR="Parallels Mac Management" VER_FULL_BUILD_NUMBER_STR="7.3.3.5" MAGT_LAUNCHAGENT_PLISTFILE="/Library/LaunchAgents/com.parallels.pma.agent.launchagent.plist" MAGT_LAUNCHDAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launchdaemon.plist" MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launch.appindex.daemon.plist" MAGT_LAUNCH_CEP_DAEMON_PLISTFILE="/Library/LaunchDaemons/com.parallels.pma.agent.launchcep.plist" MAGT_PLISTFILE="/Library/Preferences/com.parallels.pma.agent.plist" MAGT_SUPPORT_DIR="/Library/Application Support/Parallels/PMA_Agent" MAGT_KEYCHAIN_FILE="/Library/Keychains/pmm-client.keychain" MAGT_CACHE_DIR="/Library/Caches/com.parallels.pma.agent" MAGT_INSTALL_DIR="/Library/Parallels" MAGT_APP_PORTAL_INSTALL_DIR="/Applications" MAGT_APP_PORTAL_BUNDLE_NAME="Parallels Application Portal.app" MAC_AGENT_SUCATALOG_URL="SuCatalogUrl" PMM_CLIENT_BUNDLE_NAME="pma_agent.app" SOFTWARE_UPDATE_TOOL_PATH="/usr/sbin/softwareupdate" SOFTWARE_UPDATE_PREFERENCES_PLIST="/Library/Preferences/com.apple.SoftwareUpdate.plist" PMM_PREFERENCE_PANE_PATH="/Library/PreferencePanes/PRLPmmPreferencePane.prefPane" MAGT_PROBLEM_REPORTS_DIR="/Users/Shared/Parallels/Problem Reports" DSCL_USERS_CACHE_PATH="/var/db/dslocal/nodes/Default/users" SCRIPT_NAME=`basename "${0}"` SCRIPT_DIR=`dirname "${0}"` SCRIPT_TITLE="$VER_SHORTPRODUCTNAME_STR v$VER_FULL_BUILD_NUMBER_STR - Uninstall Mac Client" Log() { echo "${SCRIPT_NAME%.*}: $@" test -z "$ROOT_PATH" && logger -p install.info -t "$VER_SHORTPRODUCTNAME_STR" "$@" return 0 } LogError() { echo >&2 "${SCRIPT_NAME%.*}:Error: $@" test -z "$ROOT_PATH" && logger -p install.info -t "$VER_SHORTPRODUCTNAME_STR" "Error: $@" return 0 } RemoveFile() { local path="$(CleanPath "$ROOT_PATH/$1")" local fileName="$(basename "$path")" local dirPath="$(dirname "$path")" while IFS= read -r -d '' file; do Log "Removing file $file..." test "$DRY_RUN" && continue local output=$(rm -f "$file" 2>&1) if [ $? -ne 0 ]; then LogError "Cannot delete file: $file" LogError "$output" fi done < <(find "$dirPath" -maxdepth 1 -type f -name "$fileName" -print0 2>/dev/null) return 0 } RemoveDir() { local path="$(CleanPath "$ROOT_PATH/$1")" local fileName="$(basename "$path")" local dirPath="$(dirname "$path")" while IFS= read -r -d '' file; do Log "Removing directory $file..." test "$DRY_RUN" && continue local output=$(rm -rf "$file" 2>&1) if [ $? -ne 0 ]; then LogError "Cannot delete file: $file" LogError "$output" fi done < <(find "$dirPath" -maxdepth 1 -type d -name "$fileName" -print0 2>/dev/null) return 0 } # # Checks is specified directory empty. Ignores .DS_Store file. # IsDirEmpty() { local path="$(CleanPath "$ROOT_PATH/$1")" test ! -d "$path" && return 1 find "$path" -maxdepth 1 -mindepth 1 -not -name ".DS_Store" &>/dev/null if [ $? -eq 0 ]; then echo YES return 0 else echo '' return 1 fi } # # Removes all redundant '/' from path # CleanPath() { shopt -s extglob echo "${1//+(\/)//}" shopt -u extglob } # # Prints script usage information # PrintUsage() { read -d '' help <<- EOF Usage: $SCRIPT_NAME [--root <path>] [--dry-run] EOF LogError "$help" } IsSystemAtLeast() { if [ -z "$SYSTEM_VERSION" ]; then SYSTEM_VERSION=$(/usr/bin/sw_vers -productVersion) fi local minVer=$(printf "%04d%04d%04d%04d" $(echo "$1" | tr '.' '\n' | head -n 4)) local curVer=$(printf "%04d%04d%04d%04d" $(echo "$SYSTEM_VERSION" | tr '.' '\n' | head -n 4)) if [ $minVer -le $curVer ]; then echo YES return 0 else echo '' return 1 fi } # Source launchctl helpers . "$SCRIPT_DIR/launchctl_utils.sh" ############################################################################### # Main ############################################################################### Log "${SCRIPT_TITLE} ("`date`")" # # Parse command line arguments # while [[ $# > 0 ]] do key="$1" case $key in --root) ROOT_PATH="$2" shift ;; --dry-run) DRY_RUN="yes" ;; *) LogError "Unrecognized argument \"$key\"" PrintUsage exit 1 ;; esac shift done # # Check required arguments # if [ "${ROOT_PATH+yes}" ]; then if [ -z "$ROOT_PATH" ]; then LogError "Argument --root cannot be empty" exit 1 elif [ ! -d "$ROOT_PATH" ]; then LogError "Directory not found: $ROOT_PATH" exit 1 fi fi if [ "$EUID" -ne 0 ]; then LogError "Requires admin privileges, please re-run as root via sudo" exit 1 fi # # Stop services # if [ -z "$ROOT_PATH" ]; then if [ -f "$MAGT_LAUNCHAGENT_PLISTFILE" ]; then # Stop all instances of MacClient agent for userName in $(ps -xao user,comm | grep -E "^.*/pma_agent.app/.*/pma_agent_ui(?:\s+.*)*$" | tr -s ' ' | cut -d ' ' -f 1) do LaunchctlUnload "$MAGT_LAUNCHAGENT_PLISTFILE" "$userName" done fi test -f "$MAGT_LAUNCHDAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCHDAEMON_PLISTFILE" test -f "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" test -f "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" && LaunchctlUnload "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" fi # # Remove services configuration plists # RemoveFile "$MAGT_LAUNCHDAEMON_PLISTFILE" RemoveFile "$MAGT_LAUNCHAGENT_PLISTFILE" RemoveFile "$MAGT_LAUNCH_APPINDEX_DAEMON_PLISTFILE" RemoveFile "$MAGT_LAUNCH_CEP_DAEMON_PLISTFILE" # # Remove Application Support data # RemoveDir "$MAGT_SUPPORT_DIR" # # Remove per-user Application Support data # for userPlist in "$(CleanPath "$ROOT_PATH/$DSCL_USERS_CACHE_PATH")"/[!_]*.plist do test -e "$userPlist" || break userName=$(/usr/libexec/PlistBuddy -c "Print :name:0" "$userPlist" 2>&1) if [ $? -ne 0 ]; then LogError "$userName" continue fi userUid=$(/usr/libexec/PlistBuddy -c "Print :uid:0" "$userPlist" 2>&1) if [ $? -ne 0 ]; then continue; fi # Local user has UIDs in range (500;1000) or 0 (root) if [[ "$userUid" != 0 && ( "$userUid" -le 500 || "$userUid" -ge 1000 ) ]]; then continue; fi userHome=$(/usr/libexec/PlistBuddy -c "Print :home:0" "$userPlist" 2>&1) if [ $? -ne 0 ]; then continue; fi RemoveDir "$userHome/$MAGT_SUPPORT_DIR" done # # Revert Software Update catalog URL if need # if [ -z "$ROOT_PATH" ]; then expectedURL=$(/usr/libexec/PlistBuddy -c "Print :$MAC_AGENT_SUCATALOG_URL" "$MAGT_PLISTFILE" 2>/dev/null) actualURL=$(/usr/libexec/PlistBuddy -c "Print :CatalogURL" "$SOFTWARE_UPDATE_PREFERENCES_PLIST" 2>/dev/null) if [[ "$actualURL" && "$actualURL" == "$expectedURL" ]]; then if [ $(IsSystemAtLeast "10.9") ]; then $($SOFTWARE_UPDATE_TOOL_PATH --clear-catalog) else $(/usr/libexec/PlistBuddy -c "Delete :CatalogURL" "$SOFTWARE_UPDATE_PREFERENCES_PLIST") fi fi fi # Remove preferences RemoveFile "${MAGT_PLISTFILE/%plist/*}" # Remove keychain file RemoveFile "$MAGT_KEYCHAIN_FILE" # Remove cache directory RemoveDir "$MAGT_CACHE_DIR" # Remove problem reports RemoveFile "$MAGT_PROBLEM_REPORTS_DIR/PmaProblemReport*" # TODO: remove dirs up to "/Users/Shared/Parallels" if empty # Remove MacClient bundle RemoveDir "$MAGT_INSTALL_DIR/$PMM_CLIENT_BUNDLE_NAME" if [ $(IsDirEmpty "$MAGT_INSTALL_DIR") ]; then RemoveDir "$MAGT_INSTALL_DIR" fi # Remove AppPortal bundle RemoveDir "$MAGT_APP_PORTAL_INSTALL_DIR/$MAGT_APP_PORTAL_BUNDLE_NAME" # Remove Preferences Pane bundle RemoveDir "$PMM_PREFERENCE_PANE_PATH" # Remove install info RemoveFile "/var/db/receipts/com.parallels.pkg.pma.agent.*" Log "Completed"
echo $TMPDIR
The Parallels Mac Management log files are located in the following directories:
• Windows computer running Parallels Configuration Manager Proxy: %Windir%\Logs; %Windir%\Logs\pmm
• Windows computer running Parallels OS X Software Update Point: %Windir%\Logs\pmm
• Windows computer running Configuration Manager console: %Windir%\Logs
• OS X (Parallels Mac Client): /Library/Logs/
tags: MrNetTek