Archive
Postman Advanced – Passing Data
In my previous posts about Postman I showed you how to setup Postman for working with Jamf Pro, how to create and update policies, how to gather our queries into collections, and much more. In this post I’m going to expand a little on our use of the Runner functionality which I covered in Part 4 and Part 5.
Often times we want to perform an action on more than just one object. Maybe we want update a list of devices with PO Number or some other data. Sure, we can run a search, export the data as a CSV, and then use that CSV to feed a runner, but what if we could grab a search in Postman and parse out the devices we want to update? I went down this rabbit trail today and wanted to share the results with you.
Our use case, for this post, is to update the PO Number on a group of computers that we will gather using an Advanced Search in Jamf Pro. To do this we will need an Advanced Search in Jamf Pro to capture our devices and in Postman we will use two API endpoints: “Find computer search by ID” and “Update computer by SN”.
Pre-Request Scripts & Tests
Postman provides two features that allow us to utilize JavaScript to manipulate data, either before or after a request: Pre-Request Scripts and Tests. A Pre-Request Script can allow us to set variables before the running of a request. We used a pre-request script in Part 2 when we briefly talked about using variables. In that instance we used the command pm.environment.set
to set the “id” variable to the ID of a policy. We did it this way so that we did not change the Params tab of the request and hardcode the ID variable so we can use the request in a runner later.
Similar to the Pre-Request Scritps tab, the Tests tab allows you to utlize JavaScript to perform actions after the request has run. This could be testing the response code that is returned to make sure the request ran, or it can be storing your results for use in the next request in a Runner (which is what we will be doing).
Investigate the Data
Before we get too far down the rabbit trail, we will need to understand where the serial numbers are in the response body of an Advanced Search, and how far they are nested. To do this we will need the ID number of our Advanced Search (ID can be found in the URL of your advanced search, like: https://<jpsURL>/advancedComputerSearches.html?id=6). With the ID number in hand, we can use the “Find Computer Search by ID” API endpoint ({{url}}/JSSResource/advancedcomputersearches/id/:id) to pull back the XML of that search. In our demo case the XML from our search for devices that have a model identifier like “macmini” results in:
<?xml version="1.0" encoding="UTF-8"?>
<advanced_computer_search>
<id>6</id>
<name>Update PO Numbers</name>
<view_as>Standard Web Page</view_as>
<sort_1/>
<sort_2/>
<sort_3/>
<criteria>
<size>1</size>
<criterion>
<name>Model Identifier</name>
<priority>0</priority>
<and_or>and</and_or>
<search_type>like</search_type>
<value>Macmini</value>
<opening_paren>false</opening_paren>
<closing_paren>false</closing_paren>
</criterion>
</criteria>
<display_fields>
<size>4</size>
<display_field>
<name>Computer Name</name>
</display_field>
<display_field>
<name>Model Identifier</name>
</display_field>
<display_field>
<name>PO Number</name>
</display_field>
<display_field>
<name>Serial Number</name>
</display_field>
</display_fields>
<computers>
<size>5</size>
<computer>
<name>MinneMini’s Mac mini</name>
<udid>3CBC248B-0E2B-5D12-AB55-7F14D13D0103</udid>
<id>1</id>
<Computer_Name>MinneMini’s Mac mini</Computer_Name>
<Model_Identifier>Macmini9,1</Model_Identifier>
<PO_Number/>
<Serial_Number>H2WDV8K6Q6NV</Serial_Number>
</computer>
<computer>
<name>Jeremy’s Mac mini</name>
<udid>156DBF18-45A6-5429-BE12-DA32ADC50621</udid>
<id>6</id>
<Computer_Name>Jeremy’s Mac mini</Computer_Name>
<Model_Identifier>Macmini7,1</Model_Identifier>
<PO_Number/>
<Serial_Number>A02X1111HV2X</Serial_Number>
</computer>
<computer>
<name>Jake’s Mac mini</name>
<udid>CF3753BF-ADCE-5B38-B821-381C0A4B1182</udid>
<id>10</id>
<Computer_Name>Jake’s Mac mini</Computer_Name>
<Model_Identifier>Macmini8,1</Model_Identifier>
<PO_Number/>
<Serial_Number>A02X1111HV3X</Serial_Number>
</computer>
<computer>
<name>McGonagall's Magical Mac Mini</name>
<udid>EE867891-ECBA-45EB-B3D8-7D40842ACA7A</udid>
<id>11</id>
<Computer_Name>McGonagall's Magical Mac Mini</Computer_Name>
<Model_Identifier>Macmini7,1</Model_Identifier>
<PO_Number/>
<Serial_Number>49B113C952DF</Serial_Number>
</computer>
<computer>
<name>H2WFNFQUQ6NV</name>
<udid>C033C746-76A3-5EA8-8B3C-50F050C4AE01</udid>
<id>36</id>
<Computer_Name>H2WFNFQUQ6NV</Computer_Name>
<Model_Identifier>Macmini9,1</Model_Identifier>
<PO_Number/>
<Serial_Number>H2WFNFQUQ6NV</Serial_Number>
</computer>
</computers>
<site>
<id>-1</id>
<name>None</name>
</site>
</advanced_computer_search>
Wow, that’s a lot of data, and you can see (based on the tab indents) that we have a few nests to work out. To get to the serial number of our computers, we have to traverse into the <advanced_computer_search>
section, then into the <computers>
section, then into each <computer>
object, and finally pull the <Serial_Number>
key. Once we have the serial number, we will need to store that in an array that can be used by the next request in our chain of requests: “Update Computer By SN”.
To capture the serial numbers we will use the Test tab to place the response body from our request into an array variable. First we need to convert the output from XML to JSON, since JSON is much easier to work with here. We’ll start with grabbing the entire response body and outputting it to the console so we can see what we’re getting. On the Tests tab enter the following:
const response = xml2Json(responseBody);
console.log(response);
Open the console in Postman by clicking on “Console” in the status bar of the window:

With the Console open, go ahead and send your request to your Jamf Pro server. You should get something like this in the Console:

We’re really interested in the very last line of the console (highlighted above). This is the response body in JSON format. We can use the disclosure triangle to open this up and see what our dataset looks like. From this view we can see that we have to go down 4 levels to get the serial number:

If you’ve never dealt with JSON before or had to get nested values, don’t be afraid. Using JavaScript you can “dot walk” to the data you need. Dot walking, in simple terms, means seprating each nested level by a period when you are pulling data. For our example, if I wanted the size of the array set we would use the following:
response.advanced_computer_search.computers.computer[0].Serial_Number
.
If we use the console.log
function to print that out as a test, we get:

“But what is that bracket notation in the dot walk” you may be asking. Because the list of computers is actually an array of values we need to use the index value of the specific item we want in that array. You can think of an array as a container of items where each item has a specific location (index) to be stored, almost like a line of children on the playground. Each child is in a specific spot and you can reference the child by that spot in line (array indexes start at 0). So if I wanted to ask the name of the child in the second position in line, I could refer to child[1] and ask that child their name. I know, kind of a clumsy analogy, but hopefully it works. You can read a little more about arrays in this post.
Gather Our Data
Ok, back to our use case. Since we need all of the serial numbers from our Advanced Search for our next API request, we will need to store those in a Postman variable. And since we have more than one serial number to get, guess what we need to use? That’s right, an array. First we have to declare a blank array:
var serial_numbers = [];
Since the <computer>
item in our JSON is an array of computers, we will need to loop over each item in that array to grab the serial number value. To do this we will use the forEach
function:
response.advanced_computer_search.computers.computer.forEach(function(computer) {
if(computer.Serial_Number){
serial_numbers.push(computer.Serial_Number);
};
});
The above bit of JavaScript goes over each item in the <computer> array, and if there is a value in the <Serial_Number>
field, it adds that serial number to our serial_numbers
array using a push
. The last step is for us to store this in a Postman variable that we can use in our next API request:
pm.variables.set("savedData", serial_numbers);
This line simply says “take our serial_numbers
array and store it in the variable that I am calling savedData
“. You can use console.log(pm.variables.get("savedData"));
to output the variable to the console to verify that you have only the serial numbers.
So putting everything together, our Tests tab should look like:
const response = xml2Json(responseBody);
var serial_numbers = [];
response.advanced_computer_search.computers.computer.forEach(function(computer) {
if(computer.Serial_Number){
serial_numbers.push(computer.Serial_Number);
};
});
pm.variables.set("savedData", serial_numbers);
console.log(pm.variables.get("savedData"));
And the output looks like:

Use The Stuff
Now that we have a Postman variable with our serial numbers, we can use that in our next API request to update PO numbers. To do this we will make use of the Pre-Request Scripts tab in Postman. The first thing we need to do is grab the dataset we saved in our first request so we can manipulate the data:
const myData = pm.variables.get('savedData');
Now that we have the data, we’ll set our variable to grab the serial number for each entry:
pm.variables.set('serialnumber',myData.shift());
What we are doing here is grabbing the serial number value from our data, myData
, and storing it in the serialnumber
variable. The use of .shift()
is what allows us to move to the next value in the myData
array each time we run the request. Think of it like walking that line of children, stopping at each one and asking their name, and then moving on to the next to ask the same question.
Next we will utilize an if/then statement to queue up the next run of the API request:
if(Array.isArray(myData) && myData.length > 0) {
postman.setNextRequest('Update Computer PO');
} else {
postman.setNextRequest(null);
}
What this is doing is checking to make sure that the length of our myData
array is greater than 0, insuring we still have values in the array. See, each time we use .shift()
we are actually removing an item from the array (this is known as a pop in other programming languages where we pop something off the stack). The postman.setNextRequest('Update Computer PO');
command is telling Postman to set the next API request to run as the name of the API request that is currently running. This might make more sense with an image so I’ll put one below. The else
portion of the if/then statement handles the case when our array of values has a length of 0, meaning it is empty. In that case we are setting the next request as null
which tells Postman to stop.
For our use case we are going to simply assume that our collection of devices all have the same PO number. So we’ll set the PO number as a static value in the body of our API request. And since that’s all we are updating, our body is pretty small:
<computer>
<purchasing>
<po_number>123456</po_number>
</purchasing>
</computer>
Run It
Now that we have everything together, we can use a Runner to run our two requests and have the serial numbers passed from the Advanced Search to our Update Computer PO request. If I look at the values in Jamf Pro before the run we can see that PO Number is blank:

If we watch the console as we run the runner, we can see that each subsequent request is a different serial number:

And we can check Jamf Pro to see that the PO numbers are now present:

Wrapping Up
The use of Postman variables to store data to pass to each subsequent request is a real gamechanger and can level up your use of Postman. There are plenty of use cases for this and I’ll cover one or two more in future posts.
I want to acknowledge a couple of articles and videos that helped me understand this and get it working:
https://medium.com/@knoldus/postman-extract-value-from-the-json-object-array-af8a9a68cc01
Home Office Setup
This post will be a bit of a departure from my normal ones. Normally I’m trying to pass on some tech knowledge that I might have, or make you aware of some new feature, but not this time. This time I’m shamelessly plugging some products with Amazon Affiliate links so I can make some dough. I could lie to you and say it was just to share my office setup, but I know y’all are smarter than that. Hey, I mean, if you see something in this post that you like and you might purchase for yourself, why not let Amazon give me a few pennies in return. Right. Right?

Let’s start from the top.
Blue Yeticaster bundle – Blue Yeti mic, Compass boom, and Radius III shock mount. I love this mic combo and use it everyday for the many of zoom calls I am on. My only regret/con, and it’s not really one aimed at the product, but it’s difficult to get the boom into a position that stays out of the field of my camera. I don’t really want to show off that I’m on an external mic.
Aureday 6″ Video Conferencing Light – based on the time of the year and the time of day, I found that I wasn’t getting enough light for zoom to show my face clearly. This light provides three different color temperatures and 10 different brightness settings. It comes with a clamp that you can attach to your computer or monitor, but I used the flexible tripod below to attach to the monitor arm.
UBeesize Tripod – This flexible tripod allowed me to wrap it around the arm holding the monitor to get my video conferencing light above the monitor and camera.
Logitech C922x Pro Stream Webcam – This is a great camera for the price. It operates at 1080p and does a good job in low light.
LG 34WN80C-B UltraWide Monitor 34″ – It took me a few days to adjust to using a curved ultra wide display. My eyes would get tired/sore and I’d have to walk away for a bit. But once I got used to it, I absolutley love this monitor! It has pass thru power (60W) via USB-C and it has inputs for two HDMI ports and 2 USB ports.
Fully Jarvis Monitor Mounting Arm – while this arm is made to support up to 32″ displays, it has worked fine for the 34″ LG. This has been great for clearing a little bit of space from the desk and allowed me to utilize the space between the display a bit better.
Apple AirPods Pro – these are great for calls and listening to audiobooks on the go. I, like many others, have problems with the handoff between devices if these are connected to multiple devices. The microphone is also not the greatest for zoom calls, hence the Blue Yeti I linked above.
Belkin Wireless Charger – great for keeping the AirPods Pro charged or for putting a little juice into my iPhone 11.
Lamicall S1 Cell Phone Stand – this keeps my iPhone 11 in front of me and at the right angle to utilize FaceID when I need to grab an MFA code quickly.
SOULWIT Cable Holder Clips – I’ve used other desk clips for holding cables in the past. What I liked about this particluar one is that I could get 3 different ones for the price of one of the other manufacturers.
HumanCentric Vertical Laptop Stand – I like having my laptop off the desktop and out of the way. This keeps it in one spot and off the desktop so I’m not eating up space.
Apple Magic Keyboard – I find I do not need the number keypad on my keyboard, plus I like the minimal look of having this keyboard on my desk. If I could have fewer items on my desk, I would.
Logitech M705 Marathon Mouse – What I love about this mouse is the feel and the ability to customize the buttons. I have the two side buttons set to do App Expose and Mission Control on my MacBook Pro, which allows me to see all open windows for an app or all open windows period. Combined with the ability to change desktops by pressing the scroll wheel to either side, this mouse hits all the spots.
Uplift V2 Desk with Curved Bamboo Top – I looked at several standing desks and settled on the Uplift for two reasons: customer reviews and where they are located. Uplift is located in Austin, TX and I wanted to spend money on a Texas company. I ordered the 72″ wide desk top, along with the wire organizer, two power outlet grommet, wheels, and the under desk hammock for my feet. I have really enjoyed this desk, althoug I do not use the hammock as often as I thought I would.
Topo Comfort Mat by Ergodriven – I try to stand at least 75% of the time I spend in front of my computer. I wanted a floor mat that would help prevent knee issues, back pain, and exhaustion. The beauty about this mat is it checks those boxes and provides something for my feet to do while I’m standing. The different areas of the mat allow me to stretch my legs and help prevent boredom when standing.
TP-Link TL-SG108 8 Port Switch – Hidden under my desk, this allows me to feed all three computers that are on the desk from one ethernet drop that goes back to my router. It’s unmanaged, which doesn’t matter, quiet, and super light so it sticks under the desk easily.
Raspberry Pi – I’m running Pi-hole on a Raspberry Pi that is sitting under the desk. This open source network wide ad blocker has been awesome! No more ads coming in, it blocks unwanted noise, and running on a Pi makes it super quiet and easy to hide.
eero 6 Wi-Fi 6 Mesh Network – I’m running the eero 5 system here, but if I had the money I would upgrade to the eero 6 system to get Wi-Fi 6 running. This mesh system was super easy to setup and helps provide strong wireless coverage to my entire house.
ASUS AC2900 Gaming Router – we have Frontier FIOS (formerly Verizon FIOS) and I got tired of paying Frontier for a router. This one came highly recommended and has operated perfectly for us.
Well, that’s about everything. I guess I could hype my Yeti K-State Wildcat tumbler too (Go Cats!). I hope you find at least something on this page that might help you out. And hey, if you don’t purchase using my links, that’s perfectly fine.
Until next time…
Using Postman with Jamf Pro Part 5 – More Runners
Welcome back to my series on using Postman with Jamf Pro. If you haven’t checked out the previous posts, or you’ve never used Postman with the Jamf Pro API, you may want to go read through these:
Using Postman for Jamf Pro API Testing – Part 1: The Basics
Using Postman for Jamf Pro API Testing – Part 2: Creating And Updating Policies
Using Postman with Jamf Pro Part 3: Computer Groups
Using Postman with Jamf Pro Part 4 – Variables & Runners
In my last post, I showed how you can use the Collection Runner feature in Postman to either run multiple API commands in sequence, or pass a CSV file full of data to an API command. The example we used was disabling a bunch of policies all at once.
In this post, I want to show how I used a Runner to create new installer policies for packages. In the environment, I used to manage we followed Bill Smith’s advice given in his JNUC 2017 talk Moving Beyond “Once Per Computer” Workflows and we created separate installer policies for each application that could then be called by other policies or scripts. When Adobe Creative Cloud would inevitably revision up to the next version (2021 to 2022 for example) we would have several new installer policies we’d have to create (at least 7 or more). Well, clicking around the GUI to do that is just nuts and a waste of time when we can create a CSV file to do it for us.
Setup API Command
First thing we need to do is to setup an API command and save it into a collection in Postman. We will want to set variables for the information that is different in each policy. For our Adobe example, the list of information that is unique is:
- Policy Name
- Custom Trigger
- Package ID
- Package Name
We will create variables for each of those values so that we can pass a CSV file to a Runner to create each policy. So we need to edit our XML and then save it to a collection in Postman. Our edited XML looks like this:
<policy>
<general>
<name>{{name}}</name>
<enabled>true</enabled>
<trigger_other>{{trigger}}</trigger_other>
<frequency>Ongoing</frequency>
<category>
<id>14</id>
<name>zInstallers</name>
</category>
</general>
<scope>
<all_computers>true</all_computers>
</scope>
<package_configuration>
<packages>
<size>1</size>
<package>
<id>{{pkg-id}}</id>
<name>{{pkg-name}}</name>
<action>Install</action>
</package>
</packages>
</package_configuration>
<reboot><no_user_logged_in>Do not restart</no_user_logged_in><user_logged_in>Do not restart</user_logged_in>
</reboot>
<maintenance>
<recon>true</recon>
</maintenance>
</policy>
Sidebar: if you wanted to make this a completely generic template for creating policies, you could use variables for Frequency, Category ID (do not have to have the category name as it will pull up based on ID), and more.
Package Info
One thing we’ll need is the name of each package and the corresponding ID value. Obviously, before we can get that information we’ll need to upload each package to our Jamf Pro server. Go ahead and do that. I’ll wait…..
Ok, once you’ve uploaded everything, go into Postman and locate the “Finds All Packages” API command in the Jamf Postman collection (see Part 1 for the Jamf collection). Run that command, which will pull back every package you have uploaded to Jamf Pro, and then use the “Save Response” drop down to save the output to a file. This will save out as an XML file, but you can easily open that in Excel to have it convert to a spreadsheet.

Sidebar: If you do not have Excel, or don’t want to load it on your computer, you can find online services to convert XML to CSV, like Data.page. Also, you do not have to convert to CSV or Excel, it’s just easier to grab the package ID and package name from these formats than it is from XML.
After we have the IDs and names, we’ll want to create our CSV file. When we create the CSV we want to make sure the column headers match the variable names we use. For our example the variables we’re using are:
- name
- trigger
- pkg-id
- pkg-name
Our CSV file file should look something like this:
name,trigger,pkg-id,pkg-name
Adobe Photoshop 2022,photoshop2022,2,Adobe_Photoshop_2022.pkg
Adobe Illustrator 2022,illustrator2022,3,Adobe_Illustrator_2022.pkg
Adobe InDesign 2022,indesign2022,4,Adobe_InDesign_2022.pkg
Runner
Once we have all of these pieces together, we can open a new Runner tab and run our command. Drag the proper Collection into the Runner tab, select the CSV file you created, and click Run. The Runner will cycle through each line of the CSV file and create the necessary policies in Jamf Pro via the API command.
Wrap Up
Now that we’ve covered a basic Runner example and a little more complicated one using more variables, you can hopefully see the power of Postman and how it can help in the day-to-day administration of Jamf Pro.