Skip to main content

Adding data to Jamf Pro

Whether you’re moving to a new server or setting up a development and/or QA server, you’ll need to start populating them with data. This can be accomplished by either individually enrolling devices, manually creating policies, profiles, etc… or by importing the data. If you’ve set up a server before, you’re likely familiar with the tedious process of adding data manually so let’s look at how you can import data.

Two options exist for importing data: directly importing into the database or utilizing an API. Importing directly into the database should only be used if you have a complete and healthy Jamf Pro server database to import. Importing sections or individual items can result in an unstable or unusable server. An API on the other hand provides great flexibility, convenience and safe guards for importing data.

Getting Started

As mentioned earlier, importing directly into MySQL is useful for a full import of all data:

 mysql -u root -p jamfsoftware < ../jamfsoftware.sql 

The import has little flexibility as to what is imported but provides a quick way to populate the database. Be aware that any existing data will be overwritten by the import. Utilizing the API allows you to retain existing data while importing new data or modifying existing data. This is ideal for environments where you develop and test on one server, then once things are ready, migrate the item(s) to another. You can view endpoints available in the API by browsing to:

 https://your.jamf.server:port/api 

To begin, let’s add a category to the database using bash:

 curl -sku apiUser:password https://your.jamf.server:port/JSSResource/categories/id/0 -d "<?xml version='1.0' encoding='UTF-8'?><category><name>Security</name><priority>9</priority></category>" -X POST -H "Content-Type: application/xml"

Breaking down the command:
curl - the binary that submits data to the endpoint.
-s - run silently, don’t display progress.
-k - ignore certificate validation.
-u - credentials used to connect to the server. If the password is not provided you will be prompted.
-d - the data that will be sent.
-X - the operation to perform. The most common being: POST (create new), PUT (update existing), GET, DELETE.
-H - format of the data and whether we’re sending or receiving.
-v - Not used here, but will provide verbose output. Handy for troubleshooting.

When creating new objects, always specify an id of 0 (zero).

 https://your.jamf.server:port/JSSResource/categories/id/0

Upon successful completion of the command, the server will reply with something similar to the following:

 <?xml version="1.0" encoding="UTF-8"?><category><id>23</id></category> 

Updating an existing item can be performed by referencing either the object name or ID. I prefer to reference the object by ID as it is always unique. Computers and policies for example can have duplicate names. If trying to reference the object by name, there is no guarantee you’ll update the one you’re interested in, if duplicates exist. To update the category you just created from ’Security' to ‘Security - macOS’, leave the priority as it is:

 curl -sku apiUser:password https://your.jamf.server:port/JSSResource/categories/id/23 -d "<?xml version='1.0' encoding='UTF-8'?><category><name>macOS - Security</name></category>" -X PUT -H "Content-Type: application/xml"

Stepping it Up

Let’s look at adding something a little more complex, printers for example. Previously, with categories, you only needed to provide a name for the object and had the option to provide a priority. With printers you have a name, category, URI, CUPS name, location, along with several other attributes available. To get an idea for what the structure of the XML needed to add a printer is, let’s visit the Jamf developer portal at http://developer.jamf.com/apis/classic-api/index and navigate to the POST method within the “printers” resource.

Note all the fields required when creating a new printer object. Looking at the Jamf server GUI when creating a printer will help identify the required fields:

Next, you’ll create a spreadsheet containing the information for your printers. Be sure to save the spreadsheet with line endings appropriate to the OS, and as plain text. At the very least you need the display name for the printer, CUPS (queue) name for the printer, and device URI. See example here: https://github.com/BIG-RAT/addPrinters/releases/download/current/printers.csv

You’ve added a few extra bits of information along with the required fields. One important note, as you’ve included a category called Printers, that category must exist prior to adding the printers to the Jamf Server. Now, let’s build a script to import the information, the path to the file containing your printer information will be passed as an argument to the script.

Let's start with a little help on how to run the script. If the data file is not provided you will provide some usage information:

 if [ "$1" = "" ];then
 echo "Usage:"
 echo " /path/to/createPrinters.sh /path/to/printers.csv"
 exit
fi

Verify the file provided is accessible:

 if [[ ! -f $1 ]];then
 echo "unable to locate data file: $1"
 exit
fi

Next prompt for credentials used to create the printers:

 echo -n "Jamf Server Admin: "
 read uname
