Posted on 03-08-2017 01:17 PM
I wrote this script to easily update package files used in policies when replacement packages have been uploaded to the JSS. Historically, we have almost a dozen JAMF PRO Sites where packages are updated centrally, but the policies are managed at the site-level. So updating policies when a new packages is available can be tedious. This script makes it a sinch.
#!/bin/bash
# This script was written to provide a consistent way to updated
# JAMF PRO policies that reference outdated package files. The
# idea is to search for a unique string that will identify all policies
# that deploy a common package. For example, your JAMF PRO server
# may have multiple policies deploying the latest Firefox to various
# sites, while others may target updates or Self-Service options.
# Once an updated package has been uploaded to the JAMF PRO server,
# you can run this script and optionally include a case-sensitive
# search string as a parameter. If no search string is provided, the
# script will prompt for one during runtime. The partial string should
# target package names that contain different versions of the same
# installer package.
# The script will then enumerate all package names that match the
# provided search string and then find all the policies that reference
# those packages. The latest matching package (highest id) will be the
# package used to update outdated packages (lesser matching package ids)
# contained within existing policies.
# So if your JAMF PRO server just received the latest installer package
# for Firefox, but the server has 3 references to an outdated Firefox
# package, the script will provide 3 separate prompts asking you if
# you want it to update each policy with the latest Firefox package.
# This script works in a similar fashion found within Casper Admin,
# where you can right-click on a package and select "Replace in all
# Configurations with..." and then you get to select which package to
# replace. The only difference is this script works against packages
# within policies instead of configurations.
# The script only updates the package ID and NAME within a policy.
# If a polcy has multiple packages, only the package that matches the
# search string will be affect. Similarly, if a package was set to
# "Cache" or "Install Cached", those actions will remain unchange.
# The script contains variables where you can add a JSS_USER name and
# JSS_PASSWORD to avoid prompting during runtime. The credentials you
# provide will need to have access to the APIs and to update policy records.
# Author: Andrew Thomson
# Date: 12-04-2016
# GitHub: https://github.com/thomsontown
#JSS_USER="" # Un-comment this line and add your login name if different from your os x login account.
#JSS_PASSWORD="" # Un-comment this line and add your password to prevent being prompted each time.
DEBUG=false
VERSION="1.02"
# load common source variables
if [ -f ~/.bash_source ]; then
source ~/.bash_source
fi
# if no jss url path specified
# then read from preference file
if [ -z $JSS_URL ]; then
# if no jss url found in preference file
# then display error
if ! JSS_URL=`/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url`; then
echo "ERROR: Unable to read default url."
exit $LINENO
fi
fi
# if no jss user name specified
# then use current user name
if [ -z $JSS_USER ]; then
JSS_USER=$USER
fi
# if no jss password specified
# then prompt user to enter
if [ -z $JSS_PASSWORD ]; then
echo "Please enter JSS password for account: $USER."
read -s JSS_PASSWORD
fi
# prompt for partial word search to match
# with package names
if [ -z $1 ]; then
echo "Please enter a partial package name to search:"
read "SEARCH"
else
SEARCH=$1
fi
# get ids of packages that match the partial word search
MATCHING_PACKAGES=(`/usr/bin/curl -X GET -H "Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/packages | /usr/bin/xpath "/packages/package//./name[contains(.,'$SEARCH')]/../id" 2> /dev/null | /usr/bin/awk -F'</?id>' '{for(i=2;i<=NF;i++) print $i}'`)
# sort array from oldest id to newest
MATCHING_PACKAGES=(`/usr/bin/printf "%s
" "${MATCHING_PACKAGES[@]}" | /usr/bin/sort -n`)
if $DEBUG; then echo "MATCHING PACKAGE IDS: ${MATCHING_PACKAGES[@]}"; fi
# two or more packages are required for the possibility
# of updating to the most recent package
if [ ${#MATCHING_PACKAGES[@]} -eq 1 ]; then
echo "ERROR: Only 1 package was found containing the string "$SEARCH"."
exit $LINENO
elif [ ${#MATCHING_PACKAGES[@]} -eq 0 ]; then
echo "ERROR: No packages were found containing the string "$SEARCH"."
exit $LINENO
fi
# display the count of packages that match
# the search string
echo "MATCHING PACKAGES: ${#MATCHING_PACKAGES[@]}"
# get newest matching package (last id in array)
NEWEST_PACKAGE_ID="${MATCHING_PACKAGES[${#MATCHING_PACKAGES[@]}-1]}"
if $DEBUG; then echo "NEWEST ID: $NEWEST_PACKAGE_ID"; fi
# get name of newest matching package
NEWEST_PACKAGE_NAME=`/usr/bin/curl -X GET -H "Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/packages/id/$NEWEST_PACKAGE_ID | /usr/bin/xpath "/package/name/text()" 2> /dev/null`
if [ -z "$NEWEST_PACKAGE_NAME" ]; then
echo "ERROR: Unable to retrieve package name."
exit $LINENO
fi
if $DEBUG; then echo "NEWEST NAME: $NEWEST_PACKAGE_NAME"; fi
# remove the newest package from array
# no need to search for updated policies
unset MATCHING_PACKAGES[${#MATCHING_PACKAGES[@]}-1]
# get ids of all policies to enumerate
ALL_POLICIES=(`/usr/bin/curl -X GET -H"Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/policies/createdBy/jss | /usr/bin/xpath "//id" 2> /dev/null | /usr/bin/awk -F'</?id>' '{for(i=2;i<=NF;i++) print $i}'`)
echo "TOTAL POLICIES: ${#ALL_POLICIES[@]}"
# set static count of policies to enumerate
POLICIES_COUNT=${#ALL_POLICIES[@]}
# enumerate policies
for INDEX in ${!ALL_POLICIES[@]}; do
# get ids of all packages found within current policy
POLICY_PACKAGES=(`/usr/bin/curl -X GET -H"Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/policies/id/${ALL_POLICIES[$INDEX]}/subset/packages | /usr/bin/xpath "//id" 2> /dev/null | /usr/bin/awk -F'</?id>' '{for(i=2;i<=NF;i++) print $i}'`)
# enumerate each package id internal to the current policy
for POLICY_PACKAGE in ${POLICY_PACKAGES[@]}; do
# enumerate each mactching pacakge id
for MATCHING_PACKAGE in ${MATCHING_PACKAGES[@]}; do
# if the current package id matches up against
# one of the package ids found in the original
# string query then set the FOUND flag to true
if [[ "$POLICY_PACKAGE" == "$MATCHING_PACKAGE" ]]; then
OUTDATED_POLICY_PACKAGE+=(${ALL_POLICIES[$INDEX]}:$MATCHING_PACKAGE)
fi
done
done
# display current policy number compared
# to the total count of policies
echo -ne "$(($INDEX+1))/$POLICIES_COUNT"
done
# display the count of policies that contain an outdated package id
echo -e "OUTDATED POLICIES: ${#OUTDATED_POLICY_PACKAGE[@]}"
# display debug info - policy id contraining outdated package : id of outdated pacakge in the policy
if $DEBUG; then echo "OUTDATED POLICY & PACKAGE: ${OUTDATED_POLICY_PACKAGE[@]}"; fi
# enumerate policies that may need updating
for POLICY_PACKAGE in ${OUTDATED_POLICY_PACKAGE[@]}; do
# display debug info - current policy id
if $DEBUG; then echo "CURRENT POLICY ID: ${POLICY_PACKAGE%:*}"; fi
# display debug info - current package id
if $DEBUG; then echo "CURRENT PACKAGE ID: ${POLICY_PACKAGE##*}"; fi
# get the name of the current policy
POLICY_NAME=`/usr/bin/curl -X GET -H "Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/policies/id/${POLICY_PACKAGE%:*} | /usr/bin/xpath "/policy/general/name/text()" 2> /dev/null`
if $DEBUG; then echo "CURRENT POLICY NAME: $POLICY_NAME"; fi
# get the name of the current package
CURRENT_PACKAGE_NAME=`/usr/bin/curl -X GET -H "Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/packages/id/${POLICY_PACKAGE##*:} | /usr/bin/xpath "/package/name/text()" 2> /dev/null`
if $DEBUG; then echo "CURRENT PACKAGE NAME: $CURRENT_PACKAGE_NAME"; fi
# prompt to update policy with new package
echo "Updated the policy "$POLICY_NAME" and replace "$CURRENT_PACKAGE_NAME" with "$NEWEST_PACKAGE_NAME"? [y/n]"
read UPDATE
# begin update process
if [ "$UPDATE" == "y" ]; then
# get xml for the current policy
POLICY_XML=`/usr/bin/curl -X GET -H "Accept: application/xml" -su ${JSS_USER}:${JSS_PASSWORD} ${JSS_URL%/}/JSSResource/policies/id/${POLICY_PACKAGE%:*}/subset/packages 2> /dev/null | /usr/bin/xmllint --format -`
# search and replace original pacakge id with the newest package id
POLICY_XML="${POLICY_XML/${POLICY_PACKAGE##*:}/$NEWEST_PACKAGE_ID}"
# search and replace original package name with the newest package name
POLICY_XML="${POLICY_XML/$CURRENT_PACKAGE_NAME/$NEWEST_PACKAGE_NAME}"
# update the policy now pointing to the new package
HTTP_CODE=`/usr/bin/curl -w "%{http_code}" -f -s -k -u "${JSS_USER}:${JSS_PASSWORD}" -d "${POLICY_XML}" -o /dev/null -H "Content-Type: application/xml" -X 'PUT' "${JSS_URL%/}/JSSResource/policies/id/${POLICY_PACKAGE%:*}"`
# display error message and return code on error
if [ "$HTTP_CODE" != "201" ]; then
echo "ERROR: Unable to update policy. [$HTTP_CODE]"
exit $LINENO
fi
else
echo "Not updating "$POLICY_NAME"."
fi
done
Posted on 03-09-2017 01:43 PM
Just a data point - doesn't seem to be working on my end. Haven't had time today to troubleshoot, but it prompts for partial package name, and then just tells me "ERROR: No packages were found containing the string "Firefox"" Tested with "Firefox", "Fire", "fire"", "Fire", etc. I know there are out-dated policies with Firefox in the name, FWIW. Same results with "Google", "google", "Google", etc. Nothing I tried resulted in it finding a package.
What would be REALLY nice, but I know a lot more work, is if it would scan the policies for any policy/package combos that were out of date and present them. I had a script sometime last year that would do that, but can't remember where I found it and have long-since lost it during a rebuild.
Posted on 03-15-2017 03:11 PM
Thanks @Taylor.Armstrong .
I wonder if you enable debugging, you might get more information about why it is not working for you? Just change the variable to
DEBUG=true
and you should see the additional information. If that doesn't point you in the right direction. I can look into other possibilities.
I agree having a selection list to choose from would be easier. I have considered it, but I thought this would be a good start for now. I appreciate the feedback. --Andrew
Posted on 01-06-2021 08:36 AM
I just tried this - I had about 35 policies where I had to update the version of printer driver included, and this worked like a charm. Thank you, Andrew!