Scope policy to multiple Macs based on status of one Mac

May
Contributor III

Hi all,

Is there any way to enable and disable a policy for a group of Macs but have that based upon the status of just one Mac ?

  • If the one Mac has any Apple updates available then the policy for the group of Macs is disabled/not scoped to them or
  • If there are no Apple updates available for the one Mac then the policy for the group of Macs becomes enabled/scoped to them

If i use a Smart group to check if updates are on the one Mac then only that Mac would be scoped for the policy or not, can anyone suggest a different approach to make this work ?

1 ACCEPTED SOLUTION

mm2270
Legendary Contributor III

Hi @May Just to be sure I wasn't pointing you in the wrong direction with this, I actually put together a working script for this. In the below, you would only need to do the following:
Either hardocode in your API account that has both read and write privileges for policies, or use $4 and $5 passed to the script for the account name and password, respectively. Of course, the latter option is more secure than having those credentials hardcoded into the script, but that all depends on who in your organization has access to look at script and policy details. If it needs to be more secure than that, you'd need to use Jamf's encrypted script parameters workflow, which you can find here.
Secondly, you can either hardcode in or pass the policy ID in question that you want to flip the state on to $6. This one should be fine to hardcode in as it's just the policy ID, not credentials.

In the script, I tried to add as many comments to things where the stuff is happening. I tested this on a junk policy by having my own machine act as the gatekeeper to it. If my machine showed available Software Updates, it would disable the policy (verified in the JSS). If I commented out the section on grabbing updates and hardcoded in a 0 for available updates, it would enable the policy (also verified in the JSS). If the policy is already in the state you want it to be in (enabled/disabled) it would do nothing since no action is needed.

Let me know if you have any questions on this, or have trouble getting it to work. It was tested against Jamf Pro 9.99.0.

#!/bin/bash

## change-policy-state

## You can choose to hardcode the API credentials below, or pass them as parameters $4 (username) and $5 (password)
APIUSER=""
APIPASS=""

## Hardcode the policy JSS ID here, or pass it to the script at runtime as $6
POLICYID=""

if [[ -z "$APIUSER" ]] && [[ ! -z "$4" ]]; then
    APIUSER="$4"
elif [ ! -z "$APIUSER" ]; then
    APIUSER="$APIUSER"
fi


if [[ -z "$APIPASS" ]] && [[ ! -z "$5" ]]; then
    APIPASS="$5"
elif [ ! -z "$APIPASS" ]; then
    APIPASS="$APIPASS"
fi


if [[ -z "$POLICYID" ]] && [[ ! -z "$6" ]]; then
    POLICYID="$6"
elif [ ! -z "$POLICYID" ]; then
    POLICYID="$POLICYID"
fi

## Check to make sure all 3 required values are not blank
if [[ -z "$APIUSER" ]] || [[ -z "$APIPASS" ]] || [[ -z "$POLICYID" ]]; then
    echo "API credentials or the JSS policy ID were not passed to this script. Pass the required values to the script and try again."
    exit 1
fi

## Get the JSS URL from plist
JSSURL=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)

function flipState ()
{

## Adjust the policy xml file by changing the state (enabled/disabled) to the desired state
sed -i "" "s|<enabled>$CURRENT_STATE</enabled>|<enabled>$DESIRED_STATE</enabled>|" /tmp/${POLICYID}.xml

## Check to see if the sed operation was successful, before continuing
if [ "$?" == 0 ]; then
    echo "State changed in local xml file. Attempting to upload"
    ## Upload the modified policy xml to flip the state of the policy
    curl -sfku "${APIUSER}:${APIPASS}" "${JSSURL}JSSResource/policies/id/${POLICYID}" -X PUT -T /tmp/${POLICYID}.xml 2>&1 > /dev/null
    ## Check the exit code for the upload step
    status=$?
    if [ "$status" == 0 ]; then
        echo "Policy state successfully changed to $DESIRED_STATE"
        rm /tmp/${POLICYID}.xml
        exit 0
    else
        echo "Error: Policy state could not be changed"
        rm /tmp/${POLICYID}.xml
        exit 1
    fi
fi

}