echo -n "Password for ${uname}: "
 stty -echo
 read passwrd
 stty echo
echo

Create an endpoint used to test authentication:

 endpointUrl="https://your.jamf.server:port/JSSResource/printers"

Test authentication:

 result=$(curl -w " %{http_code}" -m 10 -sku "${uname}":"${passwrd}" ${endpointUrl} -X GET -H "Accept: application/xml")
statusCode=$(echo $result | awk '{print $NF}')
if [[ $statusCode = "401" ]];then
 echo "Incorrect username and/or password."
 exit 0
fi

Set the URL so that you can create new objects:

 endpointUrl="${endpointUrl}/id/0"

Loop through the file capturing the attributes you'll be setting, create the XML to create the printer, and POST the XML.

 while read printer;do
name=$(echo $printer | awk -F',' '{ print $1 }')
if [[ -z $name ]];then
echo "missing printer name: ${printer}"
echo
continue
fi
category=$(echo $printer | awk -F',' '{ print $2 }')
uri=$(echo $printer | awk -F',' '{ print $3 }')
if [[ -z ${uri} ]];then
echo "missing printer URI: ${printer}"
echo
continue
fi
cups_name=$(echo $printer | awk -F',' '{ print $4 }')
if [[ -z ${cups_name} ]];then
echo "missing CUPS name (queue name): ${printer}"
echo
continue
fi
model=$(echo ${printer} | awk -F',' '{ print $5 }')
default=$(echo ${printer} | awk -F',' '{ print $6 }')
printerXml="<?xml version='1.0' encoding='UTF-8'?>\
<printer>\
<name>${name}</name>\
<category>${category}</category>\
<uri>${uri}</uri>\
<CUPS_name>${cups_name}</CUPS_name>\
<model>${model}</model>\
<make_default>${default}</make_default>\
</printer>"
result=$(curl -sku "${uname}":"${passwrd}" ${endpointUrl} -X POST -H "Content-Type: application/xml" -d "${printerXml}")
if [[ $(echo $result | grep 'technical details') = "" ]];then
echo "created printer: ${name}"
else
echo "***** failed to create printer: ${name}"
echo "$result"
echo
fi
done << EOL
$(cat "${1}")
EOL

Putting all together we have:

 #!/bin/bash
if [[ $1 = "" ]];then
echo "Missing data file."
echo "Usage: /path/to/createPrinters.sh /path/to/printers.csv"
exit
fi
if [[ ! -f $1 ]];then
echo "unable to locate data file: $1"
exit
fi
echo -n "Jamf Server Admin: "
read uname
echo -n "Password for ${uname}: "
stty -echo
read passwrd
stty echo
echo
## url used to test authentication
endpointUrl="https://your.jamf.server:port/JSSResource/printers"
## test authentication
result=$(curl -w " %{http_code}" -m 10 -sku "${uname}":"${passwrd}" ${endpointUrl} -X GET -H "Accept: application/xml")
statusCode=$(echo $result | awk '{print $NF}')
if [[ $statusCode = "401" ]];then
echo "Incorrect username and/or password."
exit 0
fi
## set the url so that we can create new objects
endpointUrl="${endpointUrl}/id/0"
while read printer;do
name=$(echo $printer | awk -F',' '{ print $1 }')
if [[ -z $name ]];then
echo "missing printer name: ${printer}"
echo
continue
fi
category=$(echo $printer | awk -F',' '{ print $2 }')
uri=$(echo $printer | awk -F',' '{ print $3 }')
if [[ -z ${uri} ]];then
echo "missing printer URI: ${printer}"
echo
continue
fi
cups_name=$(echo $printer | awk -F',' '{ print $4 }')
if [[ -z ${cups_name} ]];then
echo "missing CUPS name (queue name): ${printer}"
echo
continue
fi
model=$(echo ${printer} | awk -F',' '{ print $5 }')
default=$(echo ${printer} | awk -F',' '{ print $6 }')
printerXml="<?xml version='1.0' encoding='UTF-8'?>\
<printer>\
<name>${name}</name>\
<category>${category}</category>\
<uri>${uri}</uri>\
<CUPS_name>${cups_name}</CUPS_name>\
<model>${model}</model>\
<make_default>${default}</make_default>\
</printer>"
result=$(curl -sku "${uname}":"${passwrd}" ${endpointUrl} -X POST -H "Content-Type: application/xml" -d "${printerXml}")
if [[ $(echo $result | grep 'technical details') = "" ]];then
echo "created printer: ${name}"
else
echo "***** failed to create printer: ${name}"
echo "$result"
echo
fi
done << EOL
$(cat "${1}")
EOL

Running the script (make sure it's executable: chmod +x /path/to/addPrinters.sh):

 $ /path/to/addPrinters.sh /path/to/printers.csv
Jamf Server Admin: jpsadmin
Password for jpsadmin:
created printer: 3rd Floor Xerox
created printer: 2nd Floor HP MFP
created printer: Brother MFC-L5850DW series
created printer: Lobby

Now, what if you forgot to create the Printers category before running the script? You should see a reference to the cause of the error in the result of the curl command:

 <html>
<head>
<title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Conflict</p>
<p>Error: Problem with category name</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>

You could say the creation of the printer object (may) depend on the existence of the category object. May depend, since you can create a printer object, with no category assigned, regardless as to whether or not categories are created. It is, however, nice to know what object you should create first, to avoid potential issues later on. In general, start with shared objects, those not tied to only macOS or iOS. By ranking objects with the fewest, or no, dependencies to those with the most, you'll have something like the following:

Shared:

 Sites
User Extension Attributes
LDAP Servers
Users
Buildings
Departments
Categories
Jamf Users
Jamf Groups
Network Segments
Advanced User Searches
User Groups
macOS:
File Shares (AFP/SMB)
Directory Bindings
Dock Items
Computers
Software Update Servers
Netboot Servers
Extension Attributes
Scripts
Printers
Groups
Restricted Software
Configuration Profiles
Packages
Advanced Searches
Configurations
Policies
iOS:
Extension Attributes
Devices
Groups
Advanced iOS Searches
Configuration Profiles

Another issue you might run into is where the object (printer) already exists. Again, you will see a reference to the issue in the result of the curl command:

 <html>
<head>
<title>Status page</title>
</head>
<body style="font-family: sans-serif;">
<p style="font-size: 1.2em;font-weight: bold;margin: 1em 0px;">Conflict</p>
<p>Error: Duplicate name</p>
<p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.10">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>

Pick a Language

Prefer to script in a language other than bash? You’re in luck! Python, Java and PowerShell to name a few all support working with the API.

For example, using this PowerShell script you can create the same printers. Say you have both the PowerShell script and data file in D:\api, you run the script similar to what you did with the shell script. Before running you need to modify the file for your environment. Locate line 100 and set $endpointUrl to the server in your environment:

 $endpointUrl = "https://your.jamf.server:port/JSSResource/printers"
Now run the script from the PowerShell command prompt:
D:\api\appPrinters.ps1 D:\api\printers.cvs

The script will bring up a dialog box prompting for credentials:

Provide appropriate Jamf Pro credentials and the results should be similar to what you saw earlier, provided the printers don't already exist.

 cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
added printer: 3rd Floor Xerox
added printer: 2nd Floor HP MFP
added printer: Brother MFC-L5850DW series
added printer: Lobby

As a final example, you might find it useful to have a script that is cross platform, say in Java. Let's take this Java script, compile it and run it. Before compiling, you need to modify the file for your environment. Locate line 93 and set endpointUrl to the server in your environment:

 endpointUrl = new URL ("https://your.jamf.server:port/JSSResource/printers/id/0");

First, you'll change your present working directory to the directory you've saved the addPrinters.java script, then compile:

 workstation $ javac addPrinters.java 

This will create a file called addPrinters.class in the same directory as the .java file. Now you'll execute the addPrinters file (leaving off the extension):

 workstation $ java addPrinters /path/to/printers.csv 
Jamf Admin Username: jpsadmin
Password for jpsadmin: 
created printer: 3rd Floor Xerox
created printer: 2nd Floor HP MFP
created printer: Brother MFC-L5850DW series
created printer: Lobby
workstation $ 

Note, this was only tested using java 8.

In Conclusion

The API provides a safe, easy and flexible way to add data to your Jamf Pro server. Although the API doesn't provide access to all the settings available in the GUI, there is a tremendous amount of work you can do with it to save yourself from repetitive tasks.

You can find all the scripts and sample data file here.

Not already a Jamf Pro customer?

Take our best-of-breed Apple management solution for a free test drive and start putting these workflows in place.