Anthony’s Mac Labs Blog

Automatically Renaming Computers using a Package in Jamf Pro (📦)

Posted 2023 February 23

It seems that much of the work I do as a Mac Admin leverages the work of others, which I then customize to suit my specific circumstances and share back as appropriate. (The fact that this is common practice is one of the things I love about the Mac Admins community.) Sometimes, that work I’m leveraging is a tool, but sometimes it is a methodology or a specific technique. This post is an example of tying all those things together: tools, methodology, techniques, and then sharing back. My goal was to automate the renaming of computers at enrolment into Jamf Pro. This was quite a journey!

Adapting to EACS

I have been using Twocanoes MDS to erase my Lab computers and get them setup to be enrolled in Jamf Pro using Automated Device Enrolment (via a PreStage Enrollment). Now that about half of our Lab computers run on Apple Silicon and we are using macOS Monterey or later, I wanted to start leveraging the ability to quickly restore a Mac to factory settings (either by restoring an IPSW using Apple Configurator or using Erase All Contents and Settings if it already has the OS version I wanted). That meant taking all the steps that MDS was doing and moving them to Jamf Pro. My MDS setup was already fairly lean, so there was not a lot to move. But one of the key things I had MDS do was name the computer using our desired naming convention, as the name identifies which Lab the computer is in and thus which software titles it gets. Since I entered that name manually when using MDS,[1] I needed to find a way to automate that.

Haircut to the Rescue!

A MacAdmins Slack and Internet search surfaced an article from January 2018 by Matthew Warren (a.k.a. haircut) about Automatically Renaming Computers from a Google Sheet with Jamf Pro. Matthew leveraged the setComputerName verb for the jamf binary. It has a -fromFile option so that you can supply a CSV file with a serial number (or MAC address) and computer hostname for every computer you want to rename:

jamf setComputerName -fromFile /path/to/file.csv

Matthew’s solution put the needed information in a Google Sheet and used a bit of Python code to grab the file from a web server before running the above command. While I could have adopted Matthew’s solution by substituting Microsoft OneDrive (our institution’s provided solution) for Google Sheets, it had a few more working parts to support than I would have preferred. Since I already had the information I needed stored in FileMaker Pro, it seemed simpler to automate creation of that CSV using FileMaker and then deliver it to the target computer rather than pulling it from a web server. The list doesn’t change that often — just when we get new computers, which is once or twice a year. So how could I deliver the file, run the command, and — ideally — delete the file once I was done?

Putting the Payload in Payload-Free

When I think of installing a file in a particular location, I think of building a package. So I could replace the Google Sheet in Matthew’s workflow with a package that puts the CSV file in a specific spot in the file system and then uses a Jamf Pro policy to trigger the jamf setComputerName command. But if I was building a package anyway, I could run the jamf setComputerName command in a postinstall script in the package. That would give me a self-contained solution.

Once I had settled on that solution, I remembered that I had heard of a more elegant way to deal with files only needed during installation like the CSV file in this task. So I used my Internet/MacAdmins searching skills again and found the technique I was looking for, which had been shared by Neil Martin and Greg Neagle. I could place the file in the scripts folder of the package I was building. My script would then reference the file from within the package bundle. Essentially, I was building a payload-free package with a hidden, ephemeral payload. This was cleaner, to my mind, than dumping the file in /Users/Shared, for example, as the package is run from /private/tmp/ and will thus be cleaned up automatically.

Using AutoPkg for the Automation

Because that CSV file will change periodically, I wanted to automate building of this package. I chose to use AutoPkg because of my level of fluency with that tool, but you could choose to use munkipkg or even The Luggage if you wanted to go Old School.

My .pkg recipe (JamfSetComputerName.pkg.recipe) creates the package. It requires that you specify Input variable values for the path to the desired CSV file (CSV_PATH) and a personalized REVERSE_DOMAIN for your organization or institution. The postinstall script is hardcoded into the recipe, so only the CSV file needs creating.

While the techniques I used to create this .pkg recipe are not new, they are not well-documented, so I plan to write a follow-up post that goes through the .pkg recipe in more detail (which I will link here when it is done). For now, all you need to know is that running this recipe with appropriate values in the override generates a package that leverages the jamf binary to rename the station based on the list you supplied.

Sign, Sign, Everywhere a Sign

While my initial uses of PreStage Enrollments did not try to install any additional payload items (I let Enrollment Complete policy triggers do that), the kinds of settings that MDS established were analogous to a PreStage payload. I wanted to add my new package to my PreStage, but there was one problem: all packages added to a PreStage must be signed. Luckily, I already knew how to create a signing certificate using the Jamf Pro CA as the Certificate Authority because I had to do this with configuration profiles (crafted with ProfileCreator or iMazing Profile Editor) to protect those from being altered by Jamf Pro upon import. There are two different blog articles I found that describe how to do this: one from the aforementioned Matthew Warren and one from Frederick Abeloos. They both cover my particular use case, but Matthew’s article focusses on ways to sign profiles while Frederick’s article goes into a little more detail about how the chain of trust works; they are both worth perusing, especially if you would benefit from signing profiles or packages. I will not replicate their excellent instructions here.

Since I was using AutoPkg to build my package, it made sense to have AutoPkg sign it. This time, it is Rich Trouton to the rescue with two articles from his blog: one describing the PkgSigner shared processor that he hosts in his repo (written by Paul Suh and updated for Python3 by Nick McDonald) and another providing a template for a .sign recipe that is a child of a .pkg recipe. The PkgSigner processor makes a signed copy of the package that the .pkg recipe creates, and renames the original package with -unsigned appended. You will need to provide the value for SIGNINGCERTIFICATE in your recipe override — that will be whatever you named your certificate when you added it to your keychain. You can see the simple .sign recipe I used for this in my repo: JamfSetComputerName.sign.recipe.

…and JamfUploader, of course

Finally, once I have the signed package, I might as well have the JamfPackageUploader processor upload it to Jamf Pro for me. Since there is not currently a processor to add this package to a PreStage Enrollment, my .jamf recipe simply uploads the package and assigns it to an existing Category. You can find -pkg-uploadonly.jamf recipes in my jazzace-recipes repo for uploading either the unsigned or signed package. [If you are not familiar with the JamfUploader family of AutoPkg processors, check out the repo, the wiki, or the presentations Graham Pugh has done (sometimes with me) to learn more. It supersedes JSS Importer and is adaptable to multiple use cases. Or if you’re using Munki, just write a child .munki recipe.] So my production recipe is an override of the .jamf recipe with all of the aforementioned Input variables specified, plus the category for my package.

One Final Gotcha

Of course, no journey to automate would be complete without some sort of blocker. While I would be able to successfully add my signed package to my PreStage Enrollment if we were using Jamf Cloud, we are using an on-premises Distribution Point (DP), which means there are specific criteria to be met by the DP for PreStage deployment. When I tried to add my signed package to the Enrollment Packages payload of my PreStage, I could not select our DP, as it showed the error message, “The distribution point does not meet the requirements to host packages for enrollment.” We’re still trying to determine the exact cause of this warning, but we think the sticking point is, as the documentation indicates, “The distribution point web server cannot require authentication,” which ours does. So for now, I have a regular policy, named so that it runs first (alphabetically) after the PreStage completes (“ 00 SetComputerName”), that installs the package and does a Maintenance/recon step so that my policies will be correctly scoped on next check-in. (I have all my policies with an Enrollment Complete trigger also trigger on first check-in, since most of them require that the computer name be correct to be scoped.)

It Takes a Village to Build a Workflow

So now I am able to take the new computers I will be receiving in the upcoming months and get them enrolled and deployed in a more-automated fashion using ADE/PreStage Enrollment, AutoPkg, FileMaker Pro, and numerous techniques and tricks that I learned from others. While I doubt that you would want to replicate everything I did here for your own workflow, I hope you found (or were reminded of) a technique or tool that you can use to meet your own needs.

[1] I took advantage of the ability of MDS to prompt the person running MDS for information, which would then be stored in a variable(s) that scripts could access on startup. I used this feature to collect the station name, inventory tag number, and IP address (since we’re still using fixed IP addresses). See Workflow Variables Settings in the MDS 4 Administrator Guide for more details about that feature. [Return to main text]