Archive for April, 2020

Using Postman for Jamf Pro API Testing – Part 2: Creating And Updating Policies

April 22, 2020 Leave a comment

In my previous post I walked through the basic steps necessary to get Postman ready to do API requests to your Jamf Pro server. In this post we’ll get into using Postman and the API to create and update policies, including saving our API requests so we can use them again.

Before we get started I feel like I need to re-iterate my disclaimer:

Prod is NOT test. Be careful with your updates, deletes, and creates. I highly suggest practicing in a dev environment first if you can. If you do not have a dev environment, then use test items like test policies and test computers. The API is a powerful tool.

Ok, now that the disclaimer is out of the way, let’s get to it!

Create a New Policy

You can create a policy with as little information as the name of the policy, or with as much information as the packages to install, scripts to run, and just about anything else you can do in the GUI. One thing I know you cannot do is remove the Restart options from a policy you create. Even if you do not set anything in the Restart section of XML, the policy will still create with that “tab” added to the policy.

The Jamf Pro API takes XML code blocks to create pieces. If we were to open the POST request for creating a policy (title: “Creates a new policy by ID”) we would see a basic representation of some of the information we can send using this request to create a policy.

Let’s say we wanted to just create a shell of a policy that had a name, a trigger, and a frequency. That XML would look something like this.

		<name>Our Cool Policy</name>

We would put that block of XML into the Body section of our API request.

If we were to run that by clicking the Send button we would now have a policy named “Our Cool Policy” that was not enabled with a trigger of Recurring Check-in and a frequency of Ongoing. Pretty cool. The Jamf Pro API returns the ID of our new policy and Postman displays that for us.

We can go open up our JPS web interface to look up that policy, but why not just use Postman to look at it. Go find the GET request named “Finds policies by ID” and enter that new policy ID into the “id” Key under Path Variables. Just replace {{id}} with the ID number returned after we ran our POST request.


Now if we run that GET request we’ll get a bunch of XML back showing our new policy, even though we only entered a few items.

Look at all that info! Now, if you’re more of a visual person and need to see it in the web interface, by all means go pull up the policy. We’ll wait until you come back.

Sidebar: When you close out the GET request where we wrote over the {{id}} variable, do not save the request. We want the variable to be there for later use. I’ll show you later in this post a better way to put the ID value in.

That was all neat and all, but what about installing a package as part of the policy? For us to do that we would need a little bit of information about the package. We will need to add the ID of the package and we will need to add the action we want to take (install or cache).

You can grab the package information straight from the web interface, from Jamf Admin, or even better, right from the API here in Postman. This time I’ll let you figure it out on your own. Go find the GET request to list packages and then locate the ID of the package you want to install in our policy.

For my demo I’ll be using a policy ID of 1265 and an action of Install. Here’s what the XML looks like.

	<name>Our Cool Policy</name>

If we replace our XML in our POST request and run this in Postman, we will get error because there is already a policy named “Our Cool Policy”. We’re going to pretend like we didn’t already create that policy since we’re talking about creating new policies. I’ll discuss updating an existing policy later. (Of course we could just use Postman to delete the test policy we created above)

After running the POST we now have a policy in Jamf Pro that is disabled but has a package attached to it.

There you have it, we’ve created our very first policy that actually does something. Of course we would still need to put a scope on the policy and enable it, so why don’t we do that now.

Updating A Policy

Let’s continue building on the policy we were creating above. The ultimate goal of this policy is to be an installer policy that can be used on any computer and triggered via a Custom Trigger. This is one of the methods many Mac admins use for doing installs: have an install policy and call that install policy from other policies or scripts. Go watch Bill Smith’s (@meck or Talking Moose) JNUC presentation from 2017 titled “Moving Beyond “Once Per Computer” Workflows” for more info behind this.

We already have the framework of our policy created we just need to make a few changes and additions. We’ll need to do the following to make this into a true installer policy:

  1. Change the trigger to a custom trigger
  2. Add an inventory update
  3. Add a scope
  4. Add a category

