Addigy Custom Facts let you collect virtually any data from your macOS devices. That flexibility comes with real pitfalls, though — this article covers the common problems admins run into with Custom Facts and the best practices for collecting facts from your Macs reliably.
Prerequisites
If you're just getting started with Custom Facts, see Creating Custom Facts for the basics before working through the practices below.
Best Practices for Building Custom Facts
1. Test Your Custom Facts First
Like everything else in Addigy, Custom Facts can be deployed selectively to a policy of your choice — so you can extensively test a Custom Fact on virtual machines or spare devices before it ever touches production. We strongly recommend doing this every time. Without proper testing, a bad Custom Fact can slow down your device audits and negatively affect how your machines report facts that were already working well.
2. Watch the Maximum Upload Size
Custom Facts have a strict maximum output size. Output larger than 1 MB returns an error and can slow down your fact audits, and the maximum character limit for a Custom Fact is 3,072 characters. Avoid using facts to output the content of logs or files.
3. Watch the Maximum Run Time
Custom Facts run within the same architecture as Addigy's default facts, and a 5-minute timeout is enforced to keep that fact from slowing down the rest of the facts the Addigy auditor collects. Avoid commands that can take a long time to run, like recursively searching through a file system.
4. Anticipate Errors
Shell-based facts often wrap other binaries, and those binaries can error. Expect that, and use if-statements to anticipate problems before they happen and handle differences across macOS versions.
The sw_vers command makes it easy to detect different macOS versions. This example shows how to give older versions special treatment:
if (( $(sw_vers | grep ProductVersion: | awk -F: '{print $2}'| awk -F. '{print $1}' | xargs ) <14 then="then" this="This" machine="machine" is="is" on="on" an="an" older="older" macos="macOS" sonoma="Sonoma" fi="fi">If your script isn't robust enough, adding logic like this is a good place to start.
5. Redirect Standard Output and Standard Error
Redirect a command's output inside an if-statement so it doesn't leak into your fact's return value. In this example, the profiles command's stdout and stderr are redirected with 1/dev/null 2&1 so they don't corrupt 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'
fi6. Use Custom Error Codes for n/a Responses
Sometimes returning true, false, or even a value isn't the right call. In these cases, exit code 117 shows an n/a response on the Devices page and in GoLive. For example, this checks battery charging status and returns true or false based on charge, or n/a if the device has no 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 0Frequently Asked Questions
Why is my Custom Fact slowing down device audits?
This usually traces back to one of three things: output over the 1 MB / 3,072-character limit, a command that takes too long to run (the 5-minute timeout will kill it), or a script that wasn't tested before being deployed to production. Start by testing the fact on a spare device to isolate the cause.
My fact should return n/a sometimes, but it's showing false instead — why?
Make sure the script actually exits with code 117 in that case, rather than falling through to an echo "false" branch. Returning false and returning n/a are different things, and only exit code 117 produces the n/a display.