function getPolicyData ()
{

## Pull down entire policy xml into /tmp
curl -H "Accept: text/xml" -sfku "${APIUSER}:${APIPASS}" "${JSSURL}JSSResource/policies/id/${POLICYID}" | xmllint --format - > /tmp/${POLICYID}.xml

## Get the current enabled/disabled state of the policy
CURRENT_STATE=$(xpath '/policy/general/enabled/text()' < /tmp/${POLICYID}.xml)

echo "Current policy state: $CURRENT_STATE"

## See if the current policy state is the same as the desired state we received from the SWU check stage
if [ "$CURRENT_STATE" == "$DESIRED_STATE" ]; then
    ## If they match, there is nothing to change, so no need to modify the policy. Just exit
    echo "Policy already in desired state. Nothing to do"
    exit 0
else
    ## If they do not match, we need to modify the xml and re-upload it
    echo "Current policy state and desired state do not match. Changing state for policy"
    ## Run the flipState function
    flipState
fi

}

function checkForSWUs ()
{

echo "Getting available software updates for this system"

## Get a count of available updates on the target/base system
SWU_AVAIL_COUNT=$(/usr/sbin/softwareupdate -l | grep "*" | awk 'END{print NR}')

## The below sets SWU_AVAIL_COUNT to 0 for testing purposes only.
## Should remain commented out when used in production
#SWU_AVAIL_COUNT=0

## If the count we get is above zero, there are available updates. We need to set the policy to off, if needed
if [ "$SWU_AVAIL_COUNT" -gt 0 ]; then
    echo "Current available software updates: $SWU_AVAIL_COUNT"
    echo "Setting desired policy state to: false"
    DESIRED_STATE="false"
    ## Run the function to get the policy details
    getPolicyData
else
    ## If the count we get is not above 0, there are no available updates. We want to set the policy state to on, if needed
    echo "Current available software updates: 0"
    echo "Setting desired policy state to: true"
    DESIRED_STATE="true"
    ## Run the function to get the policy details
    getPolicyData
fi

}

## Run the function above to check for software updates
checkForSWUs

View solution in original post

8 REPLIES 8

jduvalmtb
Contributor

You could have a Policy A that runs based on your Smart Group if checking if an App's installed in it.

Policy B then has no checkin intervals, but does whatever you need to. Scope it to all computers since it has no way of running on its own.

Back in Policy A, either create a script or use Files and Processes --> Execute Command to run Policy B. "jamf policy -id ###[Policy B]"

mm2270
Legendary Contributor III

I'm curious about this. Can you elaborate a little more on the need here, as in what is the end goal? Also, will the Mac used as the basis for this policy enablement/disablement always be this same Mac, or would you need some flexibility in choosing which machine was used for this purpose?

May
Contributor III

Thanks @jduvalmtb

I like that idea! unfortunately for my situation i don't want the policy to run automatically as it's in Self Service, i'd like to make it so that all users either have access to the Self Service policy or do not, dependent on the status of the one Mac.

May
Contributor III

Hi @mm2270

The main goal is to see if i we can manage Apple updates directly from Apple and not rely on our own server, i'm trying to minimize the support required to look after an update server. At the moment we have an in house update server running on a mini and it's ticking over nicely but i have a series of policy bells and whistles to make sure the users are on VPN before the updates run, plus the limitation of the server OS is more work each new release, i started to build a Netsus in Azure then began to realize it will take specialized support + the yearly Azure fees, if i can control which updates get installed directly from Apple then that would be the simplest solution to maintain.

I've got a script together that will only install what's listed in the Script's Parameters $4 to $11 and that now works well, the concern i have is once updates have gone through or UAT phase then get released to all users in Production what happens if a new untested update is released (in the parameters i'm not using exact update names, ie just 'Safari' or 'Security Update' so i don't have to go in each week and change the name)

