Addigy Custom Facts let you collect any conceivable data from your macOS devices. Of course, collecting that data can be complicated and fraught with challenges. This article aims to tackle typical problems with Custom Facts and outline the best use-cases for collecting facts from your Macs.
For getting started with Custom Facts, go check out the article Creating Custom Facts where the basic functionality is outlined.
Understanding Custom Facts
There are lots of nuances to how Custom Facts are created. Let's go through the most common use-cases and edge-cases when building Custom Facts.
1. Test Your Custom Facts
Like all other items in Addigy, Custom Facts can be deployed selectively to the policy of your choice. This means that you have the ability to extensively test Custom Facts on virtual machines and spare devices before deploying a Custom Fact to your production machines. We strongly recommend you test Custom Facts before deploying them to production machines. Without proper testing, a bad Custom Fact can slow down the audits your devices and have a negative impact on the way your machines report the facts that already work well.
2. Maximum Upload Size
While you can do a lot with the flexible format of Custom Facts, there is a strict maximum size to the output of a Custom Fact. Custom Facts that have output larger than 1 Megabyte (MB) will return an error and can slow down your fact audits. The maximum character limit for a Custom Fact is 3072 characters. We recommend you don't use facts to output the content of logs or files.
3. Maximum Run Time
Custom Facts run within the same architecture of Addigy's default facts. To keep audits running smoothly, a timeout of five (5) minutes has been enforced to ensure that a fact does not incur a slowdown on the rest of the facts that the Addigy auditor collects. We recommend you avoid running commands that can take a long time like recursively looking through file systems.
4. Anticipate Errors
With scripting languages like Shell, many facts will be based on wrapping other binaries that can have errors. We recommend that you expect these binaries to have errors. Use if-statements to anticipate problems before they happen and handle differences between multiple OSes.
The `sw_vers` command in Bash makes it easy to detect different versions of macOS. This example shows how easy it can be to detect when older versions should get special treatment.
if (( $(sw_vers | grep ProductVersion: | awk -F: '{print $2}'| awk -F. '{print $1}' | xargs ) < 14 )); then # This machine is on an older macOS then Sonoma fi
There are many other good uses of if-statements and other logic structures to make sure your Custom Facts run smoothly. If you have issues with your script not being sufficiently robust, adding additional logic like this can be a great place to start.
5. Redirect Standard Output and Standard Error
You can redirect output of a command in an if-statement to make sure that none of the command's output is part of your fact's return value. In the example below the profiles command stdout and stderr has been redirected using 1>/dev/null 2>&1 to not ruin a boolean fact's return value.
if profiles show -type configuration -output stdout | grep "com.apple.mdm" 1>/dev/null 2>&1; then echo 'true' else echo 'false' fi
6. Custom Error Codes for n/a Responses
In certain situations, returning true or false or even a value might not be what you wish to do. In these scenarios, exit code 117 can be used instead to show a n/a response on the Devices page and in GoLive. As an example, let's do a quick check for battery charging status and return true or false based on charge OR return n/a if the device does not have a battery
#!/bin/sh
chargingStatus=$(system_profiler SPPowerDataType | awk '/Charging/ {print $2}' | head -n1)
if [ "$chargingStatus" = "No" ]; then echo "false" elif [ "$chargingStatus" = "Yes" ]; then echo "true" else exit 117 fi exit 0
Example Custom Facts
Here is an example of a Custom Fact: This is written in Bash
Bash: Local Users
This fact is quite simple. The script uses a common dscl command and a simple awk filter to print out all the local users on the Mac, both standard and admin. Our return type is list as we'd like each line of output (our command returns one username per line) to be an entry in the list. And, we use the default #!/bin/bash hashbang to invoke the script.
When added to a deployed to a policy, you should see username's in a nice array in the Local Users column.