HomeGuidesRecipesAPI ReferenceChangelog
Log In
Guides

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 )