We will want to use the PUT request named “Updates an existing policy by ID” to do these updates. Once again, we only need to put in the XML for items we need to change or add to the policy.


Those are all the XML keys that we’ll need to update in our policy (category ID 14 is the cateogry where I put installers). We’re going to drop that XML into the body section of our API request and we’re going to set the ID variable again like we did above. Just put the ID number of our cool policy.

Change ID variable

After running that API request, our installer policy should be all set and ready to be enabled.

Collecting Our Requests

Now that we’ve used a couple of different API requests, let’s put those into a collection that we can re-use later when we need to do the same thing. Let’s create a collection of requests for manipulating our policies.

Click on the down arrow next to the Save button next to the Send button and choose Save As.

In the window that opens, go ahead and give your request a descriptive name, you can even give it a description. Since this is the first request we’re saving, we’ll want to create a collection for it. Click on the “+Create Collection” link towards the bottom of the window. Give your collection a good name and click the orange check mark.

Finally, click the big orange Save button at the bottom.

Continue saving all of the requests that you want to use when creating these installer policies, or whatever type of policy. Just remember, if you have changed the ID key from {{id}} to a number, change it back to {{id}} before saving. You’ll see why in a minute.

Now that we have all of these requests in one location, it makes it easier for us in the future when we need to create or update the policy. If we set all of the XML in the POST, then in the future we just have to edit those values in the XML for the new policy.

One more thing you’ll need to do. The Classic API collection from Jamf comes with the authentication variables username and password already configured. We’ll need to do that to our new Installer Policies collection.

Click on the elipses, the three dots, to the right of our collection name and choose Edit.

In the window that opens, click on the Authorization tab. Now click on the drop down box on the left and choose Basic Auth. Fill in the username and password boxes with the variables that we need. Once you have done that click on the orange Update button at the bottom.

In a future post we’ll go over a method for converting those values into variables that we can feed with a JSON file or CSV file so that we do not have to edit our request.

Using Variables

I told you I’d show you how to use variables, like the {{id}} variable we’ve already seen. We’re going to use the “Pre-request Script” section of our request window to fill those variables. We use this method rather than directly editing the “Path Variables” on the Param tab so that we can re-use our API request in a more automated fashion later.

On the “Pre-request Script” tab you’ll notice some snippets on the far right. Click on the “Set an environment variable” snippet to add the code to the window.

I’ll bet you can figure out what we’re going to do, right? Yep, go ahead and replace “variable_key” with the variable “id”. Now anytime you want to do an PUT (update) or a GET (retrieve) for a specific policy, you can simply put the policy ID in the “variable_value” field.

For example, if we wanted to update (PUT) our cool installer policy, id = 3338, we would simply add that ID.

When we send that request, the ID variable on the Params tab will get switched out with the ID we placed here in our Pre-request script. That will in turn replace the “:id” in the URL of our request. Clear as mud, right?

Wrap Up

We covered a lot here, a lot more than I expected. Postman is a pretty powerful tool for learning about the Jamf Pro API and how to use it to manipulate your environment. I would suggest doing some Google searches and watching some YouTube videos on how to use Postman to get more advanced.

In our next post we’ll talk about creating and editing computer groups.

Until next time!

Categories: Jamf Pro, Tech Tags: , ,

Using Postman for Jamf Pro API Testing – Part 1: The Basics

April 22, 2020 1 comment

I have dabled with Postman for years but I was never able to grok the full power of the app until recently. As our environment has grown (16,000+ Mac endpoints under management) we have noticed a slow down in the response time of the Jamf Pro web interface. It’s the nature of having several hundred Smart Groups, a few thousand policies, and all of the other things that go along with a large environment.

That’s where Postman comes in. Postman allows us to make API calls to the Jamf Pro server in a nice GUI environment and without needing to know a lot about creating those calls. But the true power is in the ability to throw collections of information at an API and have Postman run through that information using a Runner. We’ll get into runners and other features in another post. This post is really about setting up Postman for use with Jamf Pro.

