Script to Add Computer to Static Group

jared_f
Valued Contributor

Has anyone successfully deployed the following script to add a computer to a static group. Goal: Have buttons in Self Service to add a computer to a static group.

$4 is the group ID.

#!/bin/sh

#API login info
apiuser="apiaccount"
apipass='apiaccountpassword'
jamfProURL="https://myjamfinstance.jamfcloud.com"

ComputerName=$(hostname)

#Group to update
GroupID="$4"
apiURL="JSSResource/computergroups/id/${GroupID}"

#XML header stuff
xmlHeader="<?xml version="1.0" encoding="UTF-8" standalone="no"?>"

apiData="<computer_group><id>${GroupID}</id><name>Whatever the GroupName Is</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -sSkiu ${apiuser}:${apipass} "${jamfProURL}/${apiURL}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT  > /dev/null
18 REPLIES 18

jhuls
Contributor III

Just out of curiosity why wouldn't a Smart Group work for the scope of what you're trying to accomplish?

AdamCraig
Contributor III

I have a goal of phasing this out in hopes of using a drop down extension attribute instead. But we do currently do this.

It has a few preset parameters:
$4 = Group ID#
$5 = 'additions' or 'deletions' It has to be just either of those strings with no quotes, or this won't function. This will control if it adds the computer or deletes the computer from the group.
$6 = APIUserName
$7 = APIPassword
if API username and Password are not given it will prompt the user for the JSS ID and Password.
$8 = Silent
anystring will make this run silent. If this is left blank then the user will get an alert of success or failure joining the group.

#! /bin/bash

jamfproURL="https://yoururlhere.jamfcloud.com"

default_group="856"
computer_name="$2"
current_user="$3"
silent="$8"



if [ -z $4 ]
    then
        groupid="$default_group"
    else
        groupid="$4"
fi

if [ -z $5 ]
    then
        action="additions"
    else
        action="$5"
fi

if [ -z $6 ]
    then
        APIUser=$(/usr/bin/osascript<<END
        tell application "System Events"
        activate
        set the answer to text returned of (display dialog "Please enter your Jamf Username:" default answer "" buttons {"Continue"} default button 1)
        end tell
        END)
    else
        APIUser="$6"
fi

if [ -z $7 ]
    then
        APIPass=$(/usr/bin/osascript<<END
        tell application "System Events"
        activate
        set the answer to text returned of (display dialog "Please enter your Jamf Password:" default answer "" with hidden answer buttons {"Continue"} default button 1)
        end tell
        END)
    else
        APIPass="$7"
fi

xml="<computer_group><computer_${action}><computer><name>$computer_name</name></computer></computer_${action}></computer_group>"

fullURL="${jamfproURL}/JSSResource/computergroups/id/${groupid}"

echo "Created XML"
echo $xml

echo "Jamf API URL"
echo $fullURL


result=$`curl "$fullURL" -u "$APIUser:$APIPass" -H "Content-Type: text/xml" -X PUT -d "$xml"`

echo "result"
echo "$result"

if [ -z $silent ] ; then
    if [[ "$result" == *"<p>The request requires user authentication</p>"* ]] ; then
        echo "Showing Fail message"
        /usr/bin/osascript -e 'tell app "System Events" to display dialog "Failed to update Static Group. Please check your network connection and try again."'
    else
        echo "Showing success message"
        dialog="${action} for the Static Group probably successful."
        cmd="Tell app "System Events" to display dialog "$dialog""
        /usr/bin/osascript -e "$cmd"
    fi
else
    echo "running silent No user notifications"
fi
exit 0

athomson
New Contributor III

This may be more than you bargained:

#!/bin/bash


#    While static groups within JAMF PRO are typically thought of as 
#    less-uesful than their more dynamic counterparts known as smart 
#    groups, I have found they still serve a critical role. This is 
#    especially so for those who extract the most from JAMF PRO via a
#    series of API scripts. Those API script often return a list of 
#    computer IDs or names. But those lists usually need to be acted 
#    upon in some way. Being able to easily create a static group within
#    JAMF PRO can make that happen. 

#    That is why I wrote this script. It allows for the quick creation
#    and/or re-population of a static group based on a string of computer 
#    IDs or names

#    Author:        Andrew Thomson
#    Date:          02-10-2017
#    GitHub:        https://github.com/thomsontown
#    Version:       1.02 (12-11-2019)


#    uncomment to override variables
# JSS_USER=""
# JSS_PASSWORD=""
# JSS_AUTH=""


function addGroupMembers() {

    local GROUP_ID="$1"
    local GROUP_NAME="$2"
    local COUNT=1

    #   create opening xml for replacing members of the static group
    XML_COMPUTER_TEMPLATE="<computer_group><id>$GROUP_ID</id><name>$GROUP_NAME</name><computers>"

    #   enumerate computers to re-populate static group
    for COMPUTER in ${COMPUTERS[@]}; do

        progressBar "${#COMPUTERS[@]}" "$((COUNT++))"

        #   derermine if computer id (integer) or name (alpha) was provided
        if isInteger "$COMPUTER"; then
            XML_COMPUTER=$(/usr/bin/curl -X GET -H "Content-Type: application/xml" -H "Authorization: Basic $JSS_AUTH" -s "${JSS_URL%/}/JSSResource/computers/id/$COMPUTER/subset/general" 2> /dev/null)

            COMPUTER_NAME=$(echo $XML_COMPUTER | /usr/bin/xpath "/computer/general/name/text()"          2> /dev/null)
            COMPUTER_SN=$(echo $XML_COMPUTER   | /usr/bin/xpath "/computer/general/serial_number/text()" 2> /dev/null)
            COMPUTER_ID="$COMPUTER"
        else
            XML_COMPUTER=$(/usr/bin/curl -X GET -H "Content-Type: application/xml" -H "Authorization: Basic $JSS_AUTH" -s "${JSS_URL%/}/JSSResource/computers/name/$COMPUTER/subset/general" 2> /dev/null)

            COMPUTER_ID=$(echo $XML_COMPUTER | /usr/bin/xpath "/computer/general/id/text()"            2> /dev/null)
            COMPUTER_SN=$(echo $XML_COMPUTER | /usr/bin/xpath "/computer/general/serial_number/text()" 2> /dev/null)
            COMPUTER_NAME="$COMPUTER"
            COMPUTER_NAME=$(encodeUrl "$COMPUTER_NAME")
        fi

        #   verify computer details were found      
        if [ -z "$COMPUTER_SN" ] || [ -z "$COMPUTER_ID" ] || [ -z "$COMPUTER_NAME" ]; then 
            echo -e  "
ERROR: Unable to retrieve info for computer [$COMPUTER]." >&2
            continue
        fi 

        #   insert xml requied for each computer be a member of the static group
        XML_COMPUTER_TEMPLATE+="<computer><id>$COMPUTER_ID</id><name>$COMPUTER_NAME</name><serial_number>$COMPUTER_SN</serial_number></computer>"
    done

    #   close out the xml for re-populating members of the static group
    XML_COMPUTER_TEMPLATE+="</computers></computer_group>"

    #   verify xml formatting before submitting
    if ! XML_COMPUTER_TEMPLATE=$(echo $XML_COMPUTER_TEMPLATE | /usr/bin/xmllint --format -); then 
        echo "ERROR: Imporperly formatted data structure found." >&2
        return $LINENO
    fi

    #   upload xml to re-populate the computers in the static group
    HTTP_CODE=$(/usr/bin/curl -X PUT -H "Content-Type: application/xml" -w "%{http_code}" -H "Authorization: Basic $JSS_AUTH" -d "$XML_COMPUTER_TEMPLATE" -o /dev/null -s "${JSS_URL%/}/JSSResource/computergroups/id/$GROUP_ID" 2> /dev/null)
    if [ "$HTTP_CODE" -ne "201" ]; then
        echo "ERROR: Unable to replace computers in static group." >&2
        return $LINENO
    fi
}


function createStaticGroup() {

    # $1 = Name of static group to create. (My Amazing Group)
    local GROUP_NAME="$1"

    #   query jss for existing group name to get id
    GROUP_ID=$(/usr/bin/curl -X GET -H "Accept: application/xml" -H "Authorization: Basic $JSS_AUTH" -s "${JSS_URL%/}/JSSResource/computergroups/name/$GROUP_NAME" | /usr/bin/xpath "//computer_group/id/text()" 2> /dev/null)

    #   create new group if no existing one can be found
    if [ -z "$GROUP_ID" ]; then

        #   minimal xml required to create static group
        XML_GROUP_TEMPLATE="<computer_group><id>0</id><name>$GROUP_NAME</name><is_smart>false</is_smart></computer_group>"

         #  upload xml to create static group
        GROUP_ID=$(/usr/bin/curl -X POST -H "Content-Type: application/xml" -H "Authorization: Basic $JSS_AUTH" -d "${XML_GROUP_TEMPLATE}" -s "${JSS_URL%/}/JSSResource/computergroups/id/0" | /usr/bin/xpath "//computer_group/id/text()" 2> /dev/null)

        #   display error or return group id
        if [ -z "$GROUP_ID" ]; then 
            echo "ERROR: Unable to create JSS computer group." >&2
            return $LINENO
        fi
    fi

    echo "$GROUP_ID"
    return 0
}


function encodeBasicAuthorization() {

    # $1 = User name for JSS query. (jsmith)
    # $2 = Password for JSS query.  (itisasecret)
    local USERNAME="$1"
    local PASSWORD="$2"

    if [ -z "$USERNAME" ] || [ -z "$PASSWORD" ]; then
        writeLog "ERROR: Missing parameter(s)." >&2
        return $LINENO
    fi

    if ! AUTHORIZATION=$(/usr/bin/printf "$USERNAME:$PASSWORD" | /usr/bin/iconv -t ISO-8859-1 2> /dev/null | /usr/bin/base64 -i - 2> /dev/null); then
        writeLog "ERROR: Unable to encode authorization credentials [$?]." >&2
        return $LINENO
    else
        echo "$AUTHORIZATION"
        return 0
    fi
}


function encodeUrl() {

    # $1 = Universal Resource Locator to be encoded. (https://www.company.com/some/path/index.htm)
    local URL="$1"
    local PREFIX=$(echo $URL | /usr/bin/grep -Eoi "^(afp|file|ftp|http|https|smb)://" 2> /dev/null)   
    local LENGTH=${#PREFIX}
    local SUFFIX=${URL:$LENGTH} 
    local ENCODED="" 

    if [ -z "$SUFFIX" ]; then
        echo "ERROR: Specified URL is incomplete."
        return $LINENO
    fi

    while [ -n "$SUFFIX" ]; do
        TAIL=${SUFFIX#?}
        HEAD=${SUFFIX%$TAIL}
        case $HEAD in
          [-._~0-9A-Za-z/:]) ENCODED+=$(/usr/bin/printf  %c "$HEAD");; #  encoding not required   
          *) ENCODED+=$(/usr/bin/printf  %%%02x "'$HEAD")              #  encoding required
        esac
        SUFFIX=$TAIL
    done
    echo "${PREFIX}$ENCODED"
}


function initJamf() {

    #   verify encoded authorization
    if [ -z "$JSS_AUTH" ]; then     

        # verify username and password for jss
        if [ -n "$JSS_USER" ] || [ -z "$JSS_PASSWORD" ]; then 
            echo "ERROR: A username or password to access to the JSS was not specified." >&2
            return $LINENO
        fi

        if declare -f encodeBasicAuthorization &> /dev/null; then
            if ! JSS_AUTH=$(encodeBasicAuthorization "$JSS_USER" "$JSS_PASSWORD"); then 
                return $LINENO
            fi
        else
            echo "ERROR: Unable to encode username and password to access the JSS." >&2
            return $LINENO
        fi
    fi

    #   get jss url
    JSS_URL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url 2> /dev/null)
    if [ -z "JSS_URL" ]; then
        echo "ERROR: A a valid URL to the JSS was not specified [JSS_URL]." >&2
        return $LINENO
    fi

    #   verify jss is available
    JSS_CONNECTION=$(/usr/bin/curl --connect-timeout 10 -H "Authorization: Basic $JSS_AUTH" -sw "%{http_code}" ${JSS_URL%/}/JSSCheckConnection -o /dev/null)
    if [ $JSS_CONNECTION -ne 200 ] && [ $JSS_CONNECTION -ne 403 ] ; then
        echo "ERROR: Unable to connect to JSS [$JSS_URL]." >&2
        return $LINENO
    fi
}


function isInteger() { 

    return $([ "$@" -eq "$@" ] 2> /dev/null) 
}


function main() {

    while [ -n "$1" ]; do
        case $1 in
            -n | --name )       shift;GROUP_NAME="$1"   ;;
            -f | --file )       shift;FILE_PATH="$1"    ;;
            -u | --user )       shift;JSS_USER="$1"     ;;
            -p | --pass )       shift;JSS_PASSWORD="$1" ;;
            -a | --auth )       shift;JSS_AUTH="$1"     ;;
            -h | --help )       shift;usage             ;;
            * )                 COMPUTERS+=( "$1" ) 
        esac
        shift
    done

    #   load common source variables
    if [ -f ~/.bash_source ]; then
        source ~/.bash_source
    fi

    #   prompt for password if missing
    if [ -z "$JSS_AUTH" ] && [ -n "$JSS_USER" ] && [ -z "$JSS_PASSWORD" ]; then 
        echo "Please enter JSS password for account [$JSS_USER]: "
        read -s JSS_PASSWORD
    fi

    #   verify user and password or encoded authorization
    if [ -z "$JSS_USER" ] || [ -z "$JSS_PASSWORD" ] && [ -z "$JSS_AUTH" ]; then 
        echo "ERROR: A JSS user name and password are required if no encoded authorization is provided." >&2
        usage
        exit $LINENO
    fi 

    #   verify group name was specified
    if [ -z "$GROUP_NAME" ]; then 
        echo "Please enter a name for the group you wish to create/populate: "
        read GROUP_NAME
        if [ -z "$GROUP_NAME" ]; then 
            echo "ERROR: No group name was specified." >&2
            exit $LINENO
        fi
    fi

    #   verify member source
    if [ ! -r "$FILE_PATH" ] && [ "${#COMPUTERS[@]}" -eq "0" ]; then 
        echo "Please drag-n-drop or enter the path to a line-seperated file containing computer names or IDs: "
        read FILE_PATH
        if [ ! -r "$FILE_PATH" ]; then
            echo "ERROR: The path to the specified readable file cound not be found [$FILE_PATH]." >&2
            exit $LINENO
        fi
    fi

    #   populate array with computer names from file
    if [ -r "$FILE_PATH" ]; then
        while IFS= read -r COMPUTER; do
          COMPUTERS+=($COMPUTER)
        done < "$FILE_PATH"
    fi

    #   obsure password for debug
    for ((i=1; i<${#JSS_PASSWORD}; i++)); do
        JSS_OBSURED+="◦"
    done

    #   display parameters for debug
    if $DEBUG; then 
        echo -e  "NAME: $GROUP_NAME
FILE: $FILE_PATH
USER: $JSS_USER
PASS: $JSS_OBSURED
AUTH: $JSS_AUTH
COMPUTERS: ${COMPUTERS[@]}"
    fi

    #   initialize and verify jamf connection
    if ! initJamf; then exit $LINENO; fi

    #   create/get computer group id
    if ! GROUP_ID=$(createStaticGroup "$GROUP_NAME"); then exit $LINENO; fi

    #   add each computer to group
    addGroupMembers "$GROUP_ID" "$GROUP_NAME" 

    #   play sound to signal completion
    playSound
}


function playSound() {

    # $1 = Path to sound file. (/System/Library/Sounds/Glass.aiff)
    local SOUND_FILE_PATH="$1"

    if [ -z "$SOUND_FILE_PATH" ] && [ -f "/System/Library/Sounds/Glass.aiff" ]; then
        local SOUND_FILE_PATH="/System/Library/Sounds/Glass.aiff" 
    fi

    if [ -f "$SOUND_FILE_PATH" ]; then
        /usr/bin/afplay "$SOUND_FILE_PATH" &> /dev/null
    fi
}


function progressBar() {

    # $1 = Maximum count of progress. (75)
    # $2 = Current count of progress. (10)
    local MAX="$1"
    local COUNT="$2"

    #   verify required parameters
    if [ -z "$MAX" ] || [ -z "$COUNT" ]; then
        echo "ERROR: Missing parameter."
        return $LINENO
    fi

    #   verify parameters are integers
    if [ "$MAX" -ne "$MAX" ] 2> /dev/null || [ "$COUNT" -ne "$COUNT" ] 2> /dev/null; then
        echo "ERROR: Integer expression expected." >&2
        return $LINENO
    fi

    if [ "$COUNT" -lt "$MAX" ]; then 
        #   display progress
        PERCENT_COMPLETE=$(echo "(100/$MAX)*$COUNT" | /usr/bin/bc -l | /usr/bin/awk '{print int($1+0.5)}')
        PROGRESS_DONE=$(echo "$PERCENT_COMPLETE/2" | /usr/bin/bc -l | /usr/bin/awk '{print int($1+0.5)}')
        PROGRESS_LEFT=$(( 50 - $PROGRESS_DONE ))
        DONE_PATTERN=$(/usr/bin/printf "%${PROGRESS_DONE}s") #  % number "done" of blanks
        LEFT_PATTERN=$(/usr/bin/printf "%${PROGRESS_LEFT}s") #  % number "left" of blanks

        /usr/bin/printf "
Processing: [${DONE_PATTERN// /#}${LEFT_PATTERN// /-}] ${PERCENT_COMPLETE}%%"  #  replace blanks with patterns
    else
        /usr/bin/printf "
Processing: [##################################################] 100%%
"
    fi        
}


function usage() {

    echo
    echo "Usage: ./${0##*/} -n "NAME" -f "FILE" -u "USER" -p "PASSWORD" -a "AUTHENTICATION" -h <computer_name> <computer_name>... OR <computer_id> <computer_id>..."
    echo
    echo -e "  -n | --name 		 Specify a static group name to create (required)"
    echo -e "  -f | --file 		 Specify a line-seperated file containing computer names or ids     (optional)"
    echo -e "  -u | --user 		 Specify a JSS user name with access to create computer groups      (required)"
    echo -e "  -p | --pass 		 Specify a JSS password for the user name specified above           (optional)"
    echo -e "  -a | --auth 		 Specify encoded JSS authorization credentials                      (optional)"
    echo -e " 			 If --auth is spcified then user and password are optional."                       
    echo -e "  <computer_name> 	 Computer names can be included as parameters on the command line (optional)"
    echo -e "  <computer_id> 	 Computer ids can be included as parameters on the command line     (optional)" 
    echo -e "  -h | --help 		 Display help"
}


# run main if called directly
if [[ "$BASH_SOURCE" == "$0" ]]; then
    main $@
fi

mrheathjones
New Contributor III

Not super fancy nor the best code but this is what we use to add devices to a particular static group. It's not pretty but it gets the job done.

#! /bin/sh
######################################################################
############## Define Variable Block #################################
######################################################################
groupID="$4"
utilityName="$5"
jamfAuthKey="$6" //Basic Base64 encoded

jamfBaseURL="https://your.jamf.pro:8443"
allComputersFile="/path/to/localfile.txt"
filePath="/file/path/root"
loop="Continue"
oops1Dialog="SORRY! Either the Asset Tag entered: "
oops2Dialog=" was incorrect or that device is not currently being managed by Jamf Pro. Please check the device and try again."
addAnotherDialog="Would you like to add another device?"
mainDialog="Please Enter the Asset Tag of the Mac to add to the "$utilityName" Group (format: XX123456)"
appTitle="Company Name "$utilityName" Utility"
xmlContentType="Content-Type: application/xml"
computersAPI="/JSSResource/computers"
computerIDAPI="/JSSResource/computergroups/id/"
######################################################################
################# Define Functions Block #############################
######################################################################

file_Check() {
mkdir -p "$filePath"
if [ -f "$allComputersFile" ]
then
rm -rf "$allComputersFile"
fi
}

get_List() {
curl -X GET "$jamfBaseURL""$computersAPI" -H "$xmlContentType" -H "${jamfAuthKey}"  > "$allComputersFile"
}

add_Devices() {
assetTag=$(osascript <<EOT
tell app "System Events"
text returned of (display dialog "${mainDialog}" buttons {"Cancel", "Continue"} default button "Continue" default answer "" with title "${appTitle}")
end tell
EOT
)

if ! grep -i ${assetTag} "$allComputersFile"
then
osascript <<EOT
tell app "System Events"
display dialog "${oops1Dialog} ${assetTag} ${oops2Dialog}" buttons {"Done"} default button "Done" with title "${appTitle}"
end tell
EOT
else
echo "<computer_group><computer_additions><computer><name>$assetTag</name></computer></computer_additions></computer_group>" | curl -X PUT -d @- "$jamfBaseURL""$computerIDAPI""$groupID" -H "$xmlContentType" -H "$jamfAuthKey"
fi
}

device_Loop() {
loop=$(osascript <<EOT
tell app "System Events"
button returned of (display dialog "${addAnotherDialog}" buttons {"Cancel", "Continue", "Done" } default button "Done" with title "${appTitle}")
end tell
EOT
)
}
######################################################################
################# Script Run Block ###################################
######################################################################
while [ "${loop}" = "Continue" ]
do
file_Check
get_List
add_Devices
device_Loop
file_Check
done

jared_f
Valued Contributor

@jhuls The majority of our scopes are based on PreStage and auto-assigned or moved at purchasing. We have static groups that emulate those PreStages and I want buttons for our techs to add machines to these groups.

We try to leverage a Smart Groups 97% of the time but these static groups pick up their slack.

jared_f
Valued Contributor

@strayer Your script looks perfect for us. I have the URL defined on the actual script for Jamf Cloud, and the following attributes defined. That being said, the computer is not porting to a static group.

I am getting ": No such file or directory" when it is looking for the group id, which I grabbed from the JSS URL when I am in the static group. I believe that is the correct method?

Thanks for your help!
Jared
359581f29dff439ea0ef30031d878544

AdamCraig
Contributor III

@jared_f Could be this. I put it in as a quote instead of as a script so the backtics areound result=$`curl "$fullURL" -u "$APIUser:$APIPass" -H "Content-Type: text/xml" -X PUT -d "$xml" didn't translate correctly
i've edited my comment above. let me know if that does it.

jared_f
Valued Contributor

@strayer That did the trick. Thanks for the script, it is perfect for our needs

sdagley
Esteemed Contributor II

If you're trying to add a single computer to a Static Group, the <computer_additions> mechanism is much simpler:

#!/bin/sh

#API login info
apiuser="USERNAME"
apipass='PASSWORD'
jamfProURL="https://jamfproserver:8443"

ComputerName=$(/usr/sbin/scutil --get ComputerName)

GroupID="1234"
GroupName="Whatever the Group Name is"

apiURL="JSSResource/computergroups/id/${GroupID}"

#XML header stuff
xmlHeader="<?xml version="1.0" encoding="UTF-8"?>"

apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"

curl -sSkiu ${apiuser}:${apipass} "${jamfProURL}/${apiURL}" 
    -H "Content-Type: text/xml" 
    -d "${xmlHeader}${apiData}" 
    -X PUT  > /dev/null

GGInquisitor
Release Candidate Programs Tester

I'm trying to use this and the command completes but I get bad request in the return when removing the > /dev/null and the computer doesn't post to the group.  Could you take a look and see what I'm doing wrong?  I have both the serial and computer group in my script but currently have it running off the serial number to post.  I get the same return either way.  Here is the return when running from terminal. 

+ apiuser=api_user
+ apipass='XXXX'
+ jamfProURL=https://mdm.company.org:8443
++ hostname
+ ComputerName=localcomputername
++ ioreg -rd1 -c IOPlatformExpertDevice
++ awk '-F"' '/IOPlatformSerialNumber/{print $4 tail}'
+ ComputerSerial=localcomputerserial
+ GroupID=1234
+ GroupName=APITest
+ apiURL=JSSResource/computergroups/id/1234
+ xmlHeader='<?xml version=1.0 encoding=UTF-8?>'
+ apiData='
<computer_group>
		<id>1234</id>
		<name>APITest</name>
	<computer_additions>
		<computer>
			<serial_number>localcomputerserial</serial_number>
		</computer>
	</computer_additions>
</computer_group>'
+ curl -sSkiu 'api_user:XXXX' https://mdm.company.org:8443/JSSResource/computergroups/id/1234 -H 'Content-Type: text/xml' -d '<?xml version=1.0 encoding=UTF-8?>
<computer_group>
		<id>1234</id>
		<name>APITest</name>
	<computer_additions>
		<computer>
			<serial_number>localcomputerserial</serial_number>
		</computer>
	</computer_additions>
</computer_group>' -X PUT
HTTP/2 400 
date: Thu, 24 Mar 2022 15:56:29 GMT
content-type: text/html;charset=UTF-8
content-length: 400
set-cookie: LongStringOfCharacters; Expires=Thu, 31 Mar 2022 15:56:28 GMT; Path=/
set-cookie: LongStringOfCharacters; Expires=Thu, 31 Mar 2022 15:56:28 GMT; Path=/; SameSite=None; Secure
x-frame-options: DENY
cache-control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0
strict-transport-security: max-age=31536000;includeSubDomains
x-xss-protection: 1; mode=block
accept-ranges: bytes
server: Jamf Cloud Node
vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept

<html>
<head>
   <title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Bad Request</p>
<p>Error in XML file</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>

Thanks!

GGInquisitor
Release Candidate Programs Tester

Disregard.  After reading this thread a little more thoroughly and comparing some other scripts, it seems that removing the {xmlHeader} from -d "${xmlHeader}{apiData}" solved the issue.

sdagley
Esteemed Contributor II

@GGInquisitor I would strongly advise you to look at @dlondon 's updated script which uses Bearer Token Auth which will be required for API calls later this year: https://community.jamf.com/t5/jamf-pro/bearer-token-api-and-adding-computer-to-static-group/m-p/2614...

kdpk
New Contributor II

Hello , 

I try to use this commands but every time I have error like 

 -X: command not found

 -d: command not found

Do you have any idea ? 

sdagley
Esteemed Contributor II

@kdpk Do not use the scripts in this thread. You should refer to @dlondon 's updated version which uses Bearer Token Auth (and corrects the formatting of the curl command to include the line continuation characters missing in the script you tried): https://community.jamf.com/t5/jamf-pro/bearer-token-api-and-adding-computer-to-static-group/m-p/2614...

tim_c_arnold
New Contributor

After much testing, I found we can simplify the command. You don't need to include the xmlHeader, GroupID, or GroupName in the XML data you send.

This command works to PUT the computers with ID 777 and 888 into the $GroupID Group:

curl -u "$apiUsername":"$apiPassword" 
https://jss.url.com:8443/JSSResource/computergroups/id/$GroupID 
-H "Content-Type: text/xml" 
-X PUT -s 
-d "<computer_group><computer_additions>
<computer><id>777</id></computer>
<computer><id>888</id></computer>
</computer_additions></computer_group>"

sdagley
Esteemed Contributor II

@tim.c.arnold Thanks for doing the research on trimming that command, but you might want to correct your statement

You don't need to include the xmlHeader, GroupID, or GroupName

as you do need the GroupID.

tim_c_arnold
New Contributor

Ah, yes... I meant - You don't need to supply the GroupID in the XML Data.

You do still need the GroupID in the URL. edited initial post to add clarity.

tkimpton
Valued Contributor II

@mrheathjones whats the jamfAuthKey? You not using an username and password?