There are quite a few methods that people use to make macOS updates available to their end users. My method takes a little inspiration from those posts with a few differences. This time around I wanted to use the macOS installer app from Apple which has a neat little command line tool call startosinstall. There was no particular reason to use this method other than there were no requirements to install any particular packages post-install which you can do with a tool like createOSinstallerPKG. We had a few requirements:
- Computer has sufficient free drive space.
- User is not logged in to avoid the new iCloud Drive Document Sync feature.
- Ensure the user is plugged into a power source.
- Provide dialogs to give the user feedback such as a time estimate and dialogs on what to expect next.
- Make use of the JSS parameter to allow for customization and potential re-use for future operating systems.
Before we begin, note that the following was designed with JSS v9.97. Older versions of the JSS may not have certain features that the newer versions have.
There are two policies that this process was designed with in mind, but you can do this in one policy. 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. I am going to cover both policies. And each policy with have their own smart groups; we will also create 2 smart groups.
Downloading the installer is the first part we want to take care of. There are different ways to do this:
- You can manually download from the Mac App Store and then package the app up using your packaging tool of choice.
- Grab the original package installer from Apple as described in this post by Rich Trouton.
- You can use device-based VPP to download the macOS installer app to the client using your MDM service (in this case Jamf Pro).
I’m personally a fan of method number 2, but method 1 works too for this workflow. Method number 3 would mean that you do not have a policy setup to download the macOS installer. It also means that should the macOS installer app become corrupt, you will not be able to attempt a re-download when the script (the second policy) runs.
Once you’ve got the package, upload it to your JSS Distribution Point through Casper Admin. Let’s create the policy to deploy that package.
Deploy macOS Installer app
macOS Installer App Deployment Policy
The first step is to create our first policy to deploy the macOS installer app. The policy has 2 payloads and should be scoped to the smart group we created earlier. The trigger should be custom with the name of your choosing (e.g. os1012upgrade) and recurring at check-in (optional; read further on the network considerations). The execution frequency should be Ongoing. The Packages payload should be “Install” and the Maintenance payload should have “Inventory Update” enabled.
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 may have specific requirements that you want to meet before deploying the installer, but at the very least this should be in your criteria.
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 certain installers being on the system in which case you would change the criteria. 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 Sierra.app/Contents/Info.plist" 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.
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 where the network will hopefully have less going on. And if your JSS/DP is available outside the office with a DP then maybe you setup 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 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, this is the method we went with and 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.
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. 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 script where the magic comes all together. It makes use of ALL the custom JSS 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 if I’m sharing it.
Naturally, this script is meant to be used with Jamf Pro. It will make use of macOS Sierra installer app’s command line tool “startosinstall” to initiate the upgrade. It also makes use of some JSS script parameters and is intended to be somewhat easy to modify if used with future OS deployments. Certain JSS 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 JSS 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 JSS. The labels will make more sense once you read what they do below.
Parameter 4: Free Space Required in GB (e.g. 20)
Parameter 5: Application Name (e.g. macOS Sierra)
Parameter 6: Time in Minutes (e.g. 30)
Parameter 7: Custom Trigger
Parameter 8: Min Required OS ver (e.g 10.10.5)
Parameter 9: iCloud Drive Check (Yes or leave blank)
Parameter 10: Full OS Installer Path (or alternatively “Installer Path (e.g. /Users/Install.app)”
Parameter 11: Min OS Installer app ver (e.g. 10.12.4)
Required: Parameter $4 is for the required free space. Enter space in gigabytes such as 20. In this case, 20 would equal 20 gigabytes. Enter full integers. My personal experience has been that you should have about 20 gigabytes available.
Required: Parameter $5 is for the app name (e.g. macOS Sierra) which should hopefully allow some flexibility for the admin in case this script continues to work with the next macOS release. If you want to call it something else, feel free.
Required: Parameter $6 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. The upgrade process should take at least 30-35 minutes, but could be longer in extreme cases.
Optional: Parameter $7 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 be 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 $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 determine if you want to check if iCloud Drive is enabled. Type “YES” (no quotes, case-insensitive) if you want to check otherwise leave blank. This script will check if iCloud Drive is in use. The reason for this is that macOS Sierra has a iCloud Documents sync feature which will upload documents to iCloud Drive. You may want the user to disable iCloud Drive and then push out a configuration profile to disable the iCloud Documents sync feature. This way when they re-enable iCloud Drive they do not have the option of possibly turning that specific feature on. The ability to block this feature via a configuration profile is introduced in 10.12.4.
Required: Parameter $10 is used to determine the full macOS installer app path. Enter the full path such as /Applications/Installer macOS Sierra.app or /Users/Shared/Installer macOS Sierra.app.
Optional: Parameter $11 is used to determine the minimum macOS installer app version just in case you want a specific version of the installer app on the computer.
This comes in handy in situations where the computer might have the macOS 10.12.2 installer, but you want the 10.12.4 installer at minimum on the computer.
Why? Apple may have released a specific feature (e.g. disable iCloud Doc Sync via config profile in 10.12.4+) that is in a newer minor update that you want to make use of immediately after computer has been upgraded.
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.
6: iCloud Drive is enabled. User must disable it.
7: Invalid value provided for free disk space.
8: The minimum OS version in macOS installer app version on the client is greater than the macOS installer on the computer.
9: The startosinstall exit code was not 0 which means there has been a failure in the OS upgrade.
10: The minimum OS version in macOS installer app has been supplied and we were unable to mount the disk image to determine the OS version.
Self Service Policy Creation
First upload the script to your JSS. This documented in the admin guide. In short, you have two options: 1) upload the script through Casper Admin or 2) upload it through the JSS 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.
The Self Service policy would make use of two payloads. The first is the script payload and the second is the Maintenance payload (make sure to check “Update Inventory”) so that the JSS can upload the results of the policy. There isn’t a Restart payload used in this policy because the OS installer takes care of restarting.
In terms of smart groups, you would use the same smart group that you used in the first policy we created earlier. 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.
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 …/MyGreatApp.app/Content/Resources/MyNiceIcon.icns. You will then have to convert it to 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.
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. Again, adjust 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 they run 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:
Alternative of the last screenshot:
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…
When you see a generic error message, try to check out the exit code. It’ll provide detail that you’ll understand, but would be confusing to provide to the user.
iCloud Disable Error (White):
iCloud Disable Error (Blue):
No Power Source:
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 this download. 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.
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 only focused my testing in 10.11 because all our computers are on that OS, 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.
EDIT: 3/23/17 I had to make some edits to this blog post because certain things were not included, unclear, or missing on steps such as creating the 2 smart groups (originally posted this with only 1 smart group example and forgot the second example.