On that note, Postman is a powerful tool and as such this post will not be able to cover everything. I suggest using the Postman Learning Center, Google, and YouTube to get more information. There’s only so much I can cover.

Setup the Environment

First, go grab a copy of Postman if you do not already have it and after that, go grab Jamf’s collection of API calls for Postman.

In Postman we will want to create an environment to store variables. This will be things like the user, password, and URL for connecting to your Jamf environment. These variables are shown in the interface as double curly braces surrounding a variable name, like {{somevariable}}. The collection of API calls from Jamf uses {{url}} for the JSS URL variable, {{username}} for the user, and you guessed it, {{password}} for the password.

Click on the gear icon in the upper right corner of the screen to add an environment to Postman.

In the window that opens up you’ll want to click the big orange Add button in the bottom right corner.

Give your environment a name, something descriptive like “Production Server” or “Dev Server” or “My L33T JSS”. Next you’ll want to fill in the variable names and values you want them set to.

Once you are satisified, go ahead and click Add to close this window, and then use the X to close the next window. That should put you back into the main Postman window, most likely at the launchpad.

Adding Collections

Now that we have our environment variables set, we’ll want to add in the Jamf Collection of API calls. Click on the Import button at the top left of the window.

Now drag and drop the JSON file you downloaded from the Jamf GitHub repo into the window. You should now have a new collections folder under the Collections tab on the left side named “Classic API”.

That’s it! We’re now setup to use Postman and the Jamf Classic API Collection to make API calls to our Jamf Pro server.

Our First API Call

Now that we’re all setup, let’s try one very basic API call. Let’s grab a list of all of our policies. You can navigate through the collection folder until you find the Policies endpoint, or you can use the Filter field above the collections list. I’m going to type policies into that filter so I can get to the “Find all policies” API call.

Choose your environment in the upper right of the window and then click the Send button. You should get back a list of all of the policies in your JPS.

What’s Next

Now that we have Postman setup we can do a lot of things with the API. We can list things, read values of objects, we can update objects, we can even create new objects via the API. Here’s the obligitory warning:

Prod is NOT test. Be careful with your updates, deletes, and creates. I highly suggest practicing in a dev environment first if you can. If you do not have a dev environment, then use test items like test policies and test computers. The API is a powerful tool.

Next up in the series of posts on Postman we’ll cover how to use enviorment variables in our API calls to fill in things like the ID number of a computer, how to use Postman to create, update, and delete objects, and we’ll cover some more advanced topics like using “Runners” to run multiple iterations of a call or even run multiple API calls in series.

Until next time, have some fun with Postman, but be careful!

Categories: Jamf Pro Tags: ,

Uploading Logs to Jamf Pro with the API

April 15, 2020 Leave a comment

Something I learned early on while doing a large scale deployment is that it is really difficult to get logs off of computers when you either don’t have network access, or you have 10,000 Macs to get logs from. There have been plenty of discussions on Jamf Nation about logging for scripts or gathering logs from users (there has to be a better way than waiting on users to send the logs in).

Somewhere on Jamf Nation I found a method for using the Classic API to upload files to the computer record. This was great because now I could utilize any number of methods for logging data on the Mac and then uploading that to the computer record. This would put the log file close at hand so I could troubleshoot an issue without having to SSH into a machine or ask the user to send it to me.

Once you have figured out how you want to generate logs in your scripts (I’m partial to Dan Snelson’s method here), you’ll want to create a user that has access to upload files to the Jamf Pro Server. If you are a fan of encrypting the credentials you can go grab Jamf’s Encrypted Script Parameters scripts off of GitHub and use that code to scramble the creds. But, if you’re like many, simply creating a use specific user with privileges to do only what that account needs to do (upload files in this case) is sufficient security.

Jamf Pro Setup

