Extension Attribute Best Practices
Overview
Scripted extension attributes are powerful tools that admins can utilize to see custom data for each computer. Below we'll discuss some of the best practices to follow when building out these scripts.
Best Practices
Use native macOS command line shells
Apple has standardized on zsh (z-shell) as the default shell for new macOS user accounts. Always use zsh unless you have a good reason for using a different shell.
For example:
#!/bin/zsh
or one of the other built-in shells like:
#!/bin/bash
, #!/bin/sh
, #!/bin/csh
, #!/bin/ksh
, #!/bin/tcsh
Use comments where needed
Not every command or line of a script needs a comment. But if a comment would help someone understand the actions the script is taking, add a short description or sentence.
Use command substitution instead of backticks
Command substitution uses the format $( command )
to return the result of a command instead of the command itself. It's easier to read than backticks 'command'
, which do the same thing.
Write this:
result=$( /usr/bin/osascript -e "$theCommand" )
Depending on how you call a variable, it may or may not work without quoting. But adding quotes around a variable makes it an atomic unit and generally works all the time. If a variable contains spaces or globbing characters and you don’t quote it, each word may be treated independently instead of as a whole.
Write this:
"$var"
"${var}"
Not this:
$var
${var}
The fewer commands, the better
Fewer commands means less that can go wrong. If you find a script is using multiple commands to trim data to a specific data point, investigate whether one or fewer commands will work instead.
Use full paths to binaries
When calling a command line tool (that’s not part of the shell’s built-ins), provide the full path to the binary. This improves security where a malicious attacker may edit a user’s profile and include a new path pointing to malicious software with the same name.
To find the full path to a binary, open Terminal and run type
followed by the command.
type jamf
Some commands like echo
and type
are shell built-in commands and have no external path. Some shell built-in commands like echo have a similar command external to the shell like /bin/echo
. When possible, use built-in shell commands over external shell commands.
Use double brackets
When testing a condition such as the existence of a file, a variable matches a value, or a command returns true or false, always put your tests in double brackets not single brackets. Double brackets are supported by multiple shells including bash and zsh.
Write this:
if \[[ -f file ]]; then
while \[[ "$a" = "$b" ]]; do
Not this:
if [ -f file ]; then
while [ "$a" = "$b" ]; do
Test your scripts on macOS versions supported by Jamf Pro
Jamf Pro generally supports the current macOS version along with the previous 3 major versions. For example, Jamf Pro 11.17 supports:
macOS Sequoia 15.0
macOS Sonoma 14.0
macOS Ventura 13.0
macOS Monterey 12.0
For the most up to date information on supported OS versions please view the article here.
In the script, add a list of tested and supported OS versions along with any that are untested:
#supported: macOS Sequoia 15.0
#untested: macOS Sonoma 14.0, macOS Ventura 13.0
#not supported: macOS Monterey 12.0
Always return a result
Jamf Pro displays no information for extension attributes that haven't yet run. Because we want to distinguish between situations where the EA has not yet run, and an EA that ran but returned no information, a result should always be returned by the script.
For example:
info=$( /usr/bin/defaults read /Library/Preferences/com.apple.RemoteDesktop Text1 )
if \[[ "$info" ]]; then
echo "<result>$info</result>"
else
echo "<result>Info not set</result>"
fi
Include --no-rcs in the zsh shebang
This prevents scripts from potentially running malicious code using a root escalation.
Write this:
#!/bin/zsh --no-rcs
Not this:
#!/bin/zsh
Send potential error results to /dev/null
If a command in a script may potentially produce an error, Jamf Pro will ignore the error and provide only the result. However, we encourage customers to test scripts before putting them in production. If a customer tests and sees an error, they may get confused or choose not to use the script.
Always send the potential results of a command to /dev/null using 2>/dev/null.
Use native macOS command line binaries
Extension attribute scripts should use only native binaries installed with macOS to avoid broken scripts. In some caes like jq, Apple may have added it to a supported macOS version, but it may not be available on others.
Include a comment noting which macOS versions a script supports and be sure to return a result for unsupported versions.
For example:
#!/bin/zsh\
#supported: macOS Sequoia 15.0
#not supported: macOS Sonoma 14.0, macOS Ventura 13.0, and macOS Monterey 12.0
command=$( echo '{"name": "John Doe", "age": 30, "city": "New York"}' | /usr/bin/jq -r )
if \[[ "$command" ]]; then
echo "<result>$command</result>"
else
echo "<result>macOS $( /usr/bin/sw_vers -ProductVersion ) not supported</result>"
fi
Add a timeout to curl and other network commands
Extension attributes that poll the network are unpredictable. They may respond quickly, slowly, or not at all. Include a timeout for any commands that may take longer than just a fraction of a second to complete.
Write this:
#get public IP address\
publicIP=$( /usr/bin/curl <http://ifconfig.me/ip>
--location
--silent
--max-time 10 )
Updated 5 days ago