10-05-2023 10:24 AM - edited 10-05-2023 10:26 AM
I've seen several posts about this, and have been struggling with it myself, but I finally found a way to create a sort of maintenance window for Patch Management policies. For our faculty and staff computers, we distribute patches through Self Service patch policies, which is fine. It leaves it to the user to patch when convenient. But for our lab and classroom computers, we don't want to rely on our students to push patches, so we use the automatic installation patch policies. They work fine, but unless the admin updates them during regularly scheduled down-time, the students are forced to quit any applications that need updates shortly after the patch policies are revised. I wanted to find a way to be able to update my patch policies, but not have them trigger until a certain time. Ironically, regular policies have an option to set up a scheduled time using the Client-Side Limitations. Sadly, patch policies do not have this option, so I set about finding a way to emulate this functionality. Here's how I created a maintenance window for my patch policies:
Next, I modified another, pre-existing script tp add computers to, and remove computers from a specified static group.
#!/bin/sh
##########################################################################################
#
# Static Group Membership - Computers
# Originally: Add Computer to Static Group
#
# This script uses the Jamf Pro API to get a Bearer Authentication Token and then adds a computer to a Static Group.
# In this case it could be the computer the script is run on if you uncomment the particular line. I actually set the computer name for testing to a specific name
# Adapted from https://derflounder.wordpress.com/2022/01/05/updated-script-for-obtaining-checking-and-renewing-bearer-tokens-for-the-classic-and-jamf-pro-apis/
# (not all functions in Rich's example are used as this is a very short access to the API)
# Also help from Steve Dagley in https://community.jamf.com/t5/jamf-pro/bearer-token-api-and-adding-computer-to-static-group/td-p/261269
# and https://community.jamf.com/t5/jamf-pro/script-to-add-computer-to-static-group/m-p/198738
# 2022-03-17 David London
#
# Modified for UNT by KClose
# Added parameter calls for Jamf Options.
# Updated static information to match UNT requirements.
#
# Updated 2023-09-27: Added a remove option as well and updated Parameters.
#
##########################################################################################
### VARIABLES ###
# Get the Static Group Name *REQUIRED*
[ -z "$4" ] && { echo "Static Group Name is required. Exiting."; exit 1; } || { GroupName=$4; }
# Get the Static Group ID *REQUIRED*
[ -z "$5" ] && { echo "Static Group ID is required. Exiting."; exit 1; } || { GroupID=$5; }
# Get Add/Remove. *REQUIRED*
if [[ $6 == [Aa] ]]; then
changeCommand="Add"
elif [[ $6 == [Rr] ]]; then
changeCommand="Remove"
else
echo "Change Command (A/R) is required. Exiting."
exit 1
fi
# Get the API Username. *REQUIRED*
[ -z "$7" ] && { echo "API Username is required. Exiting."; exit 1; } || { jamfpro_user=$7; }
# Get the API Password. *REQUIRED*
[ -z "$8" ] && { echo "API Password is required. Exiting."; exit 1; } || { jamfpro_password=$8; }
# Get the Jamf URL. *REQUIRED*
[ -z "$9" ] && { echo "Jamf URL is required. Exiting."; exit 1; } || { jamfpro_url=$9; }
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
# Find/Set the computer name.
ComputerName=$(/usr/sbin/scutil --get ComputerName)
# Explicitly set initial value for the api_token variable to null:
api_token=""
# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""
### FUNCTIONS ###
GetJamfProAPIToken() {
# This function uses Basic Authentication to get a new bearer token for API authentication.
# Use user account's username and password credentials with Basic Authorization to request a bearer token.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl -X POST --silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
fi
}
APITokenValidCheck() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
api_authentication_check=$(/usr/bin/curl --write-out %{http_code} --silent --output /dev/null "${jamfpro_url}/api/v1/auth" --request GET --header "Authorization: Bearer ${api_token}")
}
InvalidateToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, an API call is sent to invalidate the token.
authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" --silent --header "Authorization: Bearer ${api_token}" -X POST)
# Explicitly set value for the api_token variable to null.
api_token=""
fi
}
### MAIN SCRIPT ###
GetJamfProAPIToken
apiURL="JSSResource/computergroups/id/${GroupID}"
if [[ $changeCommand == "Add" ]]; then
echo "Adding computer to $GroupName."
apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_additions><computer><name>$ComputerName</name></computer></computer_additions></computer_group>"
elif [[ "$changeCommand" == "Remove" ]]; then
echo "Removing computer from $GroupName."
apiData="<computer_group><id>${GroupID}</id><name>${GroupName}</name><computer_deletions><computer><name>$ComputerName</name></computer></computer_deletions></computer_group>"
else
echo "Something went wrong - we shouldn't see this error."
exit 1
fi
curl -s \
--header "Authorization: Bearer ${api_token}" --header "Content-Type: text/xml" \
--url "${jamfpro_url}/${apiURL}" \
--data "${apiData}" \
--request PUT > /dev/null
InvalidateToken
exit 0
I do include an inventory update with my policies that add the computers to the static group, mostly to ensure that our records are up to date as to what software needs updating.
Expanding on from here, you could create several of the add/remove policies to schedule multiple windows for various groups of computers, or have static groups for specific applications. Once you have the pieces in place, the options for customization open up and give you a lot more control over your patch policies.
I hope that this helps someone because it certainly has given us more control over our patch management strategy and gives us the freedom to update the patch policies without concern that our users will be unexpectedly interrupted.