So first step is to setup your user. Go into System Settings and then into Jamf Pro User Accounts & Groups on your JPS. Click the New button and choose to create a new Standard Account. Fill in the particulars like Username and Password, and set the Privilege Set to “Custom”:

Now go to the Privileges tab and enable Create, Read, and Update for File Attachments.

Now that we have our user created we can add the necessary API calls to our scripts to upload log files.

Add To Your Scripts

In any script that you wish to log output for, create a logging mechanism that saves to a local file on the system. We will not get into the specifics of which method is better than another. Instead I will show you a down and dirty method for capturing all output to a log file.

Once you have determined where to save the log file, you’ll want to make sure the path is available (unless you are putting this in a standard location like /var/log or even /tmp), and you’ll want to redirect output to the log or echo into the log. The following code will create a log file at /tmp/mylog/ and set the shell to output all commands using the UNIX ‘set’ command.

mylog=$mylogpath/myawesomlog-$(date +%Y%m%d-%H%M).log

[[ ! -d ${mylogpath} ]]; mkdir -p $mylogpath

set -xv; exec 1> $mylog 2>&1

That last line is the magic. That line enables shell debugging with verbosity and re-directs ‘stdout’ (1) and ‘stderr’ (2) into our log file. Anything that our script does below this part will be output to our log file.

Upload our Log

To upload our log file we need to know the JSS ID of the computer. This can be found in the JPS by looking at the URL of a computer or by looking for the “Jamf Pro Computer ID” on the General tab of the comptuer record. We’re going to use the computer serial number and the API to determine the ID.

We first grab the serial number from System Profiler:

serial=$(system_profiler SPHardwareDataType | awk '/Serial\ Number\ \(system\)/ {print $NF}');
view raw gistfile1.txt hosted with ❤ by GitHub

Now we make an API call to get the ID and use ‘xpath’ to sort it all out:

JSS_ID=$(curl -H "Accept: text/xml" -sfku "${jss_user}:${jss_pass}" "${jss_url}/JSSResource/computers/serialnumber/${serial}/subset/general" | xpath /computer/general/id[1] | awk -F'>|<' '{print $3}')
view raw gistfile1.txt hosted with ❤ by GitHub

Now that we have our ID we use a ‘curl’ call to the API to upload the log file:

curl -sku $apiUser:$apiPass $jpsURL/JSSResource/fileuploads/computers/id/$JSS_ID -F name=@${mylog} -X POST
view raw gistfile1.txt hosted with ❤ by GitHub

And just like that, we have a log file attached to the computer record for troubleshooting.

Computer Record

Where is this stored on the computer record? Well, it’s stored down towards the bottom of the tabs, just past the Printers tab on a tab called “Attachments”.

Screen Shot 2020-04-15 at 10.43.11 PM


That’s it, that’s all that has to be done to get a log file attached to your computer records. You can use this in any of your shell scripts to upload log files. You could even write a Self Service policy that users could run that would ship log files of your choice up to the computer record.

I hope you get something out of this and are able to see the power of this feature.

mylog=$mylogpath/myawesomlog-$(date +%Y%m%d-%H%M).log
[[ ! -d ${mylogpath} ]]; mkdir -p $mylogpath
set -xv; exec 1> $mylog 2>&1
... do some stuff ...
# Upload our log using API
# get computer serial number to lookup the JSS ID of the computer
serial=$(system_profiler SPHardwareDataType | awk '/Serial\ Number\ \(system\)/ {print $NF}');
# get ID of computer
JSS_ID=$(curl -H "Accept: text/xml" -sfku "${jss_user}:${jss_pass}" "${jss_url}/JSSResource/computers/serialnumber/${serial}/subset/general" | xpath /computer/general/id[1] | awk -F'>|<' '{print $3}')
# upload the log to the comptuer record
curl -sku $apiUser:$apiPass $jpsURL/JSSResource/fileuploads/computers/id/$JSS_ID -F name=@${mylog} -X POST
view raw gistfile1.txt hosted with ❤ by GitHub

Categories: Jamf Pro Tags: , ,