Assigning Building, Department, Room (EA) using recon command with AppleScript

joethedsa
Contributor II

I'm looking for a way to use oascript to set the values of buildings, departments and extension attributes in the form of drop downs so the "jamf recon -building, -Department, etc" commands can be executed with what was selected from the dropdown.

My goal is to have a technician who doesn't have access to the Jamf admin console, the ability to set these quickly and easily. It's possible that I have to use the API but I'm not sure.

1 ACCEPTED SOLUTION

mm2270
Legendary Contributor II

Yes, using the API is actually a better way to do it, since things like Buildings and Departments cannot be successfully added to a computer record using a recon unless the corresponding building or department exists, exact spelling included. So pulling them using the API avoids typing errors and also accounts for any changes and additions that can happen from the time the script is created.

Here's an example I pulled from one of my scripts to get the values of Buildings. You can adapt this for Departments. Just change the JSSResource URL and some of the wording and variables as appropriate and it should work. Note that the username and password for the API are passed to the script as script parameters $4 & $5 respectively.

#!/bin/bash

APIUSER="$4"
APIPASS="$5"

function getAPIValues ()
{

## Pull values from the enrolled to Jamf Pro server using the API

## Jamf Pro URL this Mac is enrolled to
JP_URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed 's//$//')

## Get list of Buildings assigned in Jamf Pro
BUILDINGS=$(/usr/bin/curl -H "Accept: application/xml" -sku "${APIUSER}:${APIPASS}" "${JP_URL}/JSSResource/buildings" -X GET 2>/dev/null | xmllint --format - | awk -F'>|<' '/<name>/{print $3}')


BUILDING_SELECTION=$(/usr/bin/osascript << EOF
tell application "System Events"
set Builds to do shell script "echo "$BUILDINGS""
set BuildsList to paragraphs of Builds
set BUILDING_NAME to choose from list BuildsList with prompt "Choose a building for this computer." with title "Building Assignment"
end tell
EOF)

if [ "$BUILDING_SELECTION" == "false" ]; then
    getAPIValues
else
    echo "Building selected was: $BUILDING_SELECTION"
fi

}

## Call the function
getAPIValues

View solution in original post

22 REPLIES 22

cdenesha
Valued Contributor II

@joethedsa You'll need to use the API to make a drop-down list of what is actually in the DB. Your script will need to run curl commands with a JSS user with the correct READ permissions.. you can either hard code the JSS username and password of a service account or prompt for the tech's credentials.

The display dialog command will want a comma delimited string for the values, and the curl command to collect the values will deliver XML, so you need conversion code.

I'll sanitize my script and post it tomorrow.

techjason
New Contributor III

@joethedsa Here is a stripped version of what we use to assign the department. During setup, the techs run a policy that runs a few policies that run the scripts in self-service. For the same reason, you want to do it.

You can list more departments if you need to, I think we have about 20 in ours. They have to match the listing you have in your jamf instance. I hope this helps.

#!/bin/sh

#Sets Department Field in the JSS

dept=$(osascript <<EOF 
set depts to {"Department 1", "Department 2", "Department 3", "Department 4", "Department 5"}

set deptname to choose from list depts with prompt "Please select the department this computer will be assigned to." with title "Department Assignment" 

display dialog deptname
EOF)

jamf recon -skipApps -skipFonts -skipPlugins -department $dept

joethedsa
Contributor II

@techjason , It looks like if your method is used, it will require a manual typing of all of the departments in the script (we have 20+). Is that correct? If so, did you have a quick way of doing it?

joethedsa
Contributor II

@cdenesha , Thank you. I look forward to seeing your script. As mentioned in my other comment, using the API may be more accurate and better going forward.

mm2270
Legendary Contributor II

Yes, using the API is actually a better way to do it, since things like Buildings and Departments cannot be successfully added to a computer record using a recon unless the corresponding building or department exists, exact spelling included. So pulling them using the API avoids typing errors and also accounts for any changes and additions that can happen from the time the script is created.

