Script runs fine locally but not through Jamf

el2493
Contributor III

I have a script that's supposed to run at the end of enrollment and it asks a tech whether they want to run an "Initial Setup" Policy, which if run would trigger a bunch of apps to install. It uses Pashua and the API so that if the user clicks a button that says "Info" it will give them a list of software that would be installed.

The script is:

#!/bin/sh -v  

# Where's the jamf binary stored? This is for SIP compatibility.
jamf_binary=`/usr/bin/which jamf`

 if [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ ! -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/sbin/jamf"
 elif [[ "$jamf_binary" == "" ]] && [[ ! -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/local/bin/jamf"
 elif [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
    jamf_binary="/usr/local/bin/jamf"
 fi

## Establish API Credentials
# https://github.com/jamfit/Encrypted-Script-Parameters

function DecryptString() {
    # Usage: ~$ DecryptString "Encrypted String" "Salt" "Passphrase"
    echo "${1}" | /usr/bin/openssl enc -aes256 -d -a -A -S "${2}" -k "${3}"
}

apiUserEncrypt=$4
apiPassEncrypt=$5

apiUsername=$(DecryptString $apiUserEncrypt '20d17db979f2b85b' '5106e04c01e15cb3654e93da')
apiPassword=$(DecryptString $apiPassEncrypt '50ed367f279afee5' '8bd4ab4a94dc898d7d12ac3d')

## Use API to get computer Department from UUID
# Get computer's UUID
compUUID=$(system_profiler SPHardwareDataType | awk '/UUID/ { print $3; }')
#echo "compUUID is "$compUUID
compRaw=$(curl https://jssurl/JSSResource/computers/udid/${compUUID} --user "$apiUsername:$apiPassword")
#echo $compRaw
compDept=$(echo $compRaw | xpath '//location/department' 2>&1 | awk -F'<department>|</department>' '{print $2}')
compDept="${compDept:2}"
echo "compDept is "$compDept

##Define Initial Setup Trigger and ID of the Initial Setup script based on Department
if [[ "$compDept" == "Department1"* ]]; then
    initialSetup="Department1Initial"
    msgPolicy="Department1"
    scriptID=[ID of the Policy that's triggered by Department1Initial trigger, I could probably get this from the API but it's easier for me just to define it]
[A FEW ELIFS FOR OTHER DEPARTMENTS]
fi
echo "initialSetup is "$initialSetup

if [[ -z "$scriptID" ]]; then
    echo "no initial setup policy defined for department"
    exit 0
fi

##Pashua (for prompts)

pashuaApp="/Applications/Utilities/Pashua.app/Contents/MacOS/Pashua"
configDest="/tmp/conf.pash"

if [[ -e "$pashuaApp" ]]; then
    echo "pashua installed at expected location"
else
        echo "pashua not installed at expected location"
        exit 1
fi

## Use API to show the contents of the script with the ID defined above by $scriptID
scriptRaw=$(curl https://jssurl/JSSResource/scripts/id/${scriptID} --user "$apiUsername:$apiPassword")
#echo "scriptRaw results "$scriptRaw
scriptContents=$(echo $scriptRaw | xpath '//script_contents' 2>&1 | awk -F'<script_contents>|</script_contents>' '{print $2}')

#Delete everything before "for each"
scriptPolicies=$(echo "$scriptContents" | sed 's/^.*for each//')
#echo "scriptPolicies results "$scriptPolicies

appList=""
while IFS='#' read -ra DELIMITED; do
    for i in "${DELIMITED[@]}"; do
        if [[ "$i" != " " ]]; then
            i="${i:-2}"
            policyShort=$(echo "$i" | sed 's/ $jamf.*//')
            #policyShort="$policyShort[return]"
            #echo "$policyShort"
            appList+=("$policyShort")
        fi
    done
done <<< "$scriptPolicies"

##THE ABOVE CREATES AN ARRAY THAT HAS THE NAMES OF APPLICATIONS THAT ARE GOING TO BE INSTALLED

function joinBy () {
local d=$1; shift; echo "$1"; shift; printf "%s" "${@/#/$d}";
}

appString=$(joinBy '[return]' "${appList[@]}")
appString="${appString:9}"
#echo $appString
##THE ABOVE CREATES A STRING WITH THE NAMES OF APPS SEPARATED BY "[RETURN]", WHICH IS HOW PASHUA NEEDS IT FORMATTED TO DISPLAY IN A TEXTBOX

function enrollmentCompleteWindow () {
# Create a temp conf file to prompt user
rm $configDest
cat <<EOT >> $configDest
*.title = Enrollment Complete
txt.type = text
txt.default = Enrollment is complete. Detected Department is ${compDept}.[return][return]Would you like to run the ${msgPolicy} Initial Setup policy to install Applications? 
txt.width = 310
txt.height = 310
cb.type = cancelbutton
cb.label = No
b.type = button
b.label = Info
db.type = defaultbutton
db.label = Yes
EOT

# Run Pashua and store the variable
enrollButton=$($pashuaApp $configDest)
}

function appListWindow () {
# Create a temp conf file to list apps
rm $configDest
cat <<EOT >> $configDest
*.title = Policy List
tb.type = textbox
tb.label = The following apps/policies will be installed:
tb.default = $appString
tb.width = 310
tb.height = 310
tb.disabled = 1
cb.type = cancelbutton
cb.label = Cancel
db.type = defaultbutton
db.label = Install
EOT

# Run Pashua and store the variable
appListButton=$($pashuaApp $configDest)
}

enrollmentCompleteWindow
if [[ "$enrollButton" == *"cb=1"* ]]; then
    echo "user clicked no"
    exit 0
elif [[ "$enrollButton" == *"db=1"* ]]; then
    echo "user clicked yes"
    #$jamf_binary policy -trigger $initialSetup
elif [[ "$enrollButton" == "b=1"* ]]; then
    echo "user clicked info"
    appListWindow
    if [[ "$appListButton" == *"cb=1"* ]]; then
        echo "user clicked cancel"
        exit 0
    elif [[ "$appListButton" == *"db=1"* ]]; then
        echo "user clicked install"
        #$jamf_binary policy -trigger $initialSetup COMMENTED OUT WHILE TESTING
    fi
fi

Whenever I run the script locally (apart from Jamf) it takes less than a second to do everything and give me the popup window every single time. I tried to run it from Jamf through Self Service a few times and found that it seemed to freeze. It would show as Installing but would get about half done (based on the progress circle) and not do anything further, forcing me to cancel it (I've waited over 5 minutes and it just stays there). Oddly, sometimes about a minute after cancelling it I would get the popup window, which I thought was weird.

I tried setting it to Recurring Checkin, did a sudo jamf policy and the window immediately popped up. Just to be safe I cleared the logs (since it was set to run once per computer) and waited for the computer to checkin normally, and when it did that it showed in jamf.log in Console that it started running the policy, but then 25 minutes later it still hadn't gotten farther than that and I didn't have any way to cancel it (and it was holding up other Policies that needed to run at Recurring Checkin).

Does anyone have any idea what could be wrong with the script that makes it not work well in Jamf? I know it's a pretty complicated script (particularly the $scriptPolicies, $appList, and appStrings sections, which effectively just format a list of apps that will be installed in a way that can be used by Pashua) and I'm sure it could be done better, so if anyone has some general recommendations about how to troubleshoot something like this so I at least can get a log of where it's hanging, that would also be helpful. If I end up cancelling it through Self Service I don't get any logs, which makes it tricky to troubleshoot.

2 REPLIES 2

mm2270
Legendary Contributor III

Hi @el2493. I have some ideas on what might be happening. This bears mentioning again, even though I know this has been discussed a few times on other threads.

When a jamf policy gets called by the natural check-in process, the policy, and any items in it, like scripts, are being run by a LaunchDaemon. Since LaunchDaemons run as root, the entire script is being run in a root context. This often causes issues on modern macOS where the OS doesn't like processes to run as anything but the current user, presumably for security reasons. So because your Pashua dialog has interaction elements in it, asking for user input, the OS is likely blocking it from appearing on screen. It could also be that Pashua itself doesn't like being called from the root account to display the dialog. I'm not certain which it is as I don't have a lot of experience using Pashua.

You might ask how this is possible since you ran sudo jamf policy and it showed the dialog. But the difference here is that even though you invoked sudo, YOU are still running the policy and subsequently the script, in your user space, not as root. sudo does not mean root, it means elevate your privs temporarily to run the command, but it's still you running the process, not a LaunchDaemon calling it as root.
Does that make sense? I know it sounds strange, but I've seen this many times over the past few years, so I'm pretty certain at this point about those differences in how the policy is called.

So, how to fix this? Hard to know for sure, but likely you will need to call the Pashua application and it's config file as the current user within your script. I'm not that familiar with how Pashua works, so I don't know if I can specifically give you an answer on this, but I'll try.

One method I use religiously to run things as the current user is launchctl asuser This might also work for you. The syntax looks something like this:

LOGGED_IN_USER=$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "
");'
LOGGED_IN_UID=$(id -u "$LOGGED_IN_USER")

/bin/launchctl asuser "${LOGGED_IN_UID}" sudo -iu "${LOGGED_IN_USER}" <command goes here>

So in your case, it might mean changing these lines

enrollButton=$($pashuaApp $configDest)

to

enrollButton=$(/bin/launchctl asuser "${LOGGED_IN_UID}" sudo -iu "${LOGGED_IN_USER}" "$pashuaApp" $configDest)

You would need to do the same for the appListButton variable in your script, and possibly some other places.
Of course, you need to include the lines that get the logged in user and UID for it to work. I would give that a try to see if it addresses the issue.

BTW, nice work on the script. It does look pretty advanced from what I can see. It looks like a good way to do a full "setup" of an enrolled device - something I'm looking to tackle myself in the coming months as I now move away from traditional imaging.

el2493
Contributor III

@mm2270 , thanks so much for that very in-depth and comprehensive answer! What you're saying makes a lot of sense and I should have thought of that since I was having a very similar issue when trying to use an osascript display dialog command a few months ago (I had to specify to run as logged in user). This is my first experiment with Pashua, so I hadn't connected the dots yet. It does work pretty well for these purposes.

fd457f8ab78f42f391e8548454bbedb3
a22f87330b0e43c082c352a6e4258df6