As part of Apple’s discussion of Declarative Device Management (DDM) at WWDC 2024, Apple announced that DDM management on macOS 15 Sequoia and later now included the ability to manage sets of tamper-resistant files which run tasks either on behalf of the logged-in user or which run with root privileges to provide services in the background. Running these tasks may involve any of the following:
In turn, these tools are triggered by the following LaunchD items:
Jamf Pro’s Blueprints supports deploying and managing these service background tasks via the Service background tasks component. Let’s see how this looks, using the following example:
Goal
Using a service background task to run a Jamf Pro inventory update each time the managed Mac starts up.
Tools used
For more details, please see below the jump.
To deploy this script and LaunchDaemon with Blueprints as a service background task, several things are needed. To start with, we need the following:
The script is named runjamfproinventoryupdate.sh and is available below:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/zsh –no-rcs | |
# This script runs the following actions: | |
# | |
# 1. Verifies that it can connect to the Jamf Pro server which manages this Mac. | |
# 2. Once verification is successful, an inventory update is sent to the Jamf Pro server | |
/usr/local/jamf/bin/jamf checkJSSConnection -retry 60 && /usr/local/jamf/bin/jamf recon | |
exit 0 |
The LaunchDaemon file is named com.github.runjamfproinventoryupdate.plist and is available below:
One thing to note is that the LaunchDaemon is running the runjamfproinventoryupdate.sh script at the following location:
/private/var/db/ManagedConfigurationFiles/BackgroundTaskServices/Services/com.github.runjamfproinventoryupdate/runjamfproinventoryupdate.sh
/private/var/db/ManagedConfigurationFiles/BackgroundTaskServices/Services is the tamper-resistant directory where macOS is storing the executable binaries, scripts, etc. it uses to run service background tasks. The LaunchAgents and LaunchDaemons are stored in separate tamper-resistant directories within /private/var/db/ManagedConfigurationFiles/BackgroundTaskServices:
Meanwhile, the runjamfproinventoryupdate.sh script is itself being stored inside a com.github.runjamfproinventoryupdate directory. This directory is named to match the label of the LaunchDaemon being deployed to run this service background task: com.github.runjamfproinventoryupdate
Once you have the script and the LaunchDaemon available, the following items are needed:
1. A zip file which contains both the directory and file structure of the script in question.
The script is stored in a directory named com.github.runjamfproinventoryupdate and the file is named runjamfproinventoryupdate.sh, so a zip file containing a directory named com.github.runjamfproinventoryupdate, with the script set to be executable and named runjamfproinventoryupdate.sh inside the com.github.runjamfproinventoryupdate directory, is needed for this.
For this example, we’ll name the zip file as com.github.runjamfproinventoryupdate.zip.
2. The SHA-256 hash of the zip file.
You can use the sha256sum command line tool to get the SHA-256 hash of the zip file, so using a command similar to the one shown below should provide that information:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sha256sum /path/to/filename_goes_here |
Assuming our SHA-256 hash is 48fa6c5e25590536970e71ae4bdf02c5153dbcb12ae5a3c2c7682ac94e065582, you should see output like this when you run the command above:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
username@ZWCM2JG74W ~ % sha256sum /Users/username/Desktop/com.github.runjamfproinventoryupdate.zip | |
48fa6c5e25590536970e71ae4bdf02c5153dbcb12ae5a3c2c7682ac94e065582 /Users/username/Desktop/com.github.runjamfproinventoryupdate.zip | |
username@ZWCM2JG74W ~ % |
3. The SHA-256 hash of the LaunchDaemon file
Assuming our SHA-256 hash is d913416e04862a8dfa5d58ba9ca045bc8527da7e40b9cdee608d4dcbd4104183, you should see output like this when you run the command above:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
username@ZWCM2JG74W ~ % sha256sum /Users/username/Desktop/com.github.runjamfproinventoryupdate.plist | |
d913416e04862a8dfa5d58ba9ca045bc8527da7e40b9cdee608d4dcbd4104183 /Users/username/Desktop/com.github.runjamfproinventoryupdate.plist | |
username@ZWCM2JG74W ~ % |
4. A place to download the zip and LaunchDaemon files from, which allows downloading without authentication.
For this example, I’ve set up an S3 bucket in Amazon Web Services named 75d831079efb4d02ada44eed4f8ae093 and uploaded the .zip and LaunchDaemon files. Once uploaded, the following files were set to be publicly accessible from that S3 bucket:
Once I have all the above available, I can set up a Blueprint in Jamf Pro to deploy the sudo configuration file as a Service configuration file.
1. Log into Jamf Pro.
2. Select Blueprints
3. Click the Open button for Service background tasks.
4. Give it a name when prompted. For this example, I’m using Run Jamf Pro Inventory at Startup.
5. Select a Jamf Pro smart or static group. For this example, I’m selecting a static group named Service Background Task Deployment Group.
6. Provide the necessary information to download the com.github.runjamfproinventoryupdate.zip and com.github.runjamfproinventoryupdate.plist files.
Task Type:
The name provided here must exactly match the label of the LaunchDaemon being deployed to run this service background task. In this case, this means that the name used here is the following:
com.github.runjamfproinventoryupdate
Description:
This is optional, you may fill this in or not as desired.
Executable asset:
This is the zip file with the runjamfproinventoryupdate.sh script inside. For this example, the following information is being used:
Launchd asset #1:
This is the com.github.runjamfproinventoryupdate.plist LaunchDaemon which is triggering the runjamfproinventoryupdate.sh script to run. For this example, the following information is being used:
Important Note
Wherever you’re downloading the LaunchDaemon file from, it’s important that the content headers being provided for that file match what is set for the content type in the service background task’s configuration. Otherwise, what will occur is that the service configuration task will not install on your managed Macs.
While testing this on my end, I initially could not get the configuration to work and couldn’t figure out why until I checked the headers I was getting from Amazon’s S3 service. Those headers looked similar to this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
username@computername ~ % curl -I https://75d831079efb4d02ada44eed4f8ae093.s3.us-east-1.amazonaws.com/com.github.runjamfproinventoryupdate.plist | |
HTTP/1.1 200 OK | |
x-amz-id-2: bXMExGDQxfF5mgBiaHklA8LPNrOBqpB10r1GPygtJgNmg6L7vDNFS9wNJ41/Z9H3U8SwXKqcoQk= | |
x-amz-request-id: QENYC85SCPY6EA41 | |
Date: Mon, 09 Jun 2025 15:14:33 GMT | |
Last-Modified: Mon, 09 Jun 2025 15:10:39 GMT | |
ETag: "1d92da3d92ac98519e1574b2fa56f5af" | |
x-amz-server-side-encryption: AES256 | |
Accept-Ranges: bytes | |
Content-Type: binary/octet-stream | |
Content-Length: 521 | |
Server: AmazonS3 | |
username@computername ~ % |
The Content-Type header was reporting the following:
Content-Type: binary/octet-stream
Fortunately, I was able to change the content type by using the AWS CLI tool to run the following command to force the content-header I wanted for the com.github.runjamfproinventoryupdate.plist LaunchDaemon file:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
aws s3 cp s3://S3_BUCKET_HERE/ s3://S3_BUCKET_HERE/ –exclude '*' –include 'com.github.runjamfproinventoryupdate.plist' –no-guess-mime-type –content-type="application/xml" –metadata-directive="REPLACE" –recursive |
Note: S3_BUCKET_HERE is a placeholder for the name of the actual S3 bucket being used.
Once that was done, the headers now looked like this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
username@computername ~ % curl -I https://75d831079efb4d02ada44eed4f8ae093.s3.us-east-1.amazonaws.com/com.github.runjamfproinventoryupdate.plist | |
HTTP/1.1 200 OK | |
x-amz-id-2: bXMExGDQxfF5mgBiaHklA8LPNrOBqpB10r1GPygtJgNmg6L7vDNFS9wNJ41/Z9H3U8SwXKqcoQk= | |
x-amz-request-id: QENYC85SCPY6EA41 | |
Date: Mon, 09 Jun 2025 16:43:13 GMT | |
Last-Modified: Mon, 09 Jun 2025 16:10:33 GMT | |
ETag: "1d92da3d92ac98519e1574b2fa56f5af" | |
x-amz-server-side-encryption: AES256 | |
Accept-Ranges: bytes | |
Content-Type: application/xml | |
Content-Length: 521 | |
Server: AmazonS3 | |
username@computername ~ % |
Now that the Content-Type header was reporting the following to match the application/xml content type set for my configuration, the configuration applied successfully:
Content-Type: application/xml
Returning to our example, once all the information has been entered and verified to be correct, click the Save button.
Once everything has been configured, Jamf Pro should inform you that you have undeployed changes. Click the Deploy button to deploy the changes to the Macs you want to manage.
Once deployed, the Blueprints screen in Jamf Pro should show the newly-created Run Jamf Pro Inventory at Startup blueprint as being deployed.
On your managed devices, you can verify that the new service background task configuration has been deployed by clicking on the enrollment profile, then scrolling to the bottom.
In the case of this example, you should see a Device Declarations section with a listing for Background Tasks: com.github.runjamfproinventoryupdate.
If you click on the Background Tasks: com.github.runjamfproinventoryupdate listing, it should report the following: