Avoid Unicode Mangling in Jamf with shef

Computers are “the equivalent of a bicycle for our minds” as Steve Job once said. Sure enough, like a bicycle, they also require maintenance! Sometimes that requires soliciting the user to take action. Asking at the right time is very important (see my post Don’t Be a Jerk) and so is brevity. I’ve found a sprinkling of emoji and other symbols can help your message cut through the noise. A bright red stop sign πŸ›‘ can convey its meaning with only a glance, especially for non-native speakers of your language. Your tooling however, may trip you up and mangle your text.

While AppleScript, Swift Dialog and JamfHelper can all handle Unicode, Jamf databases tables by default do not support 4-byte Unicode! I touched on this in my post: jpt 1.0 text encoding fun. While the database engine might support it, chances are your tables are Latin1 which top out at 3-byte UTF-8 encoded characters. 4-byte characters get mangled. Let’s take a look at this in action:

Looks good while editing…
Once saved, the mangling is clear

Only the gear makes it through because it’s actually two 3-byte characters (U+2699 βš™ gear plus variation selector U+FE0F) but who has time to check which one is which? How about an encoding tool written in shell, that allows you to escape and format Unicode for shell scripts and/or Jamf script parameters that can make it through unscathed and un-mangled? Sound good? Great! I present shef, the Shell Encoder and Formatter!

Give it some text, specify an encoding scheme and/or quoting style and out comes your string ready for use! You can put the resulting string in your script as a variable or as a script parameter in a Jamf policy depending on quoting options. I’ve done the hard work of finding the various ways to escape special characters for shell and made this handy script for you!

shef Examples

First, let’s make a simple script for Jamf that processes the output of shef. I’ve chosen AppleScript so you can play along at home even if you don’t have Jamf installed, this can also be applied to “wrapper scripts” that leverage other tools like Swift Dialog or JamfHelper. At it’s heart is the simple technique of encoding the input with shef then decoding it in the script before presentation. If you use bash use echo -e if you use sh or zsh then just echo will work, it’s that simple. Our example script simpleAlert-AS.sh, keeps things small and as minimal as possible but keep in mind my fuller-featured tool shui for more robust scripts where you may need text or password entry, file picking, etc. without external dependencies but don’t want to bother learning AppleScript.

#!/bin/bash
#simpleAlert-AS - Copyright (c) 2023 Joel Bruner (https://github.com/brunerd)
#Licensed under the MIT License

#Simple Applescript alert dialog for Jamf - just a title, a message and an OK button
#Accepts hex (\xnn) and octal (\0nnn) escaped UTF-8 encoded characters (since the default Jamf db character set mangles 4 byte Unicode)
#Use shef to encode your strings/files for use in this script: https://github.com/brunerd/shef

#function to interpret the escapes and fixup characters that can screw up Applescript if unescaped \ and "
function interpretEscapesFixBackslashesAndQuotes()(echo -e "${@}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')

function jamflog(){
	local logFile="/var/log/jamf.log"
	#if we cannot write to the log or it does not exist, unset and tee simply echoes
	[ ! -w "${logFile}" ] && unset logFile
	#this will tee to jamf.log in the jamf log format: <Day> <Month> DD HH:MM:SS <Computer Name> ProcessName[PID]: <Message>
	echo "$(date +'%a %b %d %H:%M:%S') ${myComputerName:="$(scutil --get ComputerName)"} ${myName:="$(basename "${0}" | sed 's/\..*$//')"}[${myPID:=$$}]: ${1}" | tee -a "${logFile}" 2>/dev/null
}


#process our input then escape for AppleScript
message=$(interpretEscapesFixBackslashesAndQuotes "${4}")
title=$(interpretEscapesFixBackslashesAndQuotes "${5}")
#could be a path or a built-in icon (stop, caution, note)
icon="${6}"
#invoke the system open command with this argument (URL, preference pane, etc...)
open_item="${7}"

#these are the plain icons (Applescript otherwise badges them with the calling app)
case "${icon}" in
	"stop") icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertStopIcon.icns";;
	"caution") icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertCautionIcon.icns"
		#previous icon went away in later macOS RIP
		[ ! -f "${icon}" ] && icon="/System/Library/CoreServices/Problem Reporter.app/Contents/Resources/ProblemReporter.icns";;
	"note") icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns";;
esac

#make string only if path is valid (otherwise dialog fails)
if [ -f "${icon}" ]; then
	withIcon_AS="with icon file (POSIX file \"${icon}\")"
fi

jamflog "Prompting user: $(stat -f %Su /dev/console)"

#prompt the user, giving up and moving on after 1 day (86400 seconds)
/usr/bin/osascript <<-EOF
activate
with timeout of 86400 seconds
	display dialog "${message}" with title "${title}" ${withIcon_AS} buttons {"OK"} default button "OK" giving up after "86400"
end timeout
EOF

if [ -n "${open_item}" ]; then
	jamflog "Opening: ${open_item}"
	open "${open_item}"
fi

exit 0

Upload the above script to your Jamf (if you have one), label parameters 4, 5, 6, and 7 as: Message, Title, Icon Path, an Open After OK, respectively. If running local keep this in mind and put 1, 2, 3 as place holder arguments for the first three parameters.

Jamf Script parameters names for

Let’s come up with an example message for our users. How about the common refrain coming from MacAdmins around the world:

πŸ›‘ Stop.
βš™οΈ Run your updates.
πŸ™ Thanks!

Now if I tried to pass this text to a script within a Jamf policy, all those emoji would get mangled by Jamf as we saw above. Let’s use shef to encode the string for Jamf:

% shef <<'EOF'
heredoc> πŸ›‘ Stop.
heredoc> βš™οΈ Run your updates.
heredoc> πŸ™ Thanks!
heredoc> EOF
\xF0\x9F\x9B\x91 Stop.\n\xE2\x9A\x99\xEF\xB8\x8F Run your updates.\n\xF0\x9F\x99\x8F Thanks!

For the example above I am using a “here-doc”; in practice you can simply supply shef a file path. The output encodes all the newlines in the ANSI-C style of \n and the emoji have all been replaced by their UTF-8 encodings using the hexadecimal escaping of \x. We can take this output and use it in our Jamf policy. The message will get through both textually and symbolically. As an additional feature bonus I added a simple open action in the script. Supply the file path to the Software Update panel and after the user clicks OK, it will be opened. Scope a policy like this to anyone with pending updates with Daily frequency:

Escape the mangling in Jamf!

If you are just running the script locally and calling from the Terminal you’ll want to specify a quoting option. Exclamations are tricky and shells usually love to interpret trailing exclamations as a history expansion command, shef does its best to avoid this:

bash-3.2$ shef -Qd <<'EOF'
> πŸ›‘ Stop.
> βš™οΈ Run your updates.
> πŸ™ Thanks!
> EOF
"\xF0\x9F\x9B\x91 Stop.\n\xE2\x9A\x99\xEF\xB8\x8F Run your updates.\n\xF0\x9F\x99\x8F Thanks"\!""

bash-3.2$ ./simpleAlert-AS.sh 1 2 3 "\xF0\x9F\x9B\x91 Stop.\n\xE2\x9A\x99\xEF\xB8\x8F Run your updates.\n\xF0\x9F\x99\x8F Thanks"\!"" "Software Updates Pending" stop "/System/Library/PreferencePanes/SoftwareUpdate.prefPane"
Our un-mangled output

Stop the Mangling Madness!

Wrapping things up: use shef to encode strings with Unicode so they survive storage in Jamf’s Latin1 encoded db tables. shui my fuller featured AppleScript dialog tool is ready to accept shef encoded strings. You can also use shef minify your text for your shell script. In fact, I used it to encode the help text file down to a single quoted line for use within itself. That’s 1 happy customer and counting, hope you find it useful too!