Using AutoPkg to create an installer package for SAP GUI

I’ve previously posted guides on how to manually package SAP GUI:

However it’s also possible to automate creating a SAP GUI installer package using AutoPkg. To do this, you’ll need the following:

  1. AutoPkg
  2. The SAP GUI recipes from the rtrouton-recipes repo
  3. The latest SAP GUI installer application’s disk image
  4. A SAP GUI templates.jar file (optional)

For more details, please see below the jump.

The AutoPkg recipes I’ve written will need the following information provided:

  • SAP GUI’s numeric information
  • SAP GUI’s alphanumeric information
  • SAP GUI’s application bundle identifier information

None of this information is available from the installer application, so you’ll need to provide it to AutoPkg using a com.sapgui.identifier.plist file which will be included along with the latest SAP GUI installer application’s disk image. Please see below for how to gather this information.

Preparing SAP GUI for AutoPkg

1. Copy the latest SAP GUI installer application’s disk image to a test Mac or virtual machine.

2. Mount the disk image and launch the SAP GUI for Java Installer installer application on the test Mac or virtual machine.

Screen Shot 2021 07 22 at 2 16 27 PM

3. Follow the prompts to install.

Screen Shot 2021 07 22 at 2 16 55 PM

Screen Shot 2021 07 22 at 2 17 20 PM

Screen Shot 2021 07 22 at 2 17 32 PM

Screen Shot 2021 07 22 at 2 17 56 PM

4. Once installed, run the following command to get SAP GUI’s alphanumeric version.

defaults read "/Applications/SAP Clients/SAPGUI $version/SAPGUI $version.app/Contents/Info.plist" NSHumanReadableCopyright | awk '{print $2$3$4}'

For example, if this is SAPGUI 7.70rev2, use the following command:

defaults read "/Applications/SAP Clients/SAPGUI 7.70rev2/SAPGUI 7.70rev2.app/Contents/Info.plist" NSHumanReadableCopyright| awk '{print $2$3$4}'

Screen Shot 2021 07 22 at 2 34 18 PM

5. Once you have the alphanumeric version, run the following command to get SAP GUI’s numeric version.

defaults read "/Applications/SAP Clients/SAPGUI $version/SAPGUI $version.app/Contents/Info.plist" CFBundleShortVersionString

For example, if this is SAPGUI 7.70rev2, use the following command:

defaults read "/Applications/SAP Clients/SAPGUI 7.70rev2/SAPGUI 7.70rev2.app/Contents/Info.plist" CFBundleShortVersionString

Screen Shot 2021 07 22 at 2 35 47 PM

6. Once you have both versions, run the following command to get SAP GUI’s bundle identifier:

defaults read "/Applications/SAP Clients/SAPGUI $version/SAPGUI $version.app/Contents/Info.plist" CFBundleIdentifier

For example, if this is SAPGUI 7.70rev2, use the following command:

defaults read "/Applications/SAP Clients/SAPGUI 7.70rev2/SAPGUI 7.70rev2.app/Contents/Info.plist" CFBundleIdentifier

Screen Shot 2021 07 22 at 2 45 12 PM

7. Open Terminal and run the following command to create a com.sapgui.identifier.plist file with the alphanumeric version information for SAP GUI:

defaults write /path/to/com.sapgui.identifier AlphanumericVersionString version_info_goes_here

For example, if the SAP GUI alphanumeric version is 7.70rev2, run the following command to create a com.sapgui.identifier.plist file on the Desktop:

defaults write $HOME/Desktop/com.sapgui.identifier AlphanumericVersionString 7.70rev2

Screen Shot 2021 07 22 at 2 49 14 PM

8. Run the following command to add the numeric version information for SAP GUI to the com.sapgui.identifier.plist file:

defaults write /path/to/com.sapgui.identifier CFBundleShortVersionString version_info_goes_here

For example, if the SAP GUI numeric version is 770.4.200, run the following command

defaults write $HOME/Desktop/com.sapgui.identifier CFBundleShortVersionString 770.4.200

Screen Shot 2021 07 22 at 2 59 28 PM

9. Run the following command to add the bundle identifier information for SAP GUI to the com.sapgui.identifier.plist file:

defaults write /path/to/com.sapgui.identifier CFBundleIdentifier bundle_identifier_info_goes_here

For example, if the SAP GUI bundle identifier is com.sap.platin, run the following command

defaults write $HOME/Desktop/com.sapgui.identifier CFBundleIdentifier com.sap.platin

Screen Shot 2021 07 22 at 2 58 32 PM

10. Once the com.sapgui.identifier.plist file is created and populated with the version and bundle identifier information, copy it to a convenient location along with the latest SAP GUI installer application’s disk image.

11. If you’re planning to include a templates.jar file with the SAP GUI installer, copy the templates.jar file to the same location.

Screen Shot 2021 06 18 at 12 12 51 PM

12. Create a .zip file of the following files:

  • corp.sap.sapgui.identifier.plist
  • Latest SAP GUI installer application’s disk image
  • templates.jar (if applicable)

For this example, I’m going to use the following filename for the .zip file:

latestsapgui.zip

This .zip file will be what’s used by AutoPkg to create the SAP GUI installer package.

Screen Shot 2019 10 09 at 11 30 35 AM

 

Using the AutoPkg recipes

To accomodate the fact that some folks will have a templates.jar file which they want to include and some folks won’t, I’ve written two sets of .pkg recipes:

  • SAPGUIWithTemplate.pkg – Use when you’re using a templates.jar file
  • SAPGUIWithoutTemplate.pkg – Use when you’re not using a templates.jar file

Both sets of recipes share a common SAPGUI.download recipe.