So my thinking was if i have one Mac that will get all the updates along with UAT, then once we're happy we release those updates to production via Self Service for restart updates and non restart updates are installed automatically. Then if the new updates become available on the main Mac it will disable the access to Self Service and the automatic policies for all Production users, until they've gone through UAT phase.

thank for listening!

mm2270
Legendary Contributor III

@May Ok, I see. I'm thinking there may be an overall better way to handle this scenario, but at the moment, it's not coming to me. If I can think of an easier process for this, I'll post back.

For now, the reason I asked if the Mac you're using as your test base will always be the same is that, you can probably scope a policy just to that machine that runs at whatever frequency you want. The policy can run a script that checks to see if the Mac is seeing any available updates. If it does, use the API to pull down the policy that you have scoped to all other Macs that you described above. and have it set the policy to be disabled and then update the policy back in the JSS. Meaning, the scope for the policy that uses your $ thru $11 parameters can stay the same, but the Mac that acts as the gatekeeper for it, can have it's own policy that causes it to either disable or enable the aforementioned policy as needed.

Does that make sense? If you need more of an explanation, or a working example, I can probably post an example or framework script that you can use that would help get you on the right track.

May
Contributor III

Hi @mm2270

I think i understand it, sounds like it would fit the requirements perfectly.
Are you able to point me towards a good example re working with the JSS API, and if you do have a snippet of script that'll point me in the right direction re enabling/disabling a policy vis the API that would be most appreciated!

thanks!

EDIT
i've just found previous info you posted re working with the API here, https://www.jamf.com/jamf-nation/discussions/18454/jamf-api-access
that should get me started, if you do have an example of enabling/disabling via script that would be great

mm2270
Legendary Contributor III

Hi @May Just to be sure I wasn't pointing you in the wrong direction with this, I actually put together a working script for this. In the below, you would only need to do the following:
Either hardocode in your API account that has both read and write privileges for policies, or use $4 and $5 passed to the script for the account name and password, respectively. Of course, the latter option is more secure than having those credentials hardcoded into the script, but that all depends on who in your organization has access to look at script and policy details. If it needs to be more secure than that, you'd need to use Jamf's encrypted script parameters workflow, which you can find here.
Secondly, you can either hardcode in or pass the policy ID in question that you want to flip the state on to $6. This one should be fine to hardcode in as it's just the policy ID, not credentials.

In the script, I tried to add as many comments to things where the stuff is happening. I tested this on a junk policy by having my own machine act as the gatekeeper to it. If my machine showed available Software Updates, it would disable the policy (verified in the JSS). If I commented out the section on grabbing updates and hardcoded in a 0 for available updates, it would enable the policy (also verified in the JSS). If the policy is already in the state you want it to be in (enabled/disabled) it would do nothing since no action is needed.

Let me know if you have any questions on this, or have trouble getting it to work. It was tested against Jamf Pro 9.99.0.

#!/bin/bash

## change-policy-state

## You can choose to hardcode the API credentials below, or pass them as parameters $4 (username) and $5 (password)
APIUSER=""
APIPASS=""

## Hardcode the policy JSS ID here, or pass it to the script at runtime as $6
POLICYID=""

if [[ -z "$APIUSER" ]] && [[ ! -z "$4" ]]; then
    APIUSER="$4"
elif [ ! -z "$APIUSER" ]; then
    APIUSER="$APIUSER"
fi


if [[ -z "$APIPASS" ]] && [[ ! -z "$5" ]]; then
    APIPASS="$5"
elif [ ! -z "$APIPASS" ]; then
    APIPASS="$APIPASS"
fi


if [[ -z "$POLICYID" ]] && [[ ! -z "$6" ]]; then
    POLICYID="$6"
