Slides from the “macOS application packaging 101” session at Penn State MacAdmins 2024

For those who wanted a copy of my installer packaging talk at Penn State MacAdmins 2024, here are links to the slides in PDF and Keynote format.

WWDC 2024 notes

This week, as in previous years since 2020, I attended Apple’s WWDC 2024 conference from the comforts of home. As part of this, I took notes during the labs and session videos. Due to wanting to stay on the right side of Apple’s NDA, I’ve posted my notes to Apple’s developer forums rather than to here.

To make it easier for Mac admins to access them, I’ve set up a post in the forums where I’ve linking the various forum posts with my notes. It’s available via the link below:

https://forums.developer.apple.com/forums/thread/758357

Update 6-17-2024: The content posted to the Apple developer forums is no longer available. I have received no communication from Apple as to why it is no longer available, but my assumption is that posting the content was objectionable and subsequently removed.

As the Apple developer forums were the only location that I felt were safe to post this content, and because I have no desire to speak with Apple Legal about this issue, I will not be sharing the notes I took at WWDC to any other locations. Instead, I would encourage interested parties to watch the session videos for the two sessions I took notes in:

 

Update 6-28-2024: The notes are back! The link above has been updated to point to a new post in the forums where I’ve linked the various forum posts with my notes.

I learned about the posts being restored via a message from Apple that the notes were restored, along with an apology for their previous removal. Apparently they were flagged as being spam, which is what prompted them to be removed.

Thanks to Apple for restoring them, and also for reaching out to let me know!

Building Jamf Pro smart groups for Sequoia-compatible and Sequoia-incompatible Mac models

As part of preparing for macOS Sequoia, it may be useful to have a way to easily distinguish between the Macs in your fleet which can run macOS Sequoia and those which can’t. Apple has published the following list of Macs which are compatible with Sequoia, which will help with both identifying the compatible Mac models as well as the incompatible Mac models. 

  • iMac: 2019 and later models
  • iMac Pro: All models
  • Mac Studio: All models
  • MacBook Pro: 2018 and later models
  • MacBook Air: 2020 and later models
  • Mac Mini: 2018 and later models
  • Mac Pro: 2019 and later models

From there, here’s the list of Mac models which are compatible with macOS Sequoia:


Mac13,1
Mac13,2
Mac14,10
Mac14,12
Mac14,13
Mac14,14
Mac14,15
Mac14,2
Mac14,3
Mac14,5
Mac14,6
Mac14,7
Mac14,8
Mac14,9
Mac15,10
Mac15,11
Mac15,12
Mac15,13
Mac15,3
Mac15,4
Mac15,5
Mac15,6
Mac15,7
Mac15,8
Mac15,9
MacBookAir10,1
MacBookAir9,1
MacBookPro15,1
MacBookPro15,2
MacBookPro15,3
MacBookPro15,4
MacBookPro16,1
MacBookPro16,2
MacBookPro16,3
MacBookPro16,4
MacBookPro17,1
MacBookPro18,1
MacBookPro18,2
MacBookPro18,3
MacBookPro18,4
MacPro7,1
Macmini8,1
Macmini9,1
VirtualMac2,1
iMac19,1
iMac19,2
iMac20,1
iMac20,2
iMac21,1
iMac21,2
iMacPro1,1

 

We can use this information to build smart groups which can help identify which Macs are compatible with Sequoia and which are not. For more details, see below the jump:

 

 

Using the information mentioned above, I was able to build two smart groups, one which displays compatible Macs and the other which displays incompatible Macs.

 

The compatible Macs’ smart group checks for if the Mac in question’s model identifier is any of the model identifiers which are compatible with Sequoia:

 