SAP GUI does not have a publicly accessible download link, so there are two options available for adding the .zip file you created earlier to your AutoPkg workflow:

1. Posting the .zip file to a web server for download.

You can upload the .zip file to a web server or other online storage location which AutoPkg can download files from. This is my preferred method because you can set the download URL in an AutoPkg override. Once that’s done, you just need to replace the .zip file on the web server when a new version of SAP GUI comes out. The next time it’s run, AutoPkg will download the new .zip file and handle the rest automatically.

2. Using AutoPkg’s -p option.

AutoPkg includes an -p option for specifying a file for input, in place of downloading from a URL.

Screen shot 2015 01 26 at 7 36 26 am

For example, if you wanted to run the SAPGUIWithTemplate.pkg recipe and use a .zip file stored locally on your Mac, you would use the following command to run an AutoPkg override of the SAPGUIWithTemplate.pkg recipe and specify a .zip file named latestsapgui.zip:

autopkg run local.pkg.SAPGUIWithTemplate -p /path/to/latestsapgui.zip

Once you’ve sorted out how you’re adding the .zip file you created to your AutoPkg workflow, you should be able to use these recipes to create SAP GUI installer packages for use in your own environment.

For those who want to use code signing to sign the SAP GUI installers created by AutoPkg, I’ve also created the following .sign recipes:

SAPGUIWithTemplate.sign – Use when you’re using a templates.jar file
SAPGUIWithoutTemplate.sign – Use when you’re not using a templates.jar file

Each .sign recipe uses the appropriate .pkg recipe as a parent recipe.

Installer package identifiers and the mystery of the missing Java 11 files

As part of developing new AutoPkg recipes to support SapMachine‘s new Long Term Support (LTS) distribution for Java 17, I ran into a curious problem when testing. When I ran the SapMachine Java 17 LTS installer that was being generated by AutoPkg, I was seeing the following behavior:

  • SapMachine Java 17 LTS is installed by itself – no problem
  • SapMachine Java 17 LTS installed, then SapMachine Java 11 LTS is installed – no problem
  • SapMachine Java 11 LTS installed, then SapMachine Java 17 LTS is installed – SapMachine Java 11 LTS is removed, only SapMachine Java 17 LTS is installed now.

I double-checked the preinstall script for the SapMachine Java 17 LTS installer. It is supposed to remove an existing SapMachine Java 17 LTS installation with the same version info, but it should not have also been removing SapMachine Java 11 LTS. After a re-review of the script and additional testing, I was able to rule out the script as the problem. But what was causing this behavior? Also, why was it happening in this order?

  • SapMachine Java 11 LTS installed, then SapMachine Java 17 LTS is installed

But not this order?

  • SapMachine Java 17 LTS installed, then SapMachine Java 11 LTS is installed

The answer was in how the package’s package identifier was set up. For more details, please see below the jump.

When I was writing the AutoPkg .pkg recipes for SapMachine Java 17 LTS, I used the existing SapMachine Java 11 LTS .pkg recipe as a template. Included with that recipe was the following:

<key>id</key>
<string>net.java.openjdk.sapmachine</string>

view raw
gistfile1.txt
hosted with ❤ by GitHub

For AutoPkg’s PkgCreator processor, this defines the package identifier.

I didn’t change that. That was a mistake, because that meant that Apple’s Installer was going to see SapMachine Java 17 LTS as an upgrade install for an existing SapMachine Java 11 LTS installation. Why is this important?

When macOS’s Installer does an upgrade install, it does the following:

  1. Consults its store of existing installer package receipts.
  2. Identifies if it has a receipt by a previous installer with a matching package identifier. If multiple receipts are found, the latest installed is used.
  3. Uses the Bill of Materials (BOM) stored with the receipt to generate a list of files which are in the receipt’s BOM and are not part of the new installer’s BOM.
  4. Removes the files in question.
  5. Adds the files from the current installer.

Note: Files added, removed or changed outside of the main installation process will not be included in the BOM. Since these files are not included in the BOM’s listing, the installer process will not move, change or delete these files. Examples of files not included or tracked by the BOM would be files added or moved by an installer package’s preinstall or postinstall scripts.

From Installer‘s point of view, the two packages were identified this way:

SapMachine Java 11 LTS

  • Identifier: net.java.openjdk.sapmachine
  • Version: 11.xx.xx

SapMachine Java 17 LTS

  • Identifier: net.java.openjdk.sapmachine
  • Version: 17.xx.xx

That meant that, on a Mac where SapMachine Java 11 LTS had been installed previously using an installer package with the net.java.openjdk.sapmachine package identifier, Installer interpreted the SapMachine Java 17 LTS install as being an upgrade for SapMachine Java 11 LTS.

In those conditions, Installer performs the following actions:

  1. Consults the existing installer package receipts.
  2. Identifies the latest version of the SapMachine Java 11 LTS receipt with a matching net.java.openjdk.sapmachine package identifier.
  3. Generates a list of the files installed by the SapMachine Java 11 LTS installer package, using the SapMachine Java 11 LTS receipt’s BOM, and identified those files which were not included in the SapMachine Java 17 LTS installer package’s BOM.
  4. Removes those files.
  5. Adds the files from the SapMachine Java 17 LTS installer.

The effect is that SapMachine Java 11 LTS’s files were automatically removed when SapMachine Java 11 LTS is installed first and then SapMachine Java 17 LTS is subsequently installed.

The fix? Make sure the SapMachine Java 17 LTS installer package has a different package identifier.

As I was using AutoPkg to build the installer package in question, I could include variables as part of the package identifier to help ensure the package identifier would be unique for each version of the SapMachine Java 17 LTS installer package.

<key>id</key>
<string>net.java.openjdk.sapmachine.universal.%version%.%BUILD_NUMBER%</string>

view raw
gistfile1.txt
hosted with ❤ by GitHub

For example, for SapMachine Java 17.35 LTS, the package identifier looks like this in the AutoPkg-created installer package:

<key>id</key>
<string>net.java.openjdk.sapmachine.universal.17.35</string>

view raw
gistfile1.txt
hosted with ❤ by GitHub

Slides and video from the “AutoPkg in the Cloud” session at MacSysAdmin 2021

For those who wanted a copy of my cloud hosting for AutoPkg talk at at the MacSysAdmin 2021 conference, here are links to the slides in PDF and Keynote format.

The video of my session is available for download from here:

Enabling full disk access for SSH on macOS Big Sur using a management profile

When connecting via SSH to a remote Mac running macOS Big Sur, Apple’s user-level privacy controls apply. You can access data in the home folder of the account you’re using to connect, but you can’t access or alter protected data in other account’s home folders.

For most use cases, this is fine. However, there may be circumstances when full disk access for SSH connections is desired. To accommodate for this, Apple added an Allow full disk access for remote users checkbox in the Remote Login settings in System Preference’s Sharing preference pane.

EnableFullDiskAccessforSSH

This setting can normally only be enabled by the logged-in user sitting at that Mac. However, there is a way to manage this with a configuration profile. For more details, please see below the jump.

I’ve written a profile to manage full disk access for SSH connections which does the following:

  • Enables the Allow full disk access for remote users checkbox in the Remote Login settings in System Preference’s Sharing preference pane
  • Enables full disk access for /usr/libexec/sshd-keygen-wrapper

The first part is mainly cosmetic. It enables the Allow full disk access for remote users checkbox, but does not actually enable full disk access for SSH. That function is handled by the second part, which are the PPPC settings to allow full disk access for /usr/libexec/sshd-keygen-wrapper.

In order to apply PPPC settings, there are some pre-requisites:

  • User Approved Mobile Device Management (UAMDM) must be enabled on the target Mac.
  • Profile must be installed by an MDM server.

Those pre-requisites also apply to deploying this profile, which is available via the link below:

https://github.com/rtrouton/profiles/tree/main/EnableFullDiskAccessforSSH

When deployed, the profile should appear similar to this in System Preference’s Profiles preference pane.

Screen Shot 2021 09 29 at 5 23 58 PM

Hat tip to poundbangbash for providing the correct PPPC settings for SSH full disk access by allowing full disk access to /usr/libexec/sshd-keygen-wrapper.

Setting up an ad-hoc TCP listener for connection testing using Python’s web service

I recently needed to set up a connection test so that an outside vendor could verify that firewall rules had been set up correctly on both ends and that a connection which originated at a specific IP address on the vendor’s end was able to resolve a DNS address on our end and make a connection.

I remembered that Python has a simple way to set up a web server, so I decided to use this to create a script which creates a connection listener by setting up a web server on the desired port. For more details, please see below the jump.

Pre-requisites:

  • Python 3

The script does the following:

  • Creates a temporary directory
  • Changes location into the temporary directory
  • Creates an index.html file inside the temporary directory
  • Starts Python 3’s web service using the content inside the temporary directory

Once running, you should be able to run connection tests against the port number defined in the script. In this example, port 8080 is being used.

Note: If you need to use a privileged port number between 0 – 1023, be aware that you will need to run this script with root privileges or by using authbind.

When the address being checked is connected to, you should see results similar to what’s shown below:

Note: In this case, localhost is being used as the address to check:

Curl connection test:

username@computername ~ % curl -v localhost:8080
* Trying ::1…
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.8.2
< Date: Tue, 14 Sep 2021 18:02:32 GMT
< Content-type: text/html
< Content-Length: 86
< Last-Modified: Tue, 14 Sep 2021 18:01:59 GMT
<
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
</body>
</html>
* Closing connection 0
username@computername ~ %

view raw
gistfile1.txt
hosted with ❤ by GitHub

Telnet connection test using curl:

username@computername ~ % curl -v telnet://localhost:8080
* Trying ::1…
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
^C
username@computername ~ %

view raw
gistfile1.txt
hosted with ❤ by GitHub

As a web page is also being created by the script, you can also try connecting via a web browser. If successful, you should see a Hello World message as shown below.

Screen Shot 2021 09 14 at 4 25 59 PM

The connection test script is available below:

#!/bin/bash
webdirectory=$(mktemp -d)
# Set port number for web service
port_number="8080"
# Create temporary directory and change directory
# into the temporary directory
cd "$webdirectory"
cat > "$webdirectory"/index.html << 'Index'
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World
</body>
</html>
Index
# Run webservice
/usr/bin/python3 -m http.server "$port_number"

view raw
connection_test.sh
hosted with ❤ by GitHub

Setting up software deployment groups using a Jamf Pro Extension Attribute

When setting up software for deployment, it’s usually a good idea to first send it out to a small percentage of the Macs in your environment. That way, if there’s a problem that wasn’t caught in testing, the amount of cleanup required is also small. If that initial deployment works, the software can then be sent out to greater percentages of the Mac population until all of them are eventually covered by the deployment.

This can be a pain to track manually though. New Macs come in, older ones are retired and keeping all Macs covered can turn into a significant investment of time. Fortunately, this is a task which can be automated and enable the Macs to assign themselves to deployment groups based on their machine UUID identifier. For more details, please see below the jump.

This method uses a Jamf Pro Extension Attribute, which is designed to calculate and set a numerical identifier for individual Macs based on the Mac’s machine UUID. This numerical identifier in turn is designed to be used to determine deployment groups.

Jamf Pro Extension Attribute Setup

Once identified, the value will be written to a plist file located in /Library/Preferences. By default, this file is named as follows:

/Library/Preferences/com.companyname.deploymentgroup.plist

You can change the companyname part by setting a different value for the organizationName variable in the Extension Attribute.

Screen Shot 2021 09 10 at 3 41 23 PM

 

If the plist file is present in /Library/Preferences, the Extension Attribute will read the correct value from the plist file.

If the plist file is not present in /Library/Preferences, the Extension Attribute will calculate the correct value and store in the plist as the value of the deploymentGroupAssignmentValue key.

Screen Shot 2021 09 10 at 8 36 20 PM

By default, this Extension Attribute is designed to assign Macs to seven deployment groups, with the following percentage of Macs assigned to each group.


Deployment Group Percentage of Macs
1 1
2 5
3 10
4 20
5 20
6 20
7 24

view raw
Deployment_Group.csv
hosted with ❤ by GitHub

When all is working correctly, the Extension Attribute will display one of the following values for each Mac:

1
2
3
4
5
6
7

view raw
gistfile1.txt
hosted with ❤ by GitHub

Screen Shot 2021 09 10 at 4 29 58 PM

 

When not working properly, the Extension Attribute will display the following value:

0

view raw
gistfile1.txt
hosted with ❤ by GitHub

The Extension Attribute’s values can then be used as Jamf Pro smart group criteria to create smart groups for software deployment.

This script is available below and also from GitHub at the following location:

https://github.com/rtrouton/rtrouton_scripts/tree/main/rtrouton_scripts/Casper_Extension_Attributes/set_deployment_group

#!/bin/bash
# This Jamf Pro Extension Attribute is designed to calculate and set a
# numerical identifier for individual Macs based on the Mac's machine UUID.
# This numerical identifier in turn is designed to be used to determine deployment
# groups.
#
# By default, this script is designed to set up and assign Macs to seven
# deployment groups, with the following percentage of Macs assigned to
# each group.
#
# Group % of Macs
# ————————-
# 1 1
# 2 5
# 3 10
# 4 20
# 5 20
# 6 20
# 7 24
#
# Put in the name of your company, school, or institution.
# Must all be one word without spaces
#
# Examples:
#
# MyGreatCompany
# TheNewSchool
# BankofGreaterNewtown
# MikesSurfShop
organizationName="companyname"
# Do not edit variables below this line
deploymentGroupFile="/Library/Preferences/com.${organizationName}.deploymentgroup.plist"
exitCode=0
log() {
local errorMsg="$1"
echo "$errorMsg"
/usr/bin/logger "$errorMsg"
}
deploymentGroupAssignment() {
deploymentGroup=7
# Get the machine's uuid
machineUUID=$(/usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice | /usr/bin/awk '/IOPlatformUUID/ { gsub(/"/,"",$3); print $3; }')
# If the UUID is available, generate a hash of the UUID
# then use that hash to assign an index number.
if [[ -n "$machineUUID" ]]; then
uuidHash=$(echo "$machineUUID" | /usr/bin/shasum -a 512 | /usr/bin/sed 's/[^0-9]*//g')
indexNumber=$(echo "${uuidHash:0:12}" | /usr/bin/awk '{ print $1 % 100 }')
if [[ -n "$indexNumber" ]]; then
# Once the index number is assigned, match the index number
# to a deployment group's numerical identifier.
case "$indexNumber" in
1) deploymentGroup=1
;;
2|3|4|5|6) deploymentGroup=2
;;
7|8|9|10|11|12|13|14|15|16) deploymentGroup=3
;;
17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36) deploymentGroup=4
;;
37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56) deploymentGroup=5
;;
57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76) deploymentGroup=6
;;
*) deploymentGroup=7
;;
esac
fi
else
log "ERROR! Unable to get machine's machine UUID"
exitCode=1
fi
}
reportExtensionAttributeValue() {
deploymentGroupAssignmentCheck=$(/usr/bin/defaults read ${deploymentGroupFile} deploymentGroupAssignmentValue)
# The extension attribute should have a numeric value greater than zero.
# If the value is blank or a non-number, the following value is reported:
#
# 0
#
# The 0 value indicates that there was a problem determining the deployment group.
if [[ "$deploymentGroupAssignmentCheck" =~ ^[0-9]+$ ]]; then
echo "<result>$deploymentGroupAssignmentCheck</result>"
else
echo "<result>0</result>"
fi
}
# Check to see if there's an existing plist file in /Library/Preferences which has the
# deployment group's numerical identifier assigned as an integer value to the plist file's
# deploymentGroupAssignmentValue key.
#
# If there is not a plist file, or there is not a deploymentGroupAssignmentValue key with a numerical
# value inside the plist file, the extension attribute generates the deployment group's numerical identifier.
# Once the deployment group's numerical identifier is generated, the identifier is stored as an integer value
# to the plist file's deploymentGroupAssignmentValue key.
if [[ -r ${deploymentGroupFile} ]]; then
reportExtensionAttributeValue
else
/usr/bin/defaults delete ${deploymentGroupFile}
deploymentGroupAssignment
/usr/bin/defaults write ${deploymentGroupFile} deploymentGroupAssignmentValue -int "$deploymentGroup"
reportExtensionAttributeValue
fi
exit $exitCode

Identifying an AWS RDS-hosted database by its tag information

Recently, I was working on a task where I wanted to set up an automated process to create manual database snapshots for a database hosted in Amazon’s RDS service. Normally this is a straightforward process because you can use the database’s unique identifier when requesting the database snapshot to be created.

However in this case, the database was being created as part of an Elastic Beanstalk configuration. This meant that there was the potential for the database in question to be removed from RDS and a new one set up, which meant a new unique identifier for the database I wanted to create manual database snapshots from.

The Elastic Beanstalk configuration does tag the database, using a Name tag specified in the Elastic Beanstalk configuration, so the answer seemed obvious: Use the tag information to identify the database. That way, even if the database identifier changed (because a new database had been created), the automated process could find the new database on its own and continue to make snapshots.

One hitch: Within the AWS API, RDS lists only the following three API calls to interact with tags.

ListTagsForResource would seem to be the answer, but the hitch there is that you have to have the database’s Amazon Resource Name (ARN) identifier available first and then use that to list the tags associated with the database:

aws rds add-tags-to-resource --resource-name arn:aws:rds:us-east-1:123456789102:db:dev-test-db-instance --tags Key=Name

I was coming at it from the other end – I wanted to use the tag information to find the database. RDS’s API doesn’t support that.

Fortunately, the RDS API is not the only way to read tags from an RDS database. For more details, please see below the jump.

The answer is that outside of RDS, there is also the Resource Groups Tagging API, which is accessible using the resourcegroupstaggingapi command for the AWS CLI tool. Among other things, the resourcegroupstaggingapi command allows you to identify a specified values for a specified key for a specified service.

For example, if you were looking for a RDS-hosted database whose database identifier you didn’t know, but you did know that it is in AWS’s eu-west-1 region and had a Name tag with the value of VIPDatabase, you could run the following query to get that databases’s ARN identifier:

aws –region eu-west-1 resourcegroupstaggingapi get-resources –resource-type-filters rds:db –query "ResourceTagMappingList[?Tags[? Key == 'Name' && Value == 'VIPDatabase']].ResourceARN"

view raw
gistfile1.txt
hosted with ❤ by GitHub

Once you had the ARN identifier, you could then use the following command to get the matching database’s instance identifier. For this example, we’re using arn:aws:rds:eu-west-1:123456789012:db:database_name_here as the ARN identifier:

aws rds --region eu-west-1 describe-db-instances --db-instance-identifier arn:aws:rds:eu-west-1:123456789012:db:database_name_here --query "*[].{DBInstanceIdentifier:DBInstanceIdentifier}"

Assuming you wanted to use this lookup capability in a shell script, the following code should get you started:

#!/bin/bash
TagKey="Tag Key Goes Here"
TagValue="Tag's Value Goes Here"
aws_region=$(/bin/curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed "s/.$//g")
RDSDatabaseARNIdentifier=$(aws –region "$aws_region" resourcegroupstaggingapi get-resources –resource-type-filters rds:db –query "ResourceTagMappingList[?Tags[? Key == '$TagKey' && Value == '$TagValue']].ResourceARN" –output=text)
RDSDatabaseDBIdentifier=$(aws rds –region "$aws_region" describe-db-instances –db-instance-identifier "$RDSDatabaseARNIdentifier" –query "*[].{DBInstanceIdentifier:DBInstanceIdentifier}" –output text)

view raw
gistfile1.txt
hosted with ❤ by GitHub

In the case of our example, where you’re looking for a database with a Name tag where the Name tag’s value is VIPDatabase, it would look like this:

#!/bin/bash
TagKey="Name"
TagValue="VIPDatabase"
aws_region=$(/bin/curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed "s/.$//g")
RDSDatabaseARNIdentifier=$(aws –region "$aws_region" resourcegroupstaggingapi get-resources –resource-type-filters rds:db –query "ResourceTagMappingList[?Tags[? Key == '$TagKey' && Value == '$TagValue']].ResourceARN" –output=text)
RDSDatabaseDBIdentifier=$(aws rds –region "$aws_region" describe-db-instances –db-instance-identifier "$RDSDatabaseARNIdentifier" –query "*[].{DBInstanceIdentifier:DBInstanceIdentifier}" –output text)

view raw
gistfile1.txt
hosted with ❤ by GitHub

Using the Jamf Pro API to report on which Macs are assigned to a particular person

Every so often, it may be necessary to generate a report from Jamf Pro on which computers are assigned to a particular person. To assist with this task, I’ve written a script which uses the Jamf Pro Classic API to search through the computer inventory records and generate a report in .tsv format.

For more details, please see below the jump.

Pre-requisites:

If setting up a specific Jamf Pro user account for this purpose with limited rights, here are the required API privileges for the account on the Jamf Pro server:

Jamf Pro Server Objects

  • Computers: Read

Computer inventory records

  • In Jamf Pro, the username field must be populated in the User and Location section of the computer inventory record.

Screen Shot 2021 08 26 at 3 43 23 PM

 

For authentication, the script can accept manual input or values stored in a ~/Library/Preferences/com.github.jamfpro-info.plist file.

The plist file can be created by running the following commands and substituting 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 account username in the plist file:

defaults write com.github.jamfpro-info jamfpro_user account_username_goes_here

To store the account password in the plist file:

defaults write com.github.jamfpro-info jamfpro_password account_password_goes_here

This script imports a list of usernames from a plaintext file and uses that information to generate a report about the computers assigned to that username.

Usage:

./Generate_Assigned_Mac_Report_From_Jamf_Pro_Usernames.sh usernames.txt

Screen Shot 2021 08 26 at 3 34 47 PM

Plaintext file format should look like this:

username
otheruser

view raw
usernames.txt
hosted with ❤ by GitHub

The script can also accept one username as input, if a plaintext file containing usernames
is not available.

Usage:

./Generate_Assigned_Mac_Report_From_Jamf_Pro_Usernames.sh

Screen Shot 2021 08 26 at 3 34 00 PM

 

Once the username(s) are read from in from the plaintext file or from manual input, the script takes the following actions:

1. Uses the Jamf Pro API to download all information about the matching computer inventory record in XML format.
2. Pulls the following information out of the inventory entry:

  • Jamf Pro ID
  • Assigned user’s username
  • Assigned user’s email
  • Manufacturer
  • Model
  • Serial Number
  • Hardware UDID

3. Create a report in tab-separated value (.tsv) format which contains the following information about the deleted Macs

  • Jamf Pro ID
  • Assigned user’s username
  • Assigned user’s email
  • Manufacturer
  • Model
  • Serial Number
  • Hardware UDID
  • Jamf Pro URL for the computer inventory record

This script will generate a report in .tsv format with information similar to what’s shown below:

One user:


Jamf Pro ID Number Assigned User Assigned User Email Make Model Serial Number UDID Jamf Pro URL
13 username username@pretendco.com Apple Mac mini (Mid 2011) C07GM01TDJD0 00BC7701-6791-573D-B461-470B44D16DF6 https://jamfpro.pretendco.com:8443/computers.html?id=13
86 username username@pretendco.com Apple iMac Pro Intel (Retina 5k, 27-inch, Late 2017) VM0N0WRc4EjC 564D33BC-AF4C-86CF-1DFB-AF6EDFC395A3 https://jamfpro.pretendco.com:8443/computers.html?id=86
87 username username@pretendco.com Apple iMac Pro Intel (Retina 5k, 27-inch, Late 2017) VMWmmR2FJqk3 564D4A6F-280F-1EC0-5E66-178DB2D45A8A https://jamfpro.pretendco.com:8443/computers.html?id=87

view raw
tmp.d5KLaAhc.tsv
hosted with ❤ by GitHub

Multiple users:


Jamf Pro ID Number Assigned User Assigned User Email Make Model Serial Number UDID Jamf Pro URL
13 username username@pretendco.com Apple Mac mini (Mid 2011) C07GM01TDJD0 00BC7701-6791-573D-B461-470B44D16DF6 https://jamfpro.pretendco.com:8443/computers.html?id=13
86 username username@pretendco.com Apple iMac Pro Intel (Retina 5k, 27-inch, Late 2017) VM0N0WRc4EjC 564D33BC-AF4C-86CF-1DFB-AF6EDFC395A3 https://jamfpro.pretendco.com:8443/computers.html?id=86
87 username username@pretendco.com Apple iMac Pro Intel (Retina 5k, 27-inch, Late 2017) VMWmmR2FJqk3 564D4A6F-280F-1EC0-5E66-178DB2D45A8A https://jamfpro.pretendco.com:8443/computers.html?id=87
85 otheruser otheruser@pretendco.com Apple VMware Virtual Platform VMD4TkB2CNtn 564D6125-8B99-47F1-9867-F92CD80BF0C9 https://jamfpro.pretendco.com:8443/computers.html?id=85

view raw
tmp.crenHMuh.tsv
hosted with ❤ by GitHub

This script is available below and also from GitHub at the following location:

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

#!/bin/bash
# This script imports a list of usernames from a plaintext file
# and uses that information to generate a report about the computers
# assigned to that username.
#
# ./Generate_Assigned_Mac_Report_From_Jamf_Pro_Usernames.sh usernames.txt
#
# The script can also accept one username as input, if a plaintext file containing usernames
# is not available.
#
# Usage: ./Generate_Assigned_Mac_Report_From_Jamf_Pro_Usernames.sh
#
# Plaintext file format should look like this:
#
# first_username_goes_here
# second_username_goes_here
# third_username_goes_here
# fourth_username_goes_here
#
# Once the username(s) are read from in from the plaintext file or from manual input, the script takes the following actions:
#
# 1. Uses the Jamf Pro API to download all information about the matching computer inventory record in XML format.
# 2. Pulls the following information out of the inventory entry:
#
# Jamf Pro ID
# Assigned user's username
# Assigned user's email
# Manufacturer
# Model
# Serial Number
# Hardware UDID
#
# 3. Create a report in tab-separated value (.tsv) format which contains the following information
# about the deleted Macs
#
# Jamf Pro ID
# Assigned user's username
# Assigned user's email
# Manufacturer
# Model
# Serial Number
# Hardware UDID
# Jamf Pro URL for the computer inventory record
report_file="$(mktemp).tsv"
# 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=""
# 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.githubjamfpro-info jamfpro_url https://jamf.pro.server.goes.here:port_number_goes_here
#
# To store the account username in the plist file:
# defaults write com.githubjamfpro-info jamfpro_user account_username_goes_here
#
# To store the account password in the plist file:
# defaults write com.githubjamfpro-info jamfpro_password account_password_goes_here
#
# If the com.github.jamfpro-info.plist file is available, the script will read in the
# relevant information from the plist file.
jamf_plist="$HOME/Library/Preferences/com.github.jamfpro-info.plist"
if [[ -r "$jamf_plist" ]]; then
if [[ -z "$jamfpro_url" ]]; then
jamfpro_url=$(defaults read "${jamf_plist%.*}" jamfpro_url)
fi
if [[ -z "$jamfpro_user" ]]; then
jamfpro_user=$(defaults read "${jamf_plist%.*}" jamfpro_user)
fi
if [[ -z "$jamfpro_password" ]]; then
jamfpro_password=$(defaults read "${jamf_plist%.*}" 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 ""
# Set initial status for exit
error=0
filename="$1"
# If a text file with usernames has not been provided, the script
# will prompt for a single username.
if [[ -z "$filename" ]]; then
read -p "Please enter the relevant username : " assigned_user
assigned_user_filename=$(mktemp)
/usr/bin/touch "$assigned_user_filename"
echo "$assigned_user" > "$assigned_user_filename"
fi
if [[ -z "$filename" ]] && [[ -r "$assigned_user_filename" ]]; then
filename="$assigned_user_filename"
fi
# Remove the trailing slash from the Jamf Pro URL if needed.
jamfpro_url=${jamfpro_url%%/}
progress_indicator() {
spinner="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
while :
do
for i in $(seq 0 7)
do
echo -n "${spinner:$i:1}"
echo -en "\010"
/bin/sleep 0.10
done
done
}
echo "Report being generated. File location will appear below once ready."
progress_indicator &
SPIN_PID=$!
trap "kill -9 $SPIN_PID" $(seq 0 15)
# Create temp files for data
idtempfile=$(mktemp)
xmltempfile=$(mktemp)
/usr/bin/touch "$xmltempfile"
# Get all computers that are associated with username
while read -r UserToMatch; do
# Get all computers associated with usernames
/usr/bin/curl -sfu "$jamfpro_user:$jamfpro_password" "${jamfpro_url}/JSSResource/computers/match/${UserToMatch}" -H "Accept: application/xml" | xmllint –format – >> "$xmltempfile"
done < "$filename"
# Extract the Jamf Pro computer IDs
/bin/cat "$xmltempfile" | sed -n 's:.*<id>\(.*\)</id>.*:\1:p' > "$idtempfile"
while read -r ID; do
if [[ "$ID" =~ ^[0-9]+$ ]]; then
ComputerRecord=$(/usr/bin/curl -sfu "$jamfpro_user:$jamfpro_password" "${jamfpro_url}/JSSResource/computers/id/$ID" -H "Accept: application/xml" 2>/dev/null)
if [[ ! -f "$report_file" ]]; then
/usr/bin/touch "$report_file"
printf "Jamf Pro ID Number\tAssigned User\tAssigned User Email\tMake\tModel\tSerial Number\tUDID\tJamf Pro URL\n" > "$report_file"
fi
Make=$(echo "$ComputerRecord" | xmllint –xpath '//computer/hardware/make/text()'2>/dev/null)
AssignedUser=$(echo "$ComputerRecord" | xmllint –xpath '//computer/location/username/text()'2>/dev/null)
AssignedUserEmail=$(echo "$ComputerRecord" | xmllint –xpath '//computer/location/email_address/text()'2>/dev/null)
MachineModel=$(echo "$ComputerRecord" | xmllint –xpath '//computer/hardware/model/text()'2>/dev/null)
SerialNumber=$(echo "$ComputerRecord" | xmllint –xpath '//computer/general/serial_number/text()'2>/dev/null)
JamfProID=$(echo "$ComputerRecord" | xmllint –xpath '//computer/general/id/text()'2>/dev/null)
UDIDIdentifier=$(echo "$ComputerRecord" | xmllint –xpath '//computer/general/udid/text()'2>/dev/null)
JamfProURL=$(echo "$jamfpro_url"/computers.html?id="$JamfProID")
if [[ $? -eq 0 ]]; then
printf "$JamfProID\t$AssignedUser\t$AssignedUserEmail\t$Make\t$MachineModel\t$SerialNumber\t$UDIDIdentifier\t${JamfProURL}\n" >> "$report_file"
else
echo "ERROR! Failed to read computer record with id $JamfProID"
error=1
fi
fi
done < "$idtempfile"
# Clean up temp files
if [[ -f "$assigned_user_filename" ]]; then
rm -rf "$assigned_user_filename"
fi
if [[ -f "$xmltempfile" ]]; then
rm -rf "$xmltempfile"
fi
if [[ -f "$idtempfile" ]]; then
rm -rf "$idtempfile"
fi
kill -9 "$SPIN_PID" 2>/dev/null
if [[ -f "$report_file" ]]; then
echo "Report on Macs available here: $report_file"
fi
exit "$error"

Codesigning, untrusted certificate authorities and why certain apps aren’t launching

A number of folks noticed that certain older applications they use on macOS stopped working as of August 24th, 2021. As of this date, this appears to affect the following applications among others:

Note: This list is not complete, it’s just the ones I’m aware of as of August 24, 2021.

Why this is happening goes back to an episode in 2018, where Symantec had to get out of the PKI certificate issuing business because of a number of issues discovered with how Symantec had been issuing certificates.

As part of these issues, Apple issued an advisory that a number of Symantec Certificate Authority (CA) root certificates were to be distrusted by Apple on a timeline which concluded with the full distrust of Symantec CAs on February 25, 2020.

While this primarily affected website operators, Symantec also issued certificates from the affected Symantec CAs which were used to provide code signing for applications. An example of this is RSA SecurID Software Token 4.2.1.


Update 8-26-2021: RSA has released RSA SecurID Software Token 4.2.2 to resolve the issue with RSA SecurID Software Token 4.2.1. For more details, please see the link below:

https://community.rsa.com/t5/securid-access-product/updated-securid-announces-securid-software-token-4-2-2-for-macos/ta-p/640000?emcs_t=S2h8ZW1haWx8Ym9hcmRfc3Vic2NyaXB0aW9ufEtTUTg5SktCWUQxS1JHfDY0MDAwMHxTVUJTQ1JJUFRJT05TfGhL


In the case of SecureID, this app relies on Qt Core for user interface support and ships copies of the QT Core framework along with the SecureID app. SecurID stores this in the following location:

/Library/Frameworks/stauto32.framework

If you take a look at the installer, you can see where the files are supposed to go.

Screen Shot 2021 08 24 at 6 25 25 PM

However, the actual file which is causing the issue is buried further down in the following location:

/Library/Frameworks/stauto32.framework/Versions/4/QtCore.cire

Screen Shot 2021 08 24 at 6 27 36 PM

This file is important because the QT Core framework is shipped without Apple’s code signing, which is to say that QT is not using an Apple Developer ID signing certificate to sign QT Core. Instead, QT ships a code signing certificate chain and that information is stored in the QtCore.cire file.

One of the certificates in the code signing certificate chain is using VeriSign Class 3 Public Primary Certification Authority – G5 as its root certificate authority (root CA). That root CA is one of the Symantec CAs which is no longer trusted by Apple.

Screen Shot 2021 08 24 at 6 03 58 PM

To summarize: the SecureID app has a component which is signed by a not-trusted certificate. This is causing SecureID to not trust that component, which then prevents the app from launching correctly.

Image

Why is this happening now?

Apple released updates on August 23, 2021 for both XProtect (now version 2150) and Malware Removal Tool (now version 1.82). My assumption is that XProtect’s update included instructions to no longer accept code signing from the distrusted Symantec CAs. XProtect checks executable code on launch, so it would be working with Gatekeeper to detect and block the no-longer-trusted code signing.

What should you do?

See if your vendor has released an updated version of the app which is having problems. If they haven’t yet, I recommend contacting them to make sure they’re aware of the problem.

Hat tip to all the folks working on this issue in the MacAdmins Slack for helping diagnose the issue and why it is happening.

Downloading and installing macOS Big Sur via macOS Recovery’s Terminal

Every so often, you may find yourself in a situation where you need to reinstall macOS Big Sur and everything is failing on you. Installing from macOS Recovery? Not working via the usual methods. Building a USB installer? Left the flash drive in your other pants. Using DFU mode and Apple Configurator on an Apple Silicon Mac? You need a second Mac to use this process and you just have the one Mac available.

For those situations, there’s one more option when you’ve exhausted all of the others. For more details, please see below the jump.

This method is referenced in the following Apple KBase article:

https://support.apple.com/HT211983

It involves the following:

  • Wiping the drive that you intend to install macOS Big Sur onto.
  • Creating the proper directory structure manually on the drive.
  • Copying the Install macOS app available in macOS Recovery manually into the proper location on the drive 
  • Using the curl tool to download the needed installer files onto the drive
  • Launching the Install macOS app
  • Using the Install macOS app to install macOS Big Sur

To install macOS Big Sur using the methods described above, use the procedure below:

1. Boot to macOS Recovery.

Screen Shot 2021 08 04 at 12 03 17 PM

2. Erase your Mac’s startup drive using the correct procedure for your Mac model:

Macs with Intel processors: https://support.apple.com/HT208496
Apple Silicon Macs: https://support.apple.com/kb/HT212030

By default, an erased drive is named Untitled. We’ll be using that drive name for the examples shown in this post.

2. Once the drive is erased, open Safari and go to the following URL:

https://support.apple.com/HT211983

Screen Shot 2021 08 04 at 12 03 18 PM

Screen Shot 2021 08 04 at 12 04 29 PM

 

3. Scroll to the bottom of the KBase article and locate the Or use Terminal to reinstall section.

Screen Shot 2021 08 04 at 12 05 46 PM

 

4. Copy the complete command which begins with curl. (This will save a lot of typing later.)

Screen Shot 2021 08 04 at 12 05 47 PM

 

5. Quit Safari.

6. Open Terminal.

Screen Shot 2021 08 04 at 12 13 45 PM

Screen Shot 2021 08 04 at 12 14 04 PM

 

7. Run the following command to change to using the drive named Untitled:

cd "/Volumes/Untitled"

Screen Shot 2021 08 04 at 10 29 27 AM

 

8. Run the following command to create a directory named private on the Untitled drive as well as a sub-directory inside of private which is named tmp:

mkdir -p private/tmp

Screen Shot 2021 08 04 at 10 30 11 AM

 

9. Run the following command to copy the Install macOS Big Sur.app application from macOS Recovery to the /Volumes/Untitled/private/tmp directory.

cp -R "/Install macOS Big Sur.app" private/tmp

Screen Shot 2021 08 04 at 10 31 03 AM

 

10. Run the following command to change to the /Volumes/Untitled/private/tmp/Install macOS Big Sur.app directory:

cd "private/tmp/Install macOS Big Sur.app"

Screen Shot 2021 08 04 at 10 32 00 AM

 

11. Run the following command to create a directory named Contents on the Untitled drive as well as a sub-directory inside of Contents which is named SharedSupport:

mkdir Contents/SharedSupport

Screen Shot 2021 08 04 at 10 33 44 AM

 

12. Paste the copied curl command and hit the enter key on your keyboard to have the macOS Big Sur installer files be downloaded into a file named SharedSupport.dmg. The SharedSupport.dmg will be located in /Volumes/Untitled/private/tmp/Install macOS Big Sur.app directory/Contents/SharedSupport.

curl -L -o Contents/SharedSupport/SharedSupport.dmg https://swcdn.apple.com/content/downloads/01/60/071-72781-A_CZ1D1FENMH/a09fvud3xxgih7qyau9a7lhtspho36mp0l/InstallAssistant.pkg

Screen Shot 2021 08 04 at 10 47 06 AM

Screen Shot 2021 08 04 at 10 47 34 AM

13. Once curl has finished downloading /Volumes/Untitled/private/tmp/Install macOS Big Sur.app directory/Contents/SharedSupport/SharedSupport.dmg, run the following command to launch the macOS Big Sur installer.

./Contents/MacOS/InstallAssistant_springboard

Screen Shot 2021 08 04 at 10 51 29 AM

Screen Shot 2021 08 04 at 10 53 05 AM

Screen Shot 2021 08 04 at 10 55 20 AM

14. Follow the prompts to install macOS Big Sur.

Screen Shot 2021 08 04 at 11 04 32 AM

Screen Shot 2021 08 04 at 11 04 38 AM

Screen Shot 2021 08 04 at 11 04 46 AM