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. 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 Bash and Python, 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 | awk -F. '{print $2}') < 12 )); then
    # This machine runs Sierra or older. Let's not assume it can handle newer commands.
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

Custom Facts work will often need to call subprocesses and binaries built-in to macOS. However, sometimes those programs can have ugly output or error messages. Redirecting standard output (stdout) and standard error (stderr) can help you curate the specific output you'd need to be parsed by the Addigy auditor. Typically, this will only apply to Bash scripts as Python directly wraps subprocess output and 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


Example Custom Facts

Here are two examples of Custom Facts: one written in Bash and the other in Python.


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.



Python: Is VM



Another simple fact, this Python was borrowed from a gist published by Mike Lynn (https://gist.github.com/pudquick/8107bb7b6e8d63eaddec7042c081e656) and is a great example of using resources found online to build awesome Custom Facts.


This script uses a system library to read information about the system CPU and determine if the device is a virtual machine or a real Mac. Our return type is boolean as we just want a simple "yes" or "no" on what type of device this is, and we use the default Python hashbang, #!/usr/bin/python.