<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
<name>Macs compatible with macOS Sequoia</name>
<is_smart>true</is_smart>
<criteria>
<size>51</size>
<criterion>
<name>Model Identifier</name>
<priority>0</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac13,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>1</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac13,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>2</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,10</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>3</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,12</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>4</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,13</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>5</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,14</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>6</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,15</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>7</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>8</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>9</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,5</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>10</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,6</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>11</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>12</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,8</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>13</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac14,9</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>14</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,10</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>15</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,11</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>16</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,12</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>17</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,13</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>18</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>19</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>20</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,5</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>21</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,6</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>22</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>23</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,8</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>24</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Mac15,9</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>25</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>26</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookAir9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>27</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>28</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>29</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>30</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>31</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>32</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>33</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>34</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro16,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>35</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro17,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>36</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>37</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>38</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>39</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacBookPro18,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>40</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>MacPro7,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>41</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Macmini8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>42</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>Macmini9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>43</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>VirtualMac2,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>44</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac19,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>45</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac19,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>46</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac20,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>47</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac20,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>48</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac21,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>49</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMac21,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>50</priority>
<and_or>or</and_or>
<search_type>is</search_type>
<value>iMacPro1,1</value>
</criterion>
</criteria>
<computers/>
</computer_group>

 

The incompatible Macs’ smart group checks for if the Mac in question’s model identifier is not any of the model identifiers which are compatible with Sequoia:

 


<?xml version="1.0" encoding="UTF-8"?>
<computer_group>
<name>Macs incompatible with macOS Sequoia</name>
<is_smart>true</is_smart>
<criteria>
<size>51</size>
<criterion>
<name>Model Identifier</name>
<priority>0</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac13,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>1</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac13,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>2</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,10</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>3</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,12</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>4</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,13</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>5</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,14</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>6</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,15</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>7</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>8</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>9</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,5</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>10</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,6</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>11</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>12</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,8</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>13</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac14,9</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>14</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,10</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>15</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,11</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>16</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,12</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>17</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,13</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>18</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>19</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>20</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,5</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>21</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,6</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>22</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,7</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>23</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,8</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>24</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Mac15,9</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>25</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir10,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>26</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookAir9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>27</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>28</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>29</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>30</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro15,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>31</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>32</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>33</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>34</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro16,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>35</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro17,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>36</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>37</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>38</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,3</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>39</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacBookPro18,4</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>40</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>MacPro7,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>41</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Macmini8,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>42</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>Macmini9,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>43</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>VirtualMac2,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>44</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac19,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>45</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac19,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>46</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac20,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>47</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac20,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>48</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac21,1</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>49</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMac21,2</value>
</criterion>
<criterion>
<name>Model Identifier</name>
<priority>50</priority>
<and_or>and</and_or>
<search_type>is not</search_type>
<value>iMacPro1,1</value>
</criterion>
</criteria>
<computers/>
</computer_group>

 

To upload these smart group XML files to a Jamf Pro server server using the API, download the XML file to a convenient location, then run the commands shown below (substituting your Jamf Pro server and Jamf Pro user account information as appropriate):

Note: You will first need to get an API bearer token for authentication. Use the commands below (depending on which OS you’re using) to obtain a bearer token from your Jamf Pro server:

 

Get API bearer token on macOS Monterey and later:


curl -X POST -u username:password -s https://server.name.here/api/v1/auth/token | plutil -extract token raw –

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

Get API bearer token on macOS Big Sur and earlier:


curl -X POST -u username:password -s https://server.name.here/api/v1/auth/token | python -c 'import sys, json; print json.load(sys.stdin)["token"]'

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

 

The bearer token should look something like this:


eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiODQwNzBjZjctOGYwNS00N2NhLTliNWItZjU3YzYwYTY2ZGIwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxMSIsImV4cCI6MTY2NTYwMTUzMX0.R8grAtlzG1raZw95HJqiLyxZavf03SwFqbgfb3eVSgg

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

 

Once you have the bearer token, use to authenticate the API command as shown below:


curl -sf https://jamfpro.server.here/JSSResource/computergroups/id/0 -T /path/to/filename.xml -X POST -H "Authorization: Bearer API_Bearer_Token_Goes_Here"

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

If the API bearer token is the one shown above, the API command should look something like this:


curl -sf https://jamfpro.server.here/JSSResource/computergroups/id/0 -T /path/to/filename.xml -X POST -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdXRoZW50aWNhdGVkLWFwcCI6IkdFTkVSSUMiLCJhdXRoZW50aWNhdGlvbi10eXBlIjoiSlNTIiwiZ3JvdXBzIjpbXSwic3ViamVjdC10eXBlIjoiSlNTX1VTRVJfSUQiLCJ0b2tlbi11dWlkIjoiODQwNzBjZjctOGYwNS00N2NhLTliNWItZjU3YzYwYTY2ZGIwIiwibGRhcC1zZXJ2ZXItaWQiOi0xLCJzdWIiOiIxMSIsImV4cCI6MTY2NTYwMTUzMX0.R8grAtlzG1raZw95HJqiLyxZavf03SwFqbgfb3eVSgg"

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

 

If the smart group was successfully uploaded, you should next see output similar to that shown below:


<?xml version="1.0" encoding="UTF-8"?><computer_group><id>95</id></computer_group>

view raw

gistfile1.txt

hosted with ❤ by GitHub

 

The new smart group should now be present on the Jamf Pro server.

Using Wi-Fi hardware network interface information with Jamf Pro to identify macOS virtual machines

A while back, I had a deployment requirement where I could deploy a specific something to regular Macs but under no circumstances could I deploy it to virtual machines. The reason was that someone could install the thing on a virtual machine, then copy the virtual machine’s files elsewhere.

The issue I was looking at was how to reliably identify a virtual machine. There are various virtualization solutions for macOS available and the macOS virtual machines hosted by them all report various information for model, serial number and other characteristics. However, one thing that macOS virtual machines should have in common regardless of virtualization solution is that they don’t have a hardware Wi-Fi interface. When you’re a VM, it’s an Ethernet world. Meanwhile, every single real Mac for decades has had Wi-Fi hardware installed. Even if it’s never used by the Mac, that Wi-Fi hardware interface should be there.

We can use this with Jamf Pro to help identify macOS virtual machines. For more details, please see below the jump.

Here’s an example Jamf Pro Extension Attribute:


#!/bin/bash
# Check_wifi_installation.sh
# Checks if there is a WiFi hardware network interface. Returns 1 if one or more WiFi
# hardware network interfaces are detected, otherwise returns 0.
WiFiInstalled=$(/usr/sbin/networksetup listallhardwareports | /usr/bin/grep -Ecm1 'Wi-?Fi')
echo "<result>$WiFiInstalled</result>"
exit 0

 

In this case, we’re using the networksetup tool’s listallhardwareports function and using it to filter for any hardware network interface with Wi-Fi (or similar enough to match) as part of the interface’s returned information. 

With the example EA, if at least one matching result is found, the following output is returned:

1

 

Otherwise, the following output is returned:

0

 

You can then use the returned result in a Jamf Pro smart group or advanced computer search to help you identify the macOS virtual machines enrolled with your Jamf Pro server.

Volume ownership and Erase All Contents and Settings on macOS Sonoma

A colleague ran into a problem recently where they tried to run the Erase All Content and Settings (EACAS) function on an Apple Silicon Mac. Instead of erasing the Mac, instead the following error message was displayed.

Erase Assistant is not supported on this Mac

The error message was misleading however, because the Mac actually supported EACAS without a problem. The root problem was the user account which was logged in had the following characteristics:

  • Had administrator rights
  • Did not have volume ownership

macOS on Apple Silicon Macs includes a concept known as volume ownership. You must be a volume owner to perform the following tasks on an Apple Silicon Mac:

  • Make changes to startup security policy for a specific install of macOS.*
  • Be able to authorize the installation of macOS software updates or macOS upgrades.
  • Authorize running Erase All Contents and Settings.

* There may be multiple installations of macOS on one Apple Silicon Mac; each macOS install would have their own startup security policy.

For more information on volume ownership, please see Apple’s Platform Deployment article linked below:

https://support.apple.com/guide/deployment/use-secure-and-bootstrap-tokens-dep24dbdcf9e/web (see the Volume ownership section.)

In this case, since the account in question did not have volume ownership, it couldn’t run EACAS. Fortunately for my colleague, there was another account on the Mac which did have the following characteristics:

  • Had administrator rights
  • Had volume ownership

Once they logged into that account and ran the EACAS function, this time EACAS worked fine and the Mac was successfully wiped.

Basic Authentication authentication now disabled for the Jamf Pro Classic API as of Jamf Pro 11.5.0

As a follow-up to my earlier post on Basic Authentication being deprecated for the Jamf Pro Classic API (first announced as part of the release of Jamf Pro 10.35.0), Jamf has disabled Basic Authentication support for the Jamf Pro Classic API as Jamf Pro 11.5.0.

One thing to note is that this change does not impact Cisco ISE integration, which also uses Basic Authentication.

For those looking now to transition away from Basic Authentication for connecting to the Jamf Pro Classic API, I’ve written a couple of shell script functions to assist with getting the bearer tokens now used for API authentication for the following categories:

For more details, please see below the jump.

Jamf Pro API clients:


#!/bin/bash
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The Jamf Pro URL
# An API client ID on the Jamf Pro server with sufficient API privileges
# The API client secret for the API client ID
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the Jamf Pro API Client ID here if you want it hardcoded.
jamfpro_api_client_id=""
# Set the Jamf Pro API Client Secret here if you want it hardcoded.
jamfpro_api_client_secret=""
# If you do not want to hardcode API information into the script, you can also store
# these values in a ~/Library/Preferences/com.github.jamfpro-info.plist file.
#
# To create the file and set the values, run the following commands and substitute
# your own values where appropriate:
#
# To store the Jamf Pro URL in the plist file:
# defaults write com.github.jamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
#
# To store the Jamf Pro API Client ID in the plist file:
# defaults write com.github.jamfpro-info jamfpro_api_client_id api_client_id_information_goes_here
#
# To store the Jamf Pro API Client Secret in the plist file:
# defaults write com.github.jamfpro-info jamfpro_api_client_secret api_client_secret_information_goes_here
#
# If the com.github.jamfpro-info.plist file is available, the script will read in the
# relevant information from the plist file.
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url)
fi
if [[ -z "$jamfpro_api_client_id" ]]; then
jamfpro_api_client_id=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_api_client_id)
fi
if [[ -z "$jamfpro_api_client_secret" ]]; then
jamfpro_api_client_secret=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_api_client_secret)
fi
fi
# If the Jamf Pro URL, the API Client ID or the API Client Secret aren't available
# otherwise, you will be prompted to enter the requested URL or API client credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_api_client_id" ]]; then
read -p "Please enter your Jamf Pro API client ID : " jamfpro_api_client_id
fi
if [[ -z "$jamfpro_api_client_secret" ]]; then
read -p "Please enter the API client secret for the $jamfpro_api_client_id API ID client: " -s jamfpro_api_client_secret
fi
echo ""
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
GetJamfProAPIToken() {
# This function uses the API client ID and client ID secret to get a new bearer token for API authentication.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl -s -X POST "$jamfpro_url/api/oauth/token" –header 'Content-Type: application/x-www-form-urlencoded' –data-urlencode client_id="$jamfpro_api_client_id" –data-urlencode 'grant_type=client_credentials' –data-urlencode client_secret="$jamfpro_api_client_secret" | python -c 'import sys, json; print json.load(sys.stdin)["access_token"]')
else
api_token=$(/usr/bin/curl -s -X POST "$jamfpro_url/api/oauth/token" –header 'Content-Type: application/x-www-form-urlencoded' –data-urlencode client_id="$jamfpro_api_client_id" –data-urlencode 'grant_type=client_credentials' –data-urlencode client_secret="$jamfpro_api_client_secret" | plutil -extract access_token raw -)
fi
}
APITokenValidCheck() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null "${jamfpro_url}/api/v1/auth" –request GET –header "Authorization: Bearer ${api_token}")
}
GetJamfProAPIToken
APITokenValidCheck
echo "$api_authentication_check"
echo "$api_token"

Jamf Pro user accounts:


#!/bin/bash
# This script uses the Jamf Pro API to get an authentication token
# Set default exit code
exitCode=0
# Explicitly set initial value for the api_token variable to null:
api_token=""
# Explicitly set initial value for the token_expiration variable to null:
token_expiration=""
# If you choose to hardcode API information into the script, set one or more of the following values:
#
# The username for an account on the Jamf Pro server with sufficient API privileges
# The password for the account
# The Jamf Pro URL
# Set the Jamf Pro URL here if you want it hardcoded.
jamfpro_url=""
# Set the username here if you want it hardcoded.
jamfpro_user=""
# Set the password here if you want it hardcoded.
jamfpro_password=""
# Read the appropriate values from ~/Library/Preferences/com.github.jamfpro-info.plist
# if the file is available. To create the file, run the following commands:
#
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url https://jamf.pro.server.here
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user API_account_username_goes_here
# defaults write $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password API_account_password_goes_here
#
if [[ -f "$HOME/Library/Preferences/com.github.jamfpro-info.plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read $HOME/Library/Preferences/com.github.jamfpro-info jamfpro_password)
fi
fi
# If the Jamf Pro URL, the account username or the account password aren't available
# otherwise, you will be prompted to enter the requested URL or account credentials.
if [[ -z "$jamfpro_url" ]]; then
read -p "Please enter your Jamf Pro server URL : " jamfpro_url
fi
if [[ -z "$jamfpro_user" ]]; then
read -p "Please enter your Jamf Pro user account : " jamfpro_user
fi
if [[ -z "$jamfpro_password" ]]; then
read -p "Please enter the password for the $jamfpro_user account: " -s jamfpro_password
fi
echo
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
GetJamfProAPIToken() {
# This function uses Basic Authentication to get a new bearer token for API authentication.
# Use user account's username and password credentials with Basic Authorization to request a bearer token.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl -X POST –silent -u "${jamfpro_user}:${jamfpro_password}" "${jamfpro_url}/api/v1/auth/token" | plutil -extract token raw -)
fi
}
APITokenValidCheck() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
api_authentication_check=$(/usr/bin/curl –write-out %{http_code} –silent –output /dev/null "${jamfpro_url}/api/v1/auth" –request GET –header "Authorization: Bearer ${api_token}")
}
CheckAndRenewAPIToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, it is used to connect to the keep-alive endpoint. This will
# trigger the issuing of a new bearer token and the invalidation of the previous one.
if [[ $(/usr/bin/sw_vers -productVersion | awk -F . '{print $1}') -lt 12 ]]; then
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | python -c 'import sys, json; print json.load(sys.stdin)["token"]')
else
api_token=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/keep-alive" –silent –request POST –header "Authorization: Bearer ${api_token}" | plutil -extract token raw -)
fi
else
# If the current bearer token is not valid, this will trigger the issuing of a new bearer token
# using Basic Authentication.
GetJamfProAPIToken
fi
}
InvalidateToken() {
# Verify that API authentication is using a valid token by running an API command
# which displays the authorization details associated with the current API user.
# The API call will only return the HTTP status code.
APITokenValidCheck
# If the api_authentication_check has a value of 200, that means that the current
# bearer token is valid and can be used to authenticate an API call.
if [[ ${api_authentication_check} == 200 ]]; then
# If the current bearer token is valid, an API call is sent to invalidate the token.
authToken=$(/usr/bin/curl "${jamfpro_url}/api/v1/auth/invalidate-token" –silent –header "Authorization: Bearer ${api_token}" -X POST)
# Explicitly set value for the api_token variable to null.
api_token=""
fi
}
GetJamfProAPIToken
APITokenValidCheck
echo "$api_authentication_check"
echo "$api_token"
CheckAndRenewAPIToken
APITokenValidCheck
echo "$api_authentication_check"
echo "$api_token"
InvalidateToken
APITokenValidCheck
echo "$api_authentication_check"
echo "$api_token"

Fix for Safari crashing on macOS Sonoma when selecting Manage Website Data option

A colleague ran into an issue recently where he was trying to clear out cached data using Safari’s Manage Website Data… option.

When they clicked the button, the expected behavior was that a new window would appear where they could find and remove the data in question.

Instead Safari crashed.

After some digging, we found a fix for the issue via a comment on this Reddit post:

I had the exact same problem. I was able to fix it by checking and unchecking “Block all cookies” checkbox above the “Manage Website Data” button. Afterward, that button started working again.

https://www.reddit.com/r/applehelp/comments/vhou48/comment/j0h6whs

For more details, please see below the jump.

If you’re also seeing this problem, please follow the process below to fix it:

1. Launch Safari

2. Under the Safari menu, click Settings.

3. Click on the Advanced option.

4. In the Advanced window, perform the following actions:

  • Check Block all cookies if unchecked, or uncheck Block all cookies if checked.
  • Uncheck Block all cookies if checked, or check Block all cookies if unchecked.

5. Click on the Privacy option.

6. Click the Manage Website Data button.

The relevant window should now open in place of Safari crashing.

Full disclosure: I don’t fully understand why the fix described above works. My working theory is that Safari has an incomplete or corrupted settings file when it crashes and forcing an update to the relevant settings file via selecting and unselecting the Block all cookies setting fixes the issue.

Losing a giant

I learned today that Charles Edge, one of the smartest people I’ve been privileged to know in my life, has passed away. I don’t know more details, but we have lost a kind and magnificent giant in the Mac Admins community and I have lost a good friend. 

It hits all the harder in that his death is so unexpected. Charles was one of the most alive people I knew. He wrote countless books (including a couple editions of Apple Device Management with me as co-author), either ran or appeared in multiple podcasts, had a full time job, contributed multiple open source tools and despite all of that always seemed to have time to talk. My regret is that I didn’t talk to him more often, with our last conversation happening in February.

I already miss you terribly, man. Wherever you are now, I hope you know how much you are loved and missed here.

If you have good memories of Charles, please share them in the comments below.

Updated scripts for downloading packages from a JCDS2 distribution point

As part of an earlier post, I had provided scripts for downloading installer packages from a JCDS2 distribution point. I’ve made some updates to the scripts to provide more checking of the installer packages, to hopefully ensure that the downloaded installer package and the package available in the JCDS2 distribution point are exactly the same package.

The original scripts would check the download directory and see if there was an installer package with the same name as an installer package in the JCDS2 distribution point. If there was an installer package with a matching name, download of the installer package was skipped and the script would move on to the next installer package.

In the updated scripts, if there are installer packages already in the download directory which have the same name as an installer package in the JCDS distribution point, the MD5 hash of the existing installer package is checked against the MD5 hash of the installer package stored in the JCDS distribution point. 

If the MD5 hashes match, download of the installer package with the matching name is skipped. If the MD5 hashes do not match, the existing installer package in the download directory is deleted and a fresh copy of the installer package is downloaded. For more details, please see below the jump.

Usage: 

/path/to/Jamf_Pro_JCDS_Installer_Package_Download.sh

The script takes the following actions:

  • Creates a download directory if none has been specified in the script.
  • Uses the Jamf Pro Classic API to download the list of installer packages from the Jamf Pro server.
  • Gets the Jamf Pro ID numbers for the individual installer packages.
  • Uses the Jamf Pro Classic API to get the names of the individual installer packages.
  • Checks to see if a file with a matching name exists in the download directory.
  • If a file with a matching name does not exist in the download directory, use the Jamf Pro API to query the JCDS2 distribution point for the download URL of the installer package and download the installer package.
  • If a file with a matching name exists in the download directory, check the MD5 hash of the file with the matching name and compare it against the MD5 hash of the file in the JCDS2 distribution point. 
    •  If the MD5 hashes match, display a message that the file exists in the download directory. 
    •  If they don’t match, delete the file with the matching name from the download directory, use the Jamf Pro API to query the JCDS2 distribution point for the download URL of the installer package and download the installer package.

The script should provide output similar to this:


username@computername ~ % /path/to/Jamf_Pro_JCDS_Installer_Package_Download.sh
Please enter your Jamf Pro server URL : https://server_name_here.jamfcloud.com
Please enter your Jamf Pro user account : username_goes_here
Please enter the password for the username_goes_here account:
Downloading Google_Chrome_121.0.6167.184.pkg to /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
################################################################################################################################################################# 100.0%
Google_Chrome_121.0.6167.184.pkg is available in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Microsoft_Edge_122.0.2365.92.pkg found in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Checking MD5 hash of Microsoft_Edge_122.0.2365.92.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR to verify match with Microsoft_Edge_122.0.2365.92.pkg on https://server_name_here.jamfcloud.com&#8230;
MD5 hash of Microsoft_Edge_122.0.2365.92.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR does not match Microsoft_Edge_122.0.2365.92.pkg on https://server_name_here.jamfcloud.com.
Deleting Microsoft_Edge_122.0.2365.92.pkg from /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Downloading Microsoft_Edge_122.0.2365.92.pkg to /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
######################################################################################################################################### 100.0%
Microsoft_Edge_122.0.2365.92.pkg is available in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Microsoft_Office_16.83.pkg found in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Checking MD5 hash of Microsoft_Office_16.83.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR to verify match with Microsoft_Office_16.83.pkg on https://server_name_here.jamfcloud.com&#8230;
MD5 hash of Microsoft_Office_16.83.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR matches Microsoft_Office_16.83.pkg on https://server_name_here.jamfcloud.com.
Microsoft_Office_16.83.pkg is available in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Microsoft_OneDrive_24.020.0128.pkg found in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
Checking MD5 hash of Microsoft_OneDrive_24.020.0128.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR to verify match with Microsoft_OneDrive_24.020.0128.pkg on https://server_name_here.jamfcloud.com&#8230;
MD5 hash of Microsoft_OneDrive_24.020.0128.pkg in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR matches Microsoft_OneDrive_24.020.0128.pkg on https://server_name_here.jamfcloud.com.
Microsoft_OneDrive_24.020.0128.pkg is available in /var/folders/vd/c27hl4p53j1_5cnv9ynpxp6m0000gn/T/tmp.R7v2rAecOR.
username@computername ~ %

view raw

gistfile1.txt

hosted with ❤ by GitHub

The scripts are available from GitHub at the following location:

https://github.com/rtrouton/rtrouton_scripts/tree/main/rtrouton_scripts/Casper_Scripts/Jamf_Pro_JCDS_Installer_Package_Download

Migrating a Jamf Pro AWS-hosted cloud distribution point to a JCDS2 cloud distribution point

I recently needed to migrate a Jamf Cloud-hosted Jamf Pro instance from using an AWS-hosted cloud distribution point to using a Jamf-hosted JCDS2 cloud distribution point. For those looking at a similar migration, please see below the jump for more details.

Advisory: I strongly advise having Jamf’s Professional Services folks involved if you’re planning a migration like this. The reason is that, as of the current Jamf Pro 11.3.2 release, you can only have one cloud distribution point at a time. A migration like the one I performed will involve a cut-over process which includes having to re-upload your current distribution point’s installer packages to the new JCDS2 distribution point. This process also necessitates having good recent backups for the Jamf Pro instance in question.

With the assistance of Jamf’s Professional Services (particular thanks to Sepie Moinipanah, Leslie Helou and David Raabe for their support), the migration in my case went smoothly. Please see below for the process I followed to migrate from an AWS-hosted cloud distribution point to using a Jamf-hosted JCDS2 cloud distribution point:

Pre-requisites:

Getting the installers from your current distribution point:

There are several ways to get the installers from an AWS-hosted cloud distribution point. The method I chose was to use AWS’s command line tool to sync the contents of the S3 bucket used by the cloud distribution point to a local directory on my workstation:

https://derflounder.wordpress.com/2018/02/15/backing-up-the-contents-of-an-aws-hosted-jamf-pro-cloud-distribution-point-to-a-local-directory/

API Client Role and API Client:

The jamfCPR wiki describes the necessary permissions needed to sync installer packages to a JCDS2 cloud distribution point, in the context of an API Client Role. As a result, I used an API Client Role and API Client in this case because the API Client Role permissions don’t always map one-to-one to the permissions available to Jamf Pro accounts and groups. Please see below for the permissions I set for my API Client Role:

  • Read Cloud Services Settings
  • Read Distribution Points
  • Create Packages
  • Read Packages
  • Update Packages
  • Delete Packages
  • Read Cloud Distribution Point
  • Update Cloud Distribution Point
  • Create Jamf Content Distribution Server Files
  • Read Jamf Content Distribution Server Files
  • Delete Jamf Content Distribution Server Files
  • Jamf Packages Action

Screenshot 2024-03-26 at 9.19.23 AM

Preparing for the distribution point migration:

1. Verify that you have the latest version of jamfCPR available.

Note: If the JCDS2 cloud distribution point you’re migrating to is located outside of the United States, make sure you’re using jamfCPR 5.x or later. jamfCPR 4.12 and earlier is not able to work with JCDS2 cloud distribution points which are hosted in AWS regions outside of the United States.

2. Verify that you have an API Client Role and API Client on the Jamf Pro server which has the correct permissions assigned.

3. Verify that you have a copy of all installers on your current AWS-hosted cloud distribution point stored on the same Mac that you have the jamfCPR app installed on.

4. Work with Jamf to make sure that a backup of your Jamf Pro service is made just prior to doing the migration.

Advisory about the Jamf Pro backup:

At this point, I want to stop for a moment and discuss why that backup of your Jamf Pro service is so important. The reason has to do with how AWS-hosted cloud distribution points are created. When you set one up, Jamf Pro will do the following:

  1. Create an S3 bucket with a randomly-generated name in the US-East-1 AWS region.
  2. Create an associated CloudFront distribution which connects to the S3 bucket created in Step 1.

As the Jamf Pro admin, you don’t get to choose anything in this process and you can’t select an existing S3 bucket. What this means is that the migration goes wrong and you need to revert back to your AWS-hosted cloud distribution point, the only way to do so is to roll back your Jamf Pro service to a point in time before the migration started. You will not be able to go back to using your existing AWS-hosted cloud distribution point without restoring from that backup because there is no way otherwise to have Jamf Pro use that existing AWS-hosted cloud distribution point.

If you try to go back otherwise, Jamf Pro will not use the existing AWS-hosted cloud distribution point. Instead, Jamf Pro will set up a new S3 bucket and CloudFront distribution and you will now have a brand-new and completely empty AWS-hosted cloud distribution point.

Running the migration:

1. Log into your Jamf Pro server as an admin user with all needed rights.

2. Verify that the Cloud Services connection is logged in and appears to be working properly.

3. Go to Settings: Server: Cloud distribution point.

4. Click the Test button and verify that your connection to the cloud distribution point is working correctly.

5. Install something from your Jamf Pro server and verify that installation is working correctly.

6. Verify in your policy logs that the installer is coming from an address which matches something similar to what’s shown below:

https://d2zft6agzhvlnv.cloudfront.net

7. In your Jamf Pro server, go to Settings: Server: Cloud distribution point.

Note: The next step is a point of no return, for reasons described above in the Advisory about the Jamf Pro backup section. Make sure a very recent Jamf Pro backup is available.

8. Select Jamf Cloud and click the Save icon.

9. Click the Test button and verify that your connection to the cloud distribution point is working correctly.

10. Open the jamfCPR app on your Mac.

11. Select the directory containing the downloaded copy of the installers from the existing AWS-hosted cloud distribution point.

12. Set up the connection to your Jamf Pro service, using the API Client ID and its associated Client ID Secret.

13. Click the List button in the jamfCPR app. You should now see a list of packages that your Jamf Pro service has, showing a status of different.

Note: To allow the migration process to go quickly for this blog post, I’m using dummy installer packages with a size of one kilobyte. The jamfCPR app should display the actual size of the installer packages in its application window when the installers are listed.

14. Select the packages you want to copy back from the downloaded copy of the installers to your Jamf Pro service.

15. Click the Replicate button.

You can monitor the replication process using the jamfCPR logs, which are available under the View menu using the Show Logs command.

16. Once replication is completed, the packages should appear as Availability Pending when you bring up information on the package in the Jamf Pro admin console.

You also won’t see any packages listed in the Cloud distribution point screen available via Settings: Server: Cloud distribution point.

This is normal and the package should already be available for installation. One way to verify that the packages are present in the JCDS2 distribution point is by downloading them from the JCDS2 distribution point. I have a post on how to do this available via the link below:

https://derflounder.wordpress.com/2024/02/24/using-the-jamf-pro-api-to-download-installer-packages-from-a-jcds2-distribution-point/

17. Install something from your Jamf Pro server and verify that installation is working correctly.

18. Verify in your policy logs that the installer is coming from an address which matches something similar to what’s shown below:

https://server_name_here.jamfcloud.com/jcds

Note: If you’re using a custom DNS name for your Jamf Cloud instance, it would appear similar to what’s shown below:

https://server_name_here.custom_domain.here/jcds

Post-migration:

After about an hour, the packages should appear listed in the Cloud distribution point screen available via Settings: Server: Cloud distribution point:

The Availability Pending message should also disappear when you view information about the installer package.