elif [ ! -z "$POLICYID" ]; then
    POLICYID="$POLICYID"
fi

## Check to make sure all 3 required values are not blank
if [[ -z "$APIUSER" ]] || [[ -z "$APIPASS" ]] || [[ -z "$POLICYID" ]]; then
    echo "API credentials or the JSS policy ID were not passed to this script. Pass the required values to the script and try again."
    exit 1
fi

## Get the JSS URL from plist
JSSURL=$(defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url)

function flipState ()
{

## Adjust the policy xml file by changing the state (enabled/disabled) to the desired state
sed -i "" "s|<enabled>$CURRENT_STATE</enabled>|<enabled>$DESIRED_STATE</enabled>|" /tmp/${POLICYID}.xml

## Check to see if the sed operation was successful, before continuing
if [ "$?" == 0 ]; then
    echo "State changed in local xml file. Attempting to upload"
    ## Upload the modified policy xml to flip the state of the policy
    curl -sfku "${APIUSER}:${APIPASS}" "${JSSURL}JSSResource/policies/id/${POLICYID}" -X PUT -T /tmp/${POLICYID}.xml 2>&1 > /dev/null
    ## Check the exit code for the upload step
    status=$?
    if [ "$status" == 0 ]; then
        echo "Policy state successfully changed to $DESIRED_STATE"
        rm /tmp/${POLICYID}.xml
        exit 0
    else
        echo "Error: Policy state could not be changed"
        rm /tmp/${POLICYID}.xml
        exit 1
    fi
fi

}


function getPolicyData ()
{

## Pull down entire policy xml into /tmp
curl -H "Accept: text/xml" -sfku "${APIUSER}:${APIPASS}" "${JSSURL}JSSResource/policies/id/${POLICYID}" | xmllint --format - > /tmp/${POLICYID}.xml

## Get the current enabled/disabled state of the policy
CURRENT_STATE=$(xpath '/policy/general/enabled/text()' < /tmp/${POLICYID}.xml)

echo "Current policy state: $CURRENT_STATE"

## See if the current policy state is the same as the desired state we received from the SWU check stage
if [ "$CURRENT_STATE" == "$DESIRED_STATE" ]; then
    ## If they match, there is nothing to change, so no need to modify the policy. Just exit
    echo "Policy already in desired state. Nothing to do"
    exit 0
else
    ## If they do not match, we need to modify the xml and re-upload it
    echo "Current policy state and desired state do not match. Changing state for policy"
    ## Run the flipState function
    flipState
fi

}

function checkForSWUs ()
{

echo "Getting available software updates for this system"

## Get a count of available updates on the target/base system
SWU_AVAIL_COUNT=$(/usr/sbin/softwareupdate -l | grep "*" | awk 'END{print NR}')

## The below sets SWU_AVAIL_COUNT to 0 for testing purposes only.
## Should remain commented out when used in production
#SWU_AVAIL_COUNT=0

## If the count we get is above zero, there are available updates. We need to set the policy to off, if needed
if [ "$SWU_AVAIL_COUNT" -gt 0 ]; then
    echo "Current available software updates: $SWU_AVAIL_COUNT"
    echo "Setting desired policy state to: false"
    DESIRED_STATE="false"
    ## Run the function to get the policy details
    getPolicyData
else
    ## If the count we get is not above 0, there are no available updates. We want to set the policy state to on, if needed
    echo "Current available software updates: 0"
    echo "Setting desired policy state to: true"
    DESIRED_STATE="true"
    ## Run the function to get the policy details
    getPolicyData
fi

}

## Run the function above to check for software updates
checkForSWUs

May
Contributor III

Hi @mm2270

Firstly, thank you so much for taking the time to read what i was trying to achieve, and then put this script together!

it does exactly as needed and flips the policy on or off dependent on the result from one Mac, i'm very excited about learning what else can be done via the API, what a great Monday!

(i now need to learn exactly how functions work!)

thanks again!
Andy