Revisiting: A macOS upgrade method using Jamf Pro Self Service

Back in 2017, I released my script that I use to do macOS upgrades in my organization through Self Service. Since the script I wrote has changed, I thought a new post was deserved to account for the changes I’ve made.

At my organization, we leverage the macOS installer app from Apple which has a neat little command line tool called startosinstall. There are quite a few scenarios that need to be accounted for to avoid running the upgrade in a situation where it would ultimately fail. With that in mind, here are some of the requirements I came up with that the upgrade script needed to solve:

  1. Computer has sufficient free drive space.
  2. Ensure the user is plugged into a power source.
  3. Confirm that the volume is not presently undergoing encryption/decryption.
  4. Provide a way for the end user to do FileVault authenticated restarts if possible.
  5. Provide dialogs to give the user feedback such as a time estimate and dialogs on what to expect next.
  6. Make use of Jamf Pro script parameters to allow for customization and potential re-use for future operating systems releases.
  7. Provide logging to see where failures may appear.
  8. Allow use of an install package that can be used with macOS 10.13+ installers.
  9. Make sure that the macOS installer app does not have expired certificates.
  10. Perform a jamf recon immediately after upgrade.

Before we begin, note that the following script was designed on Jamf Pro v9.97 initially and continues to work through Jamf Pro v10.16. But do keep in mind that older versions of the Jamf Pro may not have support for the newer versions of macOS.

There are two policies that this process was designed with in mind, but you can do this in one policy if you want. The first policy is a custom trigger policy that deploys the macOS installer. The second policy is meant to run the actual script that will ultimately kick off the upgrade. You do have the choice of running just the second policy and skipping the first policy (downloading/deploying through a JSS policy), but you would need to ensure that the macOS installer app is already downloaded to /Applications folder through some other mechanism.

Downloading the installer is the first part we want to take care of. There are different ways to do this:

  1. You can manually download from the Mac App Store and then package the app up using your packaging tool of choice.
  2. Grab the original package installer from Apple as described in this post by Rich Trouton.
  3. You can use device-based VPP to download the macOS installer app to the client using your MDM service (in this case Jamf Pro).
  4. You can use this script by Greg Neagle to download the Install macOS app.

Once you’ve got the package, upload it to your Jamf Pro Distribution Point through Jamf Admin. Let’s create the policy to deploy that package.

Deploy macOS Installer app

macOS Installer App Deployment Policy

Create a new policy to deploy the macOS installer app. Here is what the policy should include:

  • Policy payload:
    • Packages: include the package to install the macOS installer app and set the application to “Install”
    • Maintenance: enable “Inventory Update”
  • Trigger: Custom with the name of your choosing (e.g. os1012upgrade). Optionally: Recurring check-in if you want to deploy the installer app in advance.
  • Execution Frequency: Ongoing
  • Scope: Scope to a computer group for computers that “Need Install macOS”. See Smart Group/Scope section for more details.

Smart Group/Scope

Scope is an important part of a policy. You can target individual computers, computer groups, limit to specific networks, and so on. For the first smart group we are going to create, I will go into a few things you will need to consider.

The first smart group would be used for the first policy to target computers that do not have the macOS installer app. Go into your JSS and create a new smart group. You can name it whatever you want (e.g. “Needs macOS 10.12 installer app”). You may have specific requirements that you want to meet before deploying the installer, but at the very least this should be in your criteria.

Screen Shot 2017-03-12 at 8.20.36 PM.png

The Application Version criteria in this case should make sure that your app installer is the version you want to make sure you want on the system (in this screenshot, the installer app version is for 10.12.3). It would also target computers that may not have the app installer at all. Again, this is just one way to create the smart group. You may have additional criteria you need computers to adhere to before they can get this installer (e.g. must have latest AV, must have FileVault enabled, etc.). You might be OK with any version of the installers being on the system in which case you would change the criteria to not look for the application version. To get the installer app version, click once on the installer and simply press spacebar to do a quicklook. Alternatively, you can run the command:

defaults read "/Applications/Install macOS" CFBundleShortVersionString

That’s assuming the installer is in that path. Change the path to your installer app if it’s in a different location. Keep in mind that if Apple suddenly releases a new version of the macOS installer and pushes that to a computer then you might need to adjust your smart group accordingly.

Lastly, in this same smart group we are also making sure that only computers below 10.12 would be eligible to get the download.

Network Considerations

Let’s dig a little deeper into the policy scope. Depending on your network you may have different considerations to keep in mind. For example, perhaps you want to deploy this package beforehand so that computers do not have the download the installer app when they run the Self Service policy. Depending on the size of your fleet you may also want to consider utilizing network segments. Network segments allow you to limit the scope of a policy to specific computers based on the IP they have. This would be great for example if you are a global company and want to make sure the installer comes from specific Distribution Points.

Network segments can be tied to specific Distribution Points (DPs). This comes in really handy because if you have a large fleet you do not want to stress the Distribution Point too much. Perhaps you might want to setup a few DPs temporarily for this purpose where a certain range of IPs would get it from one DP, and the other range would get it from another DP. Of course, if you have a DP setup in the cloud then stressing the server might not be too much of a concern outside of the connection to the office’s connection to the internet.

Another idea to consider is when you enable this policy. Maybe you do this at the end of the day/after business hours when the network will hopefully have less activity. And if your Jamf Pro Server/DP is available outside the office with a DP then maybe you decide you want to start this on a Thursday evening which would allow some clients to get it overnight, the remaining on the Friday (a slow day in some companies) and the rest over the weekend.

There is also the option of simply allowing the user to download the installer when they click on the Self Service policy. This has the benefit of allowing downloads to be dispersed and also allows you to potentially update the installer app in the policy (say a new version of macOS comes out) so that you aren’t redeploying the installer app again. And avoids putting an installer on the computers of stragglers who refuse to upgrade sometimes in which case the installer would simply take up space on the drive. The downside to this method is that the download time will increase the upgrade process. The actual OS upgrade process should take minimally about 30 minutes more or less, but the download may add 10 minutes to the process depending on where the Distribution Point is located and other network restrictions (connection (Ethernet or Wireless) a computer is on, Quality of Service configuration, etc.).

At one company I worked at, we made use of Network Segments to force users to download over Gigabit Ethernet from our local distribution point in each office. That added about 5 minutes to the upgrade process. The network segments were invaluable in this. It also allowed us to prevent people from downloading over wireless. At another job, I’ve also deployed the installer in advance and it has also made the process much smoother for the end user who didn’t have to wait on the download.

Yet another option is to deploy the macOS installer app through device-based VPP. Of course, Apple is already automatically deploying the macOS installer on all computers running 10.11, and 10.12, and 10.13. Still, this is at least another option you can utilize. You’ll want to test this as I haven’t done so myself. Additionally, if you were to add a macOS Server running the caching service, your network team will thank you. With a caching server, only the first download of the macOS installer app will come from Apple’s servers, but after that all the apps downloaded through the Mac App Store should come from the internal caching server which is on-site.

Regardless of what you want to do, speak with your infrastructure team in advance.

Self Service policy/script

This is the second policy we need to create and it uses the script where the magic comes all together. It makes use of ALL the custom Jamf Pro script parameters and provides exit codes so you can get an idea of where things may have failed. I’ll explain what it does and then the policy you’d make. If you’re familiar with bash, you’ll be able to read and understand the script. I like to comment my scripts as it helps me if I ever need to go back to it, but would also help others reading it as well.

Naturally, this script is meant to be used with Jamf Pro. It will make use of Install macOS  installer app’s command line tool “startosinstall” to initiate the upgrade. It also makes use of some Jamf Pro script parameters and is intended to be somewhat easy to modify if used with future OS deployments. Certain Jamf Pro script parameters are going to be required otherwise the script will exit.

The script also makes use of Jamf Helper to give the user error messages. You are welcome to modify the messages to your liking. Let’s talk about the parameters. If you’ve never played with Jamf Pro script parameters, these will be filled in the policy in the script payload. I’ll have a screenshot in the end.

Because it’d be hard to determine what parameters are used for what I would recommend adding Parameter labels when the script is imported into the Jamf Pro server. The labels will make more sense once you read what they do below.

Parameter 4: Full OS Installer Path (or alternatively “Installer Path (e.g. /Users/”
Parameter 5: Time in Minutes (e.g. 45)
Parameter 6: Custom Trigger
Parameter 7: Post Upgrade Package Path
Parameter 8: Min OS Installer app ver (e.g. 10.12.4)
Parameter 9: IT Contact Info


Required: Parameter $4 is used to determine the full macOS installer app path. Enter the full path such as /Applications/Installer macOS or /Users/Shared/Installer macOS

Required: Parameter $5 is for the time estimate in minutes. Type out the time in minutes. Keep in mind, if you’re forcing the user to download the installer before the upgrade, you might need to take into account how long it would take to download depending on your network. I’d recommend testing a few times throughout different days to get an idea of how long it might take. Since Sierra, I’ve noticed the OS upgrade process take at least 30-45 minutes on average, but could be longer in extreme cases. It does seem that Mojave has been the longest in terms of upgrade time so far.

Optional: Parameter $6 is for the custom trigger name of a policy that should be used to deploy the macOS installer app. If you do not fill this in and the macOS installer app has not been installed on the computer, the script will exit and warn the user. You can opt to not use this parameter, but just make sure to deploy the macOS installer app through other means if that’s the route you choose to take.

Optional: Parameter $7 is used if you want to add an additional install package to install after the OS upgrade completes. This is done through the “–installpackage” option which was introduced in the macOS High Sierra installer app. Read the following blog for more details on the caveats with this option:

Optional: Parameter $8 is used to provide a minimum OS version required to upgrade from. For example, you cannot upgrade to 10.12 on a computer running anything lower than 10.7.5. This is optional but if used it should be in the format of 10.7.5. Note: Some versions of macOS are written as 10.12 or 10.11 instead of 10.12.0 or 10.11.0. To verify the version number, run this command in Terminal: sw_vers -productVersion.

Optional: Parameter $9 is used to provide the contact email, number or name of the IT department for the end user to reach out to should issues arise. The phrase will be “Please contact <insert info from parameter 9>”

Exit Codes

In case you want to examine why the script may have failed, I’ve provided exit codes:

1: Missing JSS parameters that are required.
2: Minimum required OS value has been provided and the client’s OS version is lower.
3: Invalid OS version value. Must be in form of 10.12.4
4: No power source connected.
5: macOS Installer app is missing “InstallESD.dmg” & “startosinstall”. Due to 1) bad path has been provided, 2) app is corrupt and missing two big components, or 3) app is not installed.
7: Invalid value provided for free disk space.
8: The minimum OS version required for the macOS installer app version on the client is greater than the macOS installer on the computer.
9: The startosinstall exit code was not 0 or 255 which means there has been a failure in the OS upgrade. See log at: /var/log/installmacos_{timestamp}.log and /var/log/install.log and /var/log/system.log
11: Insufficient free space on computer.
14: Remote users are logged into computer. Not secure when using FV2 Authenticated Restart.
16: FV2 Status is not Encrypted.
17: Logged in user is not on the list of the FileVault enabled users.
18: Password mismatch. User may have forgotten their password.
19: FileVault error with fdesetup. Authenticated restart unsuccessful.
20: Install package to run post-install during OS upgrade does not have a Product ID. Build distribution package using productbuild. More info:
21: CoreStorage conversion is in the middle of conversion. Only relevant on non-APFS. See results of: diskutil cs info /
22: Failed to unmount InstallESD. InstallESD.dmg may be mounted by the installer when it is launched through the GUI. However if you quit the GUI InstallAssistant, the app fails to unmount InstallESD which can cause problems on later upgrade attempts.
23: Expired certificate found in macOS installer app
24: Expired certificate found in one of packages inside macOS installer’s InstallESD.dmg

Self Service Policy Creation

First upload the script to your Jamf Pro server. This is documented in the admin guide. In short, you have two options: 1) upload the script through Jamf Admin or 2) upload it through the Jamf Pro web interface. You will also want to add labels to the parameters for the script. Once uploaded you are ready to create your Self Service policy.

Create a new policy to run the upgrade script. Here is what the policy should include:

  • Policy payload:
    • Script: Add the script and set priority to run “After”.
    • Maintenance: enable “Inventory Update”.
    • Packages (Optional): include the install package to run after the OS upgrade completes and set the application to “Cache”. Remember, this is only supported on macOS 10.13+ installers.
  • Trigger: None. This is a Self Service policy.
  • Execution Frequency: Ongoing
  • Scope: Scope to a computer group for computers that “Eligible to upgrade to macOS XXXXX”. See Smart Group/Scope section for more details.

Screen Shot 2019-11-12 at 3.09.07 PM.png

In terms of smart groups, you would want to scope it to the computers that are supported by the OS installer you are using. The “OS Version” is a good criteria to use in the smart group as it lets you do version comparisons.

In the Self Service tab, what you fill in here will largely be up to you. I’m just sharing what I have as a point of reference.

Screen Shot 2019-04-08 at 4.02.10 AM.png

Note: The message message for Self Service was written based on our requirements. Computers are encrypted with FileVault 2 hence the line about potentially needing to authenticate on restart. Again, this is just a point of reference.

In case you aren’t familiar with uploading icons to the JSS, on JAMF Nation there are a couple of discussions that may get you started. In short, most applications have the icons located in …/ You will then have to convert it to a PNG. Open it up in the Preview app which should let you export a PNG. Some sites online will do this for free. Or you can use some of the other methods described in that discussion I linked to.

Smart Group/Scope

This is the second smart group we are creating. It is a little simpler and would be used for the scope of the second policy. Since this is the policy that we are using in Self Service we really just want to make sure of one thing and that is that the computer is not on macOS 10.12 already.

Another important consideration is that not all hardware can be upgraded. Make sure you look up the system requirements for the operating system you wish to upgrade your computers to.

Adjust the criteria for your smart group accordingly based on any other requirements you might have.

The same network considerations that I wrote above would apply here as well if you decide to have the app installer download right when the end-user runs the Self Service policy.

JAMF Helper dialogs

Below are some screenshots of what the user will see if they encounter errors and to let them know the progress.

Let’s start with what the user will see if everything is a success.

Download in progress:


Upgrade in progress:

Upgrade in Progress Alt.png

Alternative of the last screenshot:

Upgrade In Progress.png

If a download is not required, the dialog will not display (2 of 2). Attention to the small details. 😉

And now for the error messages…

Download Failure:

Download Failure.png

Generic Error:

Generic Error.png

When you see a generic error message, try to check out the exit code in the policy log. It’ll provide detail that you’ll understand, but would be confusing to provide to the user. You can now also check out a log that is created with the output of startosinstsall. The log is created at /var/log/installmacos_<timestamp>.log

Insufficient Space:

Insufficient Space.png

No Power Source:

No Power.png

Unsupported OS:

Unsupported OS.png

Upgrade Failure:

Upgrade Failure.png


Surprises are never good and communication is key on these type of roll outs. Just to reiterate, be sure to speak with your network team in advance to determine the best way to initiate the deployment of this large installer. Additionally, before the upgrade is available send out communication to the company that is clear and succinct on how to go about upgrading and some basic expectations and requirements for them to upgrade. You might even want to create a survey to get feedback from your end-users on how their upgrade experience went so you can try to improve on it for the next time.

On my most recent communication, I created a video which I made using screen recording and a virtual machine. It captured the expected process quite nicely and I was able to condense the video from 45+ minutes to a little over minute using iMovie.

Download the script

The script referenced above can be downloaded from my GitHub repo. Lastly, I recommend you test this out and try to come up with scenarios where the script may fail. I’ve been using this script and updating it since OS X 10.11. I’ve usually focused my testing efforts on making sure at least the last major OS is supported, but maybe you’re dealing with older operating systems in which case testing would be more critical. I’ve tried to break it myself and went through quite a few scenarios that I could think of. I will gladly take any feedback in the comments below and pull requests on GitHub.

2 thoughts on “Revisiting: A macOS upgrade method using Jamf Pro Self Service

  1. Hi babodee,

    Thanks for the detailed article. I am using this for upgrade of our sytems. Everything for me works except that jamf binar doesn’t upload policy results to JSS. I checked the log “/var/log/installmacos”. It prepares the upgrade and shows that requires 30 seconds to reboot but log is not uploaded.

    The installation is successful though. I modified the paramaters and some jamf helper dialog boxes for users. I don’t know what I am missing.


    1. The reason the policy log doesn’t get uploaded is because the computer gets restarted before it ever gets a chance to upload the results to the Jamf Pro server. If you this this is a problem you could modify the script so that it doesn’t force quit apps on 10.15. However, I personally don’t care that a policy log is generated. My concern is mostly on getting a log entry generated when a policy fails so I can understand what caused the failure. And of course the even more important thing I care about is that the device upgrades successfully which I find out after 30 minutes or so after it goes through its process.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s