Here's an example I pulled from one of my scripts to get the values of Buildings. You can adapt this for Departments. Just change the JSSResource URL and some of the wording and variables as appropriate and it should work. Note that the username and password for the API are passed to the script as script parameters $4 & $5 respectively.

#!/bin/bash

APIUSER="$4"
APIPASS="$5"

function getAPIValues ()
{

## Pull values from the enrolled to Jamf Pro server using the API

## Jamf Pro URL this Mac is enrolled to
JP_URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed 's//$//')

## Get list of Buildings assigned in Jamf Pro
BUILDINGS=$(/usr/bin/curl -H "Accept: application/xml" -sku "${APIUSER}:${APIPASS}" "${JP_URL}/JSSResource/buildings" -X GET 2>/dev/null | xmllint --format - | awk -F'>|<' '/<name>/{print $3}')


BUILDING_SELECTION=$(/usr/bin/osascript << EOF
tell application "System Events"
set Builds to do shell script "echo "$BUILDINGS""
set BuildsList to paragraphs of Builds
set BUILDING_NAME to choose from list BuildsList with prompt "Choose a building for this computer." with title "Building Assignment"
end tell
EOF)

if [ "$BUILDING_SELECTION" == "false" ]; then
    getAPIValues
else
    echo "Building selected was: $BUILDING_SELECTION"
fi

}

## Call the function
getAPIValues

View solution in original post

bvondeylen
Contributor II

Or use FileMaker Pro. GET all your assets from JAMF into a FileMaker Pro database using JSON, then PUT data from FileMaker Pro back to JAMF using XML (until the universal API is ready).
This will give your tech access to change only what you allow, yet see everything. You can script to update one field, or multiple fields, and since it is FileMaker, you can use Dropdowns, Pop-ups, Radio buttons, Checkboxes, etc

cdenesha
Valued Contributor II

@joethedsa Interestingly enough, my script is very similar to the one from @mm2270! I really like his method of getting the JSS URL from the system (although AppleScript cannot have the backspace character so I had to do a string replacement) and his xmllint command was more efficient than my xpath one so I switched to it.

I'm not sure where to get the list of possible Rooms from however, do you have this stored in an Extension Attribute in the JSS?

I'm not sure how you are going to scope your policy since you don't want your techs to have access to Jamf Pro which means they won't have JSS usernames. I was envisioning this would be scoped to a user and then your tech would sign into Self Service with their username.

For completeness and to show you can do it in straight AppleScript instead of bash 🙂 (along with a little error checking):

#!/usr/bin/osascript

on run argv -- passes in list of parameters

    set jssUser to item 4 of argv -- pass in from Self Service Policy
    set jssPass to item 5 of argv -- pass in from Self Service Policy
    set apiUser to jssUser & ":" & jssPass
    set tempURL to do shell script "/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url"
    set jssURL to characters 1 through ((length of tempURL) - 1) of tempURL as string

    --simple credentials check before moving on, be sure your user has privileges to Categories
    set curlCommandCreds to "curl -s -u " & apiUser & " " & jssURL & "/JSSResource/categories"
    set Authcheck to do shell script curlCommandCreds
    try
        if "Unauthorized" is in Authcheck then
            display dialog "Error in Policy - Username or password incorrect" buttons {"Ok"}
            return
        end if
    end try

    display dialog "Getting list of Buildings..." buttons {"Ok"} default button 1 giving up after 1
    set getBuildings to do shell script "curl -H "Accept: application/xml" -s -u " & apiUser & " " & jssURL & "/JSSResource/buildings 2>/dev/null | xmllint --format - | awk -F'>|<' '/<name>/{print $3}'"
    set sBuildings to paragraphs of getBuildings
    set myBuilding to (choose from list sBuildings with title "Possible Buildings" with prompt "Which Building?") as string
    if myBuilding is "false" then -- user pressed Cancel
        display dialog "Cancelling..." buttons {"Ok"} default button 1 giving up after 1
        return
    end if

    display dialog myBuilding

end run

Let us know if this doesn't get you quite far enough.

joethedsa
Contributor II

@cdenesha , I believe "Room" is not an EA. It's part of the "User and Location" fields. @mm2270 , your script works well to retrieve those buildings! Is there a command that can be inserted to list them alphabetically? It looks like it is listing the buildings in the order it was created in JSS (the last building being the latest entry).

Once the appropriate building is selected, how is the building actually assigned to the device? Does the selection have to be fed in as a variable to actually set it?

cdenesha
Valued Contributor II

@joethedsa I think you are right in where they are stored. You can't get a list of possible entries since they aren't configured in Settings. So you may need to prompt for it.

mm2270
Legendary Contributor II

@joethedsa Sure, just add the following the very end of the BUILDINGS line where it uses curl to get the list of buildings

| sort -g

Make sure to put it inside the closing paren mark. That will sort the list of buildings in alphabetical order when it displays them.

As for how to apply that building, I would just do a recon using the variable of the selected building (and any other location items you choose to grab) at the end with the proper flags. Example:

/usr/local/bin/jamf recon -building "$BUILDING_SELECTION" -department "$DEPT_SELECTION" etc. etc.

joethedsa
Contributor II

@mm2270, that did the trick. You know how to leverage API's very well! @cdenesha, I will have to find a solution to get more granular with the room numbers. I appreciate everyone's input.

joethedsa
Contributor II

@mm2270, it appears oascript doesn't work or maybe the syntax has to change with Catalina? Pre-Catalina this script works well. By chance, have you tried this with Catalina? Not sure if it has something to do with zsh. The oascript binary appears to be in the same file location so I don't think thats where the issue lies. For what it's worth, when I execute the script, I get the "Jamf wants access to control System Events...". I click OK to allow it so I don't think that is holding it back.

blackholemac
Valued Contributor III

I have a bash script that uses enough AppleScript to display questions to a “tech” to choose from a list and then forwards the tech’s choices on to Jamf. Much like someone else who posted though it may take me an hour or two to sanitize that for you since I’ve got some things to do this morning

joethedsa
Contributor II

@blackholemac, Is the information you implement, extension attributes or the pre-built information available through the web GUI? It would be extremely helpful to have a script like that since there are other bits of information that I would like techs to enter without giving them access to the web interface. Updating building data is a high priority since I'll be scoping policies based on that criteria.

blackholemac
Valued Contributor III

My sanitized script:

#!/bin/bash

################################ READ THROUGH THIS SCRIPT IN ITS ENTIRETY AND DEFINITELY CUSTOMIZE TO YOUR ENVIRONMENT! DO NOT RUN THIS AS IS!!##########################################

touch /var/log/provisioning.log

# ENTER RELEVANT SERVER INFO HERE
apiUser=""
apiPass=""
jssUrl="https://jss.yourcompany.org:8443"
serial=`system_profiler SPHardwareDataType | awk '/Serial/ {print $4}'`

echo $jssUrl >> /var/log/provisioning.log
echo $serial >> /var/log/provisioning.log
echo $osrevision >> /var/log/provisioning.log


# HAVE THE TECH CHOOSE A DEPARTMENT FROM A LIST
departmentName=$(osascript -e 'with timeout of 86400 seconds
    tell application "SystemUIServer"
        set myDepartmentName to choose from list {“Department 1”, "Department 2”, "Department 3”, "Department 4”, "Department 5”, "Department 6”, "Department 7”, "Department 8”} with title "Jamf Pro Department" with prompt "Please choose which department that this Mac will report for the inventory record in Jamf Pro."
    end tell
end timeout')
echo $departmentName

# HAVE THE TECH INPUT AN ASSET TAG
assetTag=$(osascript -e 'with timeout of 86400 seconds
    tell application "SystemUIServer"
        set myAssetTag to text returned of (display dialog "Please insert the asset tag here.” default answer "" with title “Asset Tag Entry” buttons {"Submit"} default button 1 with icon caution)
    end tell
end timeout')
echo $assetTag >> /var/log/provisioning.log

# HAVE THE TECH CHOOSE A BUILDING FROM A LIST
buildingName=$(osascript -e 'with timeout of 86400 seconds
    tell application "SystemUIServer"
        set myBuildingName to (choose from list {“Building 1”, "Building 2”, "Building 3”, "Building 4”, "Building 5”, "Building 6”, "Building 7”, "Building 8”, "Building 9”, "Building 10”, "Building 11”, "Building 12”, "Building 13”, "Building 14”, "Building 15”, "Building 16”, "Building 17”, "Building 18”, "Building 19”} with title "Building Choice" with prompt "Please choose which building this machine will be assigned to. This step is necessary for inventory reporting." default items "Building 1")
    end tell
end timeout')
echo $buildingName >> /var/log/provisioning.log

# HAVE THE TECH INPUT A ROOM NUMBER
roomNumber=$(osascript -e 'with timeout of 86400 seconds
    tell application "SystemUIServer"
        set myRoomNumber to text returned of (display dialog "Please set the room number that this Mac will report for the inventory record in Jamf Pro."     default answer "" with title "Room Number" buttons {"Submit"} default button 1 with icon caution)
    end tell
end timeout')
echo $roomNumber >> /var/log/provisioning

# HAVE THE TECH INPUT A POSITION
position=$(osascript -e 'with timeout of 86400 seconds
    tell application "SystemUIServer"
        set the_button to button returned of (display dialog "Will this lab computer be a student, staff or building administrator machine?" buttons {"Student", "Staff", "Administrator"} default button 1)
    end tell
end timeout')
echo $position >> /var/log/provisioning.log

# IN MY CASE MY SCRIPT DOES DEFINE ALL THESE PARAMETERS. I ALSO CHOSE TO USE CURL INSTEAD OF A RECON COMMAND AS CURL IS CURRENTLY NATIVE TO THE MAC
curl -X PUT -H "Accept: application/xml" -H "Content-type: application/xml" -k -u "${apiUser}:${apiPass}" -d "<computer><location><building>$buildingName</building><position>$position</position><department>$departmentName</department><room>$roomNumber</room></location></computer>" "${jssUrl}/JSSResource/computers/serialnumber/${serial}"

blackholemac
Valued Contributor III

In honesty the sanitized script is very generic. I mainly provide it as an example of one way you could get information to define each of those variables. In my orgs case there are only three values that a human must answer to to populate each of these variables. My script demonstrates hand populating each of them instead. I also will need to modify the curl line to the sanitized version. Our asset tag is actually part of the computer name here so I don’t just raw upload the asset tag value to my JSS.

MrRoboto
Contributor II

My current script is used to define the computer name and department. I am not currently using the API for this so the list of departments is manually entered in the script, and I am using Jamf Recon to upload the data to JSS.

Other benefits of using the API aside... Will using the API to PUT the data into JSS be more reliable than doing a Jamf Recon in the script? It's rare but we do get the odd computer than completes the script the department doesn't change in JSS. Things are working because it writes the selected name and department to a local log file, and the recon runs, just the department stays empty.

blackholemac
Valued Contributor III

I have found it a mixed bag...my real script does a whole bunch of other stuff that's relevant for this environment and the sanitized one was more of a proof of concept.

I actually used the recon line instead of a put in early builds of it, but because my computer name is actually formulated from some of the values the user puts in and then has to occasionally be massaged and AD bound as part of the script, (and because I wanted to teach myself basic API), I decided to go that way...you get to control exactly what gets tweaked as an API call...a Recon command basically may collect stuff differently. Both work. The main difference is that curl is part of the OS...recon only gets laid down on proper enrollment with Jamf.

joethedsa
Contributor II

@mm2270, just wanted to check back in with you. Have you used your script in Catalina? It just sits there as if it doesn't know what to do with it. See my earlier comment on 1/21/20 for details.

mm2270
Legendary Contributor II

@joethedsa Thanks for the note! I somehow missed your previous comments about problems with Catalina. I can see in looking at what I posted that I'm not following my own guidelines and using a launchctl asuser type command to bring up that AppleScript dialog, which is likely why it's just sitting there doing nothing. Give me a few and I'll see if I can make an adjustment to get it to work under 10.15.

mm2270
Legendary Contributor II

Ok, modified script below. I ran some quick tests on a 10.15 machine and I believe this will work now. One thing of course is that since this is using scripting and calling AppleScript and so on, you want to make sure you have the proper PPPC Profiles installed on your machines to allow control over AppleEvents. Otherwise your users or techs may get a pop up asking to allow it before the script will run.

Also, my script doesn't do the recon piece at the end as discussed upthread, which you will need to add to have the selected Building added to the device record details. Or use another API command to add it.

#!/bin/bash

APIUSER="$4"
APIPASS="$5"

LOGGED_IN_USER=$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $  3 }')
LOGGED_IN_UID=$(/usr/bin/id -u "$LOGGED_IN_USER")

function getAPIValues ()
{

## Pull values from the enrolled to Jamf Pro server using the API

## Jamf Pro URL this Mac is enrolled to
JP_URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed 's//$//')

## Get list of Buildings assigned in Jamf Pro
BUILDINGS=$(/usr/bin/curl -H "Accept: application/xml" -sku "${APIUSER}:${APIPASS}" "${JP_URL}/JSSResource/buildings" -X GET 2>/dev/null | xmllint --format - | awk -F'>|<' '/<name>/{print $3}' | sort -g)

BUILDING_SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOF
tell application "System Events"
activate
set Builds to do shell script "echo "$BUILDINGS""
set BuildsList to paragraphs of Builds
set BUILDING_NAME to choose from list BuildsList with prompt "Choose a building for this computer." with title "Building Assignment"
end tell
EOF)

if [ "$BUILDING_SELECTION" == "false" ]; then
    getAPIValues
else
    echo "Building selected was: $BUILDING_SELECTION"
fi

}

## Call the function
getAPIValues

joethedsa
Contributor II

Thanks @mm2270! I actually had to adjust it a little because it still just sat there and did nothing. Thank you for providing the ground work to build upon! Here's my updates:

#!/bin/bash

APIUSER="$4"
APIPASS="$5"

LOGGED_IN_USER=$(/usr/sbin/scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $  3 }')
LOGGED_IN_UID=$(/usr/bin/id -u "$LOGGED_IN_USER")

function getAPIValues ()
{

## Pull values from the enrolled to Jamf Pro server using the API

## Jamf Pro URL this Mac is enrolled to
JP_URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed 's//$//')

## Get list of Buildings assigned in Jamf Pro
BUILDINGS=$(/usr/bin/curl -H "Accept: application/xml" -sku "${APIUSER}:${APIPASS}" "${JP_URL}/JSSResource/buildings" -X GET 2>/dev/null | xmllint --format - | awk -F'>|<' '/<name>/{print $3}' | sort -g)

BUILDING_SELECTION=$(/usr/bin/osascript << EOF
tell application "System Events"
activate
set Builds to do shell script "echo "$BUILDINGS""
set BuildsList to paragraphs of Builds
set BUILDING_NAME to choose from list BuildsList with prompt "Choose a building for this computer." with title "Building Assignment"
end tell
EOF)

if [ "$BUILDING_SELECTION" == "false" ]; then
    getAPIValues
else
    echo "Building selected was: $BUILDING_SELECTION"
fi

}

## Call the function
getAPIValues

/usr/local/bin/jamf recon -building "$BUILDING_SELECTION"

I'm also trying to get a osascript to display a similiar thing where it will prompt to enter a room number and then use that as a variable to set it a computer room location using the recon command. The script I'm working on isn't working as intended. Any thoughts?

#!/bin/sh
RoomNumber=`/usr/bin/osascript <<EOT
tell application "System Events"
    activate
    set RoomNumber to text returned of (display dialog "Enter the room number" default answer "")
end tell
EOT`
jamf recon -room $RoomNumber