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