Extension Attribute - Give a device an EA in GUI dialogue box, dropdown, etc.

joethedsa
Contributor II

I'm wondering if there is a way to bring up a GUI (jamfhelper, cocoadialog, etc) to give a device an already created EA. As mentioned, the EA is created, I just need a way for a technician who is setting up the computer to assign it to the right EA so that it drops into scope for some policies to run based on the EA. The EA I created has the following characteristics: Data Type: String, Input Type: Pop-Up Menu . I then have a few items in the dropdown to choose. I'm hoping to be able to assign one of those dropdown items directly from the computer.

4 ACCEPTED SOLUTIONS

mm2270
Legendary Contributor III

@joethedsa Sure. Here is a script that should do what you're looking for. I already tested this myself so I know it works as long as it's set up correctly.

To use this, you need to use script parameters to pass the API account username, password and the Extension Attribute's id to it.
If you haven't used script parameters before, these are essentially placeholders in the script that, at script run time, the Jamf Pro server will pass down a value for them that you specify in the policy where you add the script. In general, when needing to use things like API account credentials, especially if that account has access to write back to your Jamf server, it's best to put them into parameters so it's more secure. They won't be hardcoded into the script.
The EA ID part isn't as necessary (it could be hardcoded), but I put it there to make it a little more flexible, i.e, if you have other EAs that use drop down values, the same script could be used to provide an interactive way to apply them to a machine.

When using the script in the policy, be sure to add in a value in Parameters 4, 5 and 6, for the API account name, password and the EA's ID (you can view the ID by viewing the Extension Attribute itself - the ID will be in the URL in your browser, like ....computerExtensionAttributes.html?id=8&o=r where 8 is the ID) Make sure the account you add has the ability to read and update computer records.

Here's the script:

#!/bin/bash

APIUSER="$4"
APIPASS="$5"
EA_ID="$6"

## Make sure we got values for the 3 parameters above
if [ -z "$APIUSER" ] || [ -z "$APIPASS" ] || [ -z "$EA_ID" ]; then
    echo "One of the required script parameters was not filled in. Please check the policy script parameters and try again."
    exit 1
fi

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

## This Mac's UUID/UDID string
UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## The logged in user and their UID
LOGGED_IN_USER=$(stat -f%Su /dev/console)
LOGGED_IN_UID=$(id -u "$LOGGED_IN_USER")

## API call to obtain the Extension Attribute and store in file
curl -H "Accept: text/xml" -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computerextensionattributes/id/${EA_ID}" | xmllint --format - > /tmp/${EA_ID}.xml

## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

if [ ! -z "$VALUES_ALL" ]; then
    ## Values were obtained for the EA. Prompt user to make a selection from a list
    SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOD
    tell application "System Events"
        activate
        set Values to do shell script "echo "$VALUES_ALL""
        set ValuesList to paragraphs of Values
        choose from list ValuesList with prompt "Select a value from the list:"
    end tell
EOD)
else
    echo "No values obtained in the API"
    exit 1
fi

function updateEAValue ()
{

## Create an xml file containing the EA settings
cat << EOF > /tmp/EA_${EA_ID}.xml
<computer>
    <extension_attributes>
        <extension_attribute>
            <id>${EA_ID}</id>
            <name>${EA_NAME}</name>
            <type>String</type>
            <value>${SELECTION}</value>
        </extension_attribute>
    </extension_attributes>
</computer>
EOF

## Update the current computer's server record with the updated EA value
curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}" -T "/tmp/EA_${EA_ID}.xml" -X PUT

if [ $? == 0 ]; then
    echo "Extension Attribute value updated successfully"
    /usr/local/bin/jamf displayMessage -message "Extension Attribute value updated successfully"
    rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
    exit 0
else
    echo "Error when attempting to update the Extension Attribute value"
    /usr/local/bin/jamf displayMessage -message "Error when attempting to update the Extension Attribute value"
    rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
    exit 1
fi

}

if [ "$SELECTION" != "false" ]; then
    updateEAValue
else
    echo "User canceled from selection. Nothing to do"
    exit 0
fi

One other thing - because this has the jamf binary accessing System Events, you might run into a PPPC issue when the script is run on any macOS Mojave systems where they will see a prompt to allow the jamf binary to access System Events. If you deploy a PPPC profile to handle those cases it can bypass it, but if not, the techs will need to "allow" the access before the dialog will appear. Just something to be aware of.

View solution in original post

mm2270
Legendary Contributor III

Hey @joethedsa I'm glad the script is working for you.

As for the specific permissions needed, those kinds of things are usually a little trickier to figure out, and it's an area that Jamf really should address with proper documentation, which I don't think exists (if it does, I'd love to see it)

I created a new API account specifically for this purpose, and, it took a little testing, but here's what I figured out for minimum privileges
This is all under Jamf Pro Server Objects:

Computers = "Read" and "Update" checked
Extension Attributes = "Read" checked
Users = "Update" checked

Don't ask me why it requires update privs for Users. I only figured that out because I remember other folks here mentioning that as an odd requirement for minimum API privileges when making any changes. I wish Jamf would explain why that's necessary, but in the meantime, try using those privilege settings and see if it works.

View solution in original post

joethedsa
Contributor II

@mm2270,
Okay, now that I've read the following article here, I think that this is a result of changes in xpath. I just realized that this failure is happening on my test box running Big Sur Beta athough I have had issues with it working in Catalina also randomly. I believe that an if/then statement in the script as noted in the link to differentiate between when to use the appropriate xpath will have to be added:

xpath() {
    # the xpath tool changes in Big Sur 
    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$@"
    else
        /usr/bin/xpath "$@"
    fi
}

In the context of the script that you posted on this page, I don't know the best place to insert it.

Any initial thoughts?

View solution in original post

mm2270
Legendary Contributor III

Hey @joethedsa Thanks for actually pointing that out. I had somehow missed that xpath had changes in Big Sur, so this is an important thing to note.

As for where to put that function, going off of Armin's article you linked to, he mentions just putting near the top of the script, before xpath is called anywhere. That should be all you need to do, because since the function is using the term xpath, anyplace where xpath is called, such as on these lines:

## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

will just use the correct xpath syntax based on the OS version it sees.

View solution in original post

32 REPLIES 32

JustDeWon
Contributor III

Just edit the techs privileges to allow them to perform that action in the JSS for each machine.

joethedsa
Contributor II

@JustDeWon, I should have mentioned that I was hoping not to use the JSS GUI to do this. We will have student workers (HigherEd environment) that will be setting up computers that will not have access to JSS. I was hoping that during the unboxing of the Mac, the tech would be able to launch self service, launch a policy which in turn would ask for some type of input to give the computer the appropriate EA needed to scope it correctly.

mm2270
Legendary Contributor III

If you don't want to allow the techs to go into your Jamf Pro server and edit the computer records, which, if so, I would kind of understand, then another option could be to use an API account to read back the values in the EA, and place them as selectable options in the dialog, so the tech can choose one from the list. From there, the app/script would need to be able to apply that EA value and update the computer record. This would require an API account that can both read and write information back to your Jamf Pro server.

Here's an example API call that would grab the values of the specific Extension Attribute that has your drop down values.

curl -H "Accept: text/xml" -sfku apiuser:apipass https://your.jss.url/JSSResource/computerextensionattributes/id/<EA-ID> | xpath '/computer_extension_attribute/input_type/popup_choices' 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}'

I can post back more later on how you could use that to put those values into an AppleScript dialog if you like.

mac_mielke
New Contributor II
  1. Enrollment complete policy(ongoing) that creates a plist, key, and prompts for end user/tech to select options from a dropdown or enter value into a textbox. If the plist and key is filled out, clear it and reprompt(in case of re-enroll.)
  2. Extension Attribute set to report the contents of that file/key or return "unknown" if blank.
  3. If the value is anything but unknown, remove the device from scope.

Some tweaking to be done but it should accomplish it

joethedsa
Contributor II

@mm2270, it would be awesome if you could share how to put that in an AppleScript dialogue. @mac_mielke, I'm still fairly new to Jamf and being a MacAdmin so wouldn't know where to start with your suggestions. Could you provide more details about creating this plist file with all of the contents that would be needed to accomplish my goal?

mm2270
Legendary Contributor III

@joethedsa Sure. Here is a script that should do what you're looking for. I already tested this myself so I know it works as long as it's set up correctly.

To use this, you need to use script parameters to pass the API account username, password and the Extension Attribute's id to it.
If you haven't used script parameters before, these are essentially placeholders in the script that, at script run time, the Jamf Pro server will pass down a value for them that you specify in the policy where you add the script. In general, when needing to use things like API account credentials, especially if that account has access to write back to your Jamf server, it's best to put them into parameters so it's more secure. They won't be hardcoded into the script.
The EA ID part isn't as necessary (it could be hardcoded), but I put it there to make it a little more flexible, i.e, if you have other EAs that use drop down values, the same script could be used to provide an interactive way to apply them to a machine.

When using the script in the policy, be sure to add in a value in Parameters 4, 5 and 6, for the API account name, password and the EA's ID (you can view the ID by viewing the Extension Attribute itself - the ID will be in the URL in your browser, like ....computerExtensionAttributes.html?id=8&o=r where 8 is the ID) Make sure the account you add has the ability to read and update computer records.

Here's the script:

#!/bin/bash

APIUSER="$4"
APIPASS="$5"
EA_ID="$6"

## Make sure we got values for the 3 parameters above
if [ -z "$APIUSER" ] || [ -z "$APIPASS" ] || [ -z "$EA_ID" ]; then
    echo "One of the required script parameters was not filled in. Please check the policy script parameters and try again."
    exit 1
fi

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

## This Mac's UUID/UDID string
UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## The logged in user and their UID
LOGGED_IN_USER=$(stat -f%Su /dev/console)
LOGGED_IN_UID=$(id -u "$LOGGED_IN_USER")

## API call to obtain the Extension Attribute and store in file
curl -H "Accept: text/xml" -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computerextensionattributes/id/${EA_ID}" | xmllint --format - > /tmp/${EA_ID}.xml

## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

if [ ! -z "$VALUES_ALL" ]; then
    ## Values were obtained for the EA. Prompt user to make a selection from a list
    SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOD
    tell application "System Events"
        activate
        set Values to do shell script "echo "$VALUES_ALL""
        set ValuesList to paragraphs of Values
        choose from list ValuesList with prompt "Select a value from the list:"
    end tell
EOD)
else
    echo "No values obtained in the API"
    exit 1
fi

function updateEAValue ()
{

## Create an xml file containing the EA settings
cat << EOF > /tmp/EA_${EA_ID}.xml
<computer>
    <extension_attributes>
        <extension_attribute>
            <id>${EA_ID}</id>
            <name>${EA_NAME}</name>
            <type>String</type>
            <value>${SELECTION}</value>
        </extension_attribute>
    </extension_attributes>
</computer>
EOF

## Update the current computer's server record with the updated EA value
curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}" -T "/tmp/EA_${EA_ID}.xml" -X PUT

if [ $? == 0 ]; then
    echo "Extension Attribute value updated successfully"
    /usr/local/bin/jamf displayMessage -message "Extension Attribute value updated successfully"
    rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
    exit 0
else
    echo "Error when attempting to update the Extension Attribute value"
    /usr/local/bin/jamf displayMessage -message "Error when attempting to update the Extension Attribute value"
    rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
    exit 1
fi

}

if [ "$SELECTION" != "false" ]; then
    updateEAValue
else
    echo "User canceled from selection. Nothing to do"
    exit 0
fi

One other thing - because this has the jamf binary accessing System Events, you might run into a PPPC issue when the script is run on any macOS Mojave systems where they will see a prompt to allow the jamf binary to access System Events. If you deploy a PPPC profile to handle those cases it can bypass it, but if not, the techs will need to "allow" the access before the dialog will appear. Just something to be aware of.

joethedsa
Contributor II

@mm2270, THANK YOU! I will test this and let you know how I venture.

joethedsa
Contributor II

@mm2270, This worked like a charm! Thanks again for your contribution and expertise. I wasn't sure if a recon would be needed to be done or not to reflect the EA change. I quickly found out it wasn't needed since the script forced a communication to JSS via the API. It was pretty immediate.

joethedsa
Contributor II

@mm2270, I created an API account with full admin privileges when I was testing the functionality of this and as mentioned, it worked. Do you know what permissions I would need to grant this account to do this without opening it up with admin privileges? All of the privileges that correlate to what I think it would need access to, doesn't seem to be working. @mac_mielke, thank you also for your suggestion with the plist file.

mm2270
Legendary Contributor III

Hey @joethedsa I'm glad the script is working for you.

As for the specific permissions needed, those kinds of things are usually a little trickier to figure out, and it's an area that Jamf really should address with proper documentation, which I don't think exists (if it does, I'd love to see it)

I created a new API account specifically for this purpose, and, it took a little testing, but here's what I figured out for minimum privileges
This is all under Jamf Pro Server Objects:

Computers = "Read" and "Update" checked
Extension Attributes = "Read" checked
Users = "Update" checked

Don't ask me why it requires update privs for Users. I only figured that out because I remember other folks here mentioning that as an odd requirement for minimum API privileges when making any changes. I wish Jamf would explain why that's necessary, but in the meantime, try using those privilege settings and see if it works.

joethedsa
Contributor II

@mm2270, That did the trick. "Users" was the only one I didn't configure because it didn't seem relevant. Thank you for checking it out. It would be nice if they had a "canned" permission set for roles like an API connection or something similar.

joethedsa
Contributor II

@mm2270,
I'm trying to dissect the script that you shared on 3/15/2019. Specifically, I'm looking at the if/else statement. I'm getting the "No values obtained in the API" which I believe is indicating that something is coming up "blank" and or the command is not able to grab information using the API. I can see the file .xml file being created in the tmp folder but from there I believe it's failing. It's possible that this could be related to the running this script on Big Sur Beta although the same thing happens on Catalina at random too. Do you or anyone have any thoughts as to what might be causing the script to exit code 1?

mm2270
Legendary Contributor III

@joethedsa I would take a look at the xml file being downloaded in the previous step. It should still be in the /tmp/ directory since if it fails at that stage, it doesn't clean up the xml file. See if it contains any contents. If possible, post the contents of that xml, removing or obscuring anything that might be sensitive. I'll take a look at it to see if I can spot why it would be failing.

If the xml file is empty or doesn't look like xml, then there's something wrong with the curl command. If that's the case, double check the permissions to make sure the account can access those EAs. You can always test the access directly in Terminal. For example, run something like this, replacing the username, password and https://yourjamfserver.com sections with actual values, as well as the ID of the Extension Attribute.

curl -H "Accept: application/xml" -u 'username:password' https://yourjamfserver.com/JSSResource/computerextensionattributes/id/1 | xmllint --format -

joethedsa
Contributor II

@mm2270 ,
Sorry for delay. Here are the contents of the XML file:

<?xml version="1.0" encoding="UTF-8"?>
<computer_extension_attribute>
  <id>2</id>
  <name>Funding Type</name>
  <enabled>true</enabled>
  <description/>
  <data_type>String</data_type>
  <input_type>
    <type>Pop-up Menu</type>
    <popup_choices>
      <choice>Department</choice>
      <choice>Endowed Chair Fund</choice>
      <choice>Grant</choice>
      <choice>ITS</choice>
      <choice>Other</choice>
      <choice>Start-up</choice>
    </popup_choices>
  </input_type>
  <inventory_display>User and Location</inventory_display>
  <recon_display>Extension Attributes</recon_display>
</computer_extension_attribute>

Here is the output of running the policy manually via Terminal using "jamf policy -id <number>"

localaccount@macbook-pro ~ % sudo jamf policy -id 133
Checking for policy ID 133...
Executing Policy Computer Funding Type
Running script Computer Funding Type...
Script exit code: 1
Script result:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   538    0   538    0     0   2650      0 --:--:-- --:--:-- --:--:--  2650
Usage:
/usr/bin/xpath5.28 [options] -e query [-e query...] [filename...]

If no filenames are given, supply XML on STDIN. You must provide at
least one query. Each supplementary query is done in order, the
previous query giving the context of the next one.

Options:

-q quiet, only output the resulting PATH.
-s suffix, use suffix instead of linefeed.
-p postfix, use prefix instead of nothing.
-n Don't use an external DTD.
-:1: parser error : Document is empty

^
No values obtained in the API

Error running script: return code was 1.
Submitting log to https://myjss.jamfcloud.com/
localaccount@macbook-pro ~ %

I'm thinking this must have something to do with "xpath" possibly being different in 10.15.x and higher since I don't have the issue with computers on 10.14.x and earlier.

joethedsa
Contributor II

@mm2270,
Okay, now that I've read the following article here, I think that this is a result of changes in xpath. I just realized that this failure is happening on my test box running Big Sur Beta athough I have had issues with it working in Catalina also randomly. I believe that an if/then statement in the script as noted in the link to differentiate between when to use the appropriate xpath will have to be added:

xpath() {
    # the xpath tool changes in Big Sur 
    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$@"
    else
        /usr/bin/xpath "$@"
    fi
}

In the context of the script that you posted on this page, I don't know the best place to insert it.

Any initial thoughts?

mm2270
Legendary Contributor III

Hey @joethedsa Thanks for actually pointing that out. I had somehow missed that xpath had changes in Big Sur, so this is an important thing to note.

As for where to put that function, going off of Armin's article you linked to, he mentions just putting near the top of the script, before xpath is called anywhere. That should be all you need to do, because since the function is using the term xpath, anyplace where xpath is called, such as on these lines:

## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

will just use the correct xpath syntax based on the OS version it sees.

joethedsa
Contributor II

@mm2270,
Forgive me as I'm not well versed in scripting, in the script that I will be adding this function text to, do I need to replace anything like the "$@" with a command to actually do something or will just copying/pasting it as is from the website complement what you've already put in the script?

joethedsa
Contributor II

@mm2270, wanted to circle back to this. Thanks for the guidance. Inserting the function before the xpath command in the script resolved my issue. As always, thanks for your input.

Cheers to 2021!

ladygreyjedi
New Contributor III

Hey, I'm running into issues with this script with Big Sur. I added the xpath() function toward the beginning of the script, but now getting a different error.

Any ideas?

Running script JAMF - Extension Attribute - Change...
Script exit code: 1
Script result: % Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 480 0 480 0 0 401 0 --:--:-- 0:00:01 --:--:-- 401
dyld: missing lazy symbol called
-:1: parser error : Document is empty

^
No values obtained in the API
Error running script: return code was 1.

mm2270
Legendary Contributor III

@ladygreyjedi Without doing much testing or digging too deep, I'd say based on the error message you're seeing, it sounds like the curl command isn't actually pulling down a valid XML file. It could also be that the XML file being used for the API upload isn't formatted correctly, but I don't believe so. I say that because the "No values obtained in the API" comes from an echo line in my script, which is part of this block.

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

if [ ! -z "$VALUES_ALL" ]; then
    ## Values were obtained for the EA. Prompt user to make a selection from a list
    SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOD
    tell application "System Events"
        activate
        set Values to do shell script "echo "$VALUES_ALL""
        set ValuesList to paragraphs of Values
        choose from list ValuesList with prompt "Select a value from the list:"
    end tell
EOD)
else
    echo "No values obtained in the API"
    exit 1
fi

As you can see, it's trying to grab all the "values" contained in the EA on this line:

VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

So at this point in the script, the xpath command is not pulling out values from that xml file, which could mean the curl command didn't get anything back from the API to write into that file, or could mean the xpath command didn't work to extract those values for some reason.

To solve this, like I had mentioned to @joethedsa some posts back, first check the xml file being downloaded into tmp to see if it appears as valid XML. If not, then you'll need to check the credentials and other info being used in the script as a first step.
If it is pulling a good XML file, then the next step is going to be doing some manual xpath commands against it to see what results the script line pulls, if anything. It's possible some additional adjustments need to be made. I'm not sure if I can run any tests at the moment, but post back with any results from the above, and I'll see what I can help with.

PhillyPhoto
Valued Contributor

Is there a way to add a check for what the value is already set to and then highlight it in the menu? That would help techs see what something already is and only change it if they needed to.

mm2270
Legendary Contributor III

@PhillyPhoto Actually that might be possible. Applescript's choose from list function has something you can add to it called "default items", which means it can highlight a specific item from your list.
You'd have to do a separate API call to see IF there is an existing EA value for the system it's running on, and then make that into a variable that can be used in the script. It's the latter part of that which I haven't done before though, so I'm not exactly clear how to make that happen. But it does seem doable. Here's an example of a pure Applescript that would allow you to choose from a list, but select one of the items from it that isn't the first one said list.

tell application "System Events"
    activate
    set ValuesList to {"Windows 10", "Ubuntu Linux", "macOS", "Red Hat Enterprise", "Other"}
    choose from list ValuesList with prompt "Choose an Operating System:" default items {"macOS"}
end tell

PhillyPhoto
Valued Contributor

@mm2270 I can't get it to grab the EA value based on the EA number. I'm sure I'm missing something simple in the XMLLINT code. I reused some code I had elsewhere to grab the value based on the EA name, but I would rather keep it to using the ID number.

Add this variable to the top:

EA_NAME="$7"

Add this somewhere below the JSS_URL and UDID lines:

EA_VAL=`curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}" -X GET | xmllint --xpath "( //computer/extension_attributes/extension_attribute/name )[ contains( ., '$EA_NAME' ) ]/../value/text()" - `

You can then just append the list by changing:

choose from list ValuesList with prompt "Choose an Operating System:"

to

choose from list ValuesList with prompt "Choose an Operating System:" default items {"$EA_VAL"}

as pointed out by you.

This was only tested on an EA that configured to use a "pop-up menu" list, and not multiple values.

mm2270
Legendary Contributor III

Hey @PhillyPhoto Here is the syntax that seems to work for me. Give this a try, substituting the ID value of course for whatever Extension Attribute value you need.

xpath '/computer/extension_attributes/extension_attribute[id = "10"]/value/text()'

Oh, one note. With the above I used subset with the API call to only grab the Extension Attrributes from the Mac. So for example, the full command might look something like this

curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}/subset/extension_attributes" | xpath '/computer/extension_attributes/extension_attribute[id = "10"]/value/text()'

PhillyPhoto
Valued Contributor

@mm2270 I had to use single quote to get it to work for me. My line looks like this:

EA_VAL=$(curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}/subset/extension_attributes" | xpath '/computer/extension_attributes/extension_attribute[id = '$EA_ID']/value/text()')

My whole script:

#!/bin/bash

APIUSER="$4"
APIPASS="$5"
EA_ID="$6"

xpath() {
    # the xpath tool changes in Big Sur 
    if [[ $(sw_vers -buildVersion) > "20A" ]]; then
        /usr/bin/xpath -e "$@"
    else
        /usr/bin/xpath "$@"
    fi
}

## Make sure we got values for the 3 parameters above
if [ -z "$APIUSER" ] || [ -z "$APIPASS" ] || [ -z "$EA_ID" ]; then
    echo "One of the required script parameters was not filled in. Please check the policy script parameters and try again."
    exit 1
fi

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

## This Mac's UUID/UDID string
UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## The logged in user and their UID
LOGGED_IN_USER=$(stat -f%Su /dev/console)
LOGGED_IN_UID=$(id -u "$LOGGED_IN_USER")

## API call to obtain the Extension Attribute and store in file
curl -H "Accept: text/xml" -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computerextensionattributes/id/${EA_ID}" | xmllint --format - > /tmp/${EA_ID}.xml

## API call to obtain the current value of the Extension Attribute for the current device
EA_VAL=$(curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}/subset/extension_attributes" | xpath '/computer/extension_attributes/extension_attribute[id = '$EA_ID']/value/text()')

## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

if [ ! -z "$VALUES_ALL" ]; then
    ## Values were obtained for the EA. Prompt user to make a selection from a list
    SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOD
    tell application "System Events"
        activate
        set Values to do shell script "echo "$VALUES_ALL""
        set ValuesList to paragraphs of Values
        choose from list ValuesList with prompt "Select a value from the list:" default items {"$EA_VAL"}
    end tell
EOD)
else
    echo "No values obtained in the API"
    exit 1
fi

function updateEAValue ()
{

    ## Create an xml file containing the EA settings
    cat << EOF > /tmp/EA_${EA_ID}.xml
    <computer>
        <extension_attributes>
            <extension_attribute>
                <id>${EA_ID}</id>
                <name>${EA_NAME}</name>
                <type>String</type>
                <value>${SELECTION}</value>
            </extension_attribute>
        </extension_attributes>
    </computer>
    EOF

    ## Update the current computer's server record with the updated EA value
    curl -ku "${APIUSER}:${APIPASS}" "${JSS_URL}/JSSResource/computers/udid/${UDID}" -T "/tmp/EA_${EA_ID}.xml" -X PUT

    if [ $? == 0 ]; then
        echo "Extension Attribute value updated successfully"
        /usr/local/bin/jamf displayMessage -message "Extension Attribute value updated successfully"
        rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
        exit 0
    else
        echo "Error when attempting to update the Extension Attribute value"
        /usr/local/bin/jamf displayMessage -message "Error when attempting to update the Extension Attribute value"
        rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
        exit 1
    fi

}

if [ "$SELECTION" != "false" ]; then
    updateEAValue
else
    echo "User canceled from selection. Nothing to do"
    exit 0
fi

Thanks for the help

mac_mielke
New Contributor II

You need to be careful about passing credentials into scripts as it's not a safe practice

roiegat
Contributor III

So I have a suggestion that might work and something we use here during the build process. When the ADE process starts it brings up DepNotify and asks the user for certain information (computer name, user assigned to, location, and persona). What I did was I had the script default write those values to a plist file.

So you can create a GUI to get the data and send it to the plist file. Then you can use a EA that uses a script to read the information from the plist file.

But I do think that giving techs access do to do it themselves might be faster.

joethedsa
Contributor II

Does anyone have this same script or something similar which is using basic authentication changed over to token authentication?

For starters I have, the following ground work to get the token but I'm not sure how to use the token to authenticate to the API so I can run scripts similar to the ones shared on this thread.

#!/bin/bash

username="username_here"
password="password_here"
URL="https://someplace.jamfcloud.com"

# created base64-encoded credentials
encodedCredentials=$( printf "$username:$password" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - )

# echo encodedCredentials: "$encodedCredentials"

# generate an auth token
authToken=$( /usr/bin/curl "$URL/api/v1/auth/token" \
--silent \
--request POST \
--header "Authorization: Basic $encodedCredentials" )

# echo authToken: "$authToken"

# parse authToken for token, omit expiration
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then 	
   token=$(/usr/bin/awk -F \" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs)
else
   token=$(/usr/bin/plutil -extract token raw -o - - <<< "$authToken")
fi

 

This is a template I work from, it might need some tweaking at the command part on the bottom, but the token stuff should work.

 

#!/bin/sh

# Set some variables
username=$4
password=$5
jssURL="https://company.jamfcloud.com"

# Get the token
authBasic=$(echo '$username:$password\c' | base64) # "\c" is required to get the right format
authToken=$(curl -sk "$jssURL/api/v1/auth/token" -H "Accept: application/json" -H "Authorization: Basic $authBasic" -X POST --connect-timeout 10)
curlRes=$?

# Check the token result
if [[ $curlRes -gt 0 ]]
then
    echo "CURL return code: $curlRes"
    exit 1
fi

# We got a token, lets parse the data for the token itself
read -r -d '' JXA <<EOF
function run() {
    var tokenInfo = JSON.parse(\`$authToken\`);
    return tokenInfo.token;
}
EOF

OAuthToken=$( osascript -l 'JavaScript' <<< "${JXA}" )

read -r -d '' JXA <<EOF
function run() {
    var tokenInfo = JSON.parse(\`$authToken\`);
    return tokenInfo.expires;
}
EOF

# Set a variable with the expiration if your script will be running a while and you need to refer back
OAuthExpires=$( osascript -l 'JavaScript' <<< "${JXA}" )

# Run a Classic API call with bearer token auth
serialNum=$(ioreg -l | grep IOPlatformSerialNumber | awk '{print $4}' |  cut -d "\"" -f 2)
cloudCompID=$(curl -sk "$jssURL/JSSResource/computers/serialnumber/$serialNum" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $OAuthToken" -X GET --connect-timeout 10 | xmllint --xpath "/computer/general/id/text()" - )
echo "Current machine computer ID: $cloudCompID"

# Run an Jamf Pro API call with bearer token auth
computersJSON=$(curl -Ls -X GET "$jssURL/api/v1/computers-inventory?section=&page=0&page-size=100&sort=id%3Aasc" -H "Accept: application/json" -H "Authorization: Bearer $OAuthToken")
echo "Computers: $computersJSON"

 

 

Here is a the original script shared in this thread (thanks @mm2270 ) converted to token authentication, however I'm getting a permissions error message at one part.

#!/bin/bash

username="username_here"
password="password_here"
URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url | sed 's|/$||')
EA_ID="$6"

# Make sure xpath changes won't bother us!
xpath() {
	# the xpath tool changes in Big Sur 
	if [[ $(sw_vers -buildVersion) > "20A" ]]; then
		/usr/bin/xpath -e "$@"
	else
		/usr/bin/xpath "$@"
	fi
}

########################################
##  This section is getting the token ##
########################################

# created base64-encoded credentials
encodedCredentials=$( printf "$username:$password" | /usr/bin/iconv -t ISO-8859-1 | /usr/bin/base64 -i - )

# generate an auth token (This is not the sanitized version)
authToken=$( /usr/bin/curl "$URL/api/v1/auth/token" \
--silent \
--request POST \
--header "Authorization: Basic $encodedCredentials" )

# parse authToken for token, omit expiration (this is the sanitized version)
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then 	
	token=$(/usr/bin/awk -F \" 'NR==2{print $4}' <<< "$authToken" | /usr/bin/xargs)
else
	token=$(/usr/bin/plutil -extract token raw -o - - <<< "$authToken")
fi

########################################################################
######## This is the start of the Computer Role API Commands ###########
########################################################################

if [ -z "$username" ] || [ -z "$password" ] || [ -z "$EA_ID" ]; then
	echo "One of the required script parameters was not filled in. Please check the policy script parameters and try again."
	exit 1
fi

# Computer's UUID/UDID string
UDID=$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')

## The logged in user and their UID
LOGGED_IN_USER=$(stat -f%Su /dev/console)
LOGGED_IN_UID=$(id -u "$LOGGED_IN_USER")

## API call to obtain the Extension Attribute and store in file
curl -sk "$URL/JSSResource/computerextensionattributes/id/$EA_ID" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $token" -X GET --connect-timeout 10 | xmllint --format - > /tmp/${EA_ID}.xml


## Get the EA display name
EA_NAME=$(xpath '/computer_extension_attribute/name/text()' < /tmp/${EA_ID}.xml)

## API call to obtain a list of drop down values from the Extension Attribute
VALUES_ALL=$(xpath '/computer_extension_attribute/input_type/popup_choices' < /tmp/${EA_ID}.xml 2>/dev/null | xmllint --format - | awk -F'>|<' '/<choice>/{print $3}')

if [ ! -z "$VALUES_ALL" ]; then
	## Values were obtained for the EA. Prompt user to make a selection from a list
	SELECTION=$(/bin/launchctl asuser "$LOGGED_IN_UID" sudo -iu "$LOGGED_IN_USER" /usr/bin/osascript << EOD
	tell application "System Events"
		activate
		set Values to do shell script "echo \"$VALUES_ALL\""
		set ValuesList to paragraphs of Values
		choose from list ValuesList with prompt "Choose the appropriate computer role:"
	end tell
EOD)
else
	echo "No values obtained in the API"
	exit 1
fi

function updateEAValue ()
{

## Create an xml file containing the EA settings
cat << EOF > /tmp/EA_${EA_ID}.xml
<computer>
	<extension_attributes>
		<extension_attribute>
			<id>${EA_ID}</id>
			<name>${EA_NAME}</name>
			<type>String</type>
			<value>${SELECTION}</value>
		</extension_attribute>
	</extension_attributes>
</computer>
EOF

## Update the current computer's server record with the updated EA value
curl -sk "$URL/JSSResource/computers/udid/${UDID}" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $token" -X PUT --connect-timeout 10 | "/tmp/${EA_ID}.xml" -X PUT

if [ $? == 0 ]; then
	echo "Extension Attribute value updated successfully"
	/usr/local/bin/jamf displayMessage -message "Computer role updated successfully"
	rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
	exit 0
else
	echo "Error when attempting to update the Extension Attribute value"
	/usr/local/bin/jamf displayMessage -message "Error when attempting to update the Extension Attribute value"
	rm /tmp/EA_${EA_ID}.xml; rm /tmp/${EA_ID}.xml
	exit 1
fi

}

if [ "$SELECTION" != "false" ]; then
	updateEAValue
else
	echo "User canceled from selection. Nothing to do"
	exit 0
fi

 The part that I'm not able to get past for this API call to write the EA to the computer record is at this point:

curl -sk "$URL/JSSResource/computers/udid/${UDID}" -H "Content-Type: application/xml" -H "Accept: application/xml" -H "Authorization: Bearer $token" -X PUT --connect-timeout 10 | "/tmp/${EA_ID}.xml" -X PUT

I suspect it has something to do with the syntax- possibly the method that I'm using (PUT vs GET vs POST) to update the record or something not correct in the command.  The exact error message I'm getting is: /tmp/6.xml: Permission denied.

Any guidance would be appreciated!

Ironically, I was looking at that script myself today trying to update my old auth scripts. I got that same error, I suspect there might be another xml file in tmp with that name (since it's just a number). I updated my code to "/tmp/$Jamf_EA_{EA_ID}.xml" in a few spots and it seems to be working now (although I'm getting a HTTP 401 return code trying to get the token). Slow progress is still progress, right?

 

Edit: 

My credentials were setup like this:

encodedCredentials=$(echo "$APIUSER:$APIPASS\c" | base64)

This worked fine when I ran the script locally, but failed when running through my Self Service policy. When I swapped in the `printf` command above, I got the prompt below and clicking that worked.

Screen Shot 2023-01-23 at 11.44.50 PM.png

@PhillyPhoto , did you ever get this to work?