This script helps administrators configure Josys browser extension policies on macOS. This script is designed for use with:
NinjaOne
Addigy
Mosyle
Kandji
Other MDM/RMM tools
Supported Browsers
Google Chrome
Microsoft Edge
Requirement
- OrganizationKey
- UserEmail
User Email defining order
The script determines the user email in the below order.If email cannot be determined, the script exits with an error.
Environment / MDM variables
NinjaOne (if available)
macOS user attributes (
dscl)Username + company domain fallback
Josys macOS Extension script
#!/bin/bash
set -u
echo "**** Josys macOS Extension Policy Setup Start ****"
# βββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ORGANIZATION_KEY="${ORGANIZATION_KEY:-PASS_ORGANIZATION_KEY}"
COMPANY_DOMAIN="${COMPANY_DOMAIN:-@pass_domainname.com}"
BROWSER_LIST="${BROWSER_LIST:-chrome edge}"
CHROME_EXTENSION_ID="${CHROME_EXTENSION_ID:-moaklgcgokbgplldonjkoochhlefkbjf}"
CHROME_UPDATE_URL="${CHROME_UPDATE_URL:-https://clients2.google.com/service/update2/crx}"
EDGE_EXTENSION_ID="${EDGE_EXTENSION_ID:-hjifncajikcdkhlofdjjlhcjoennmdfc}"
EDGE_UPDATE_URL="${EDGE_UPDATE_URL:-https://edge.microsoft.com/extensionwebstorebase/v1/crx}"
# MDM-injected email variables (checked in order before fallbacks)
USEREMAIL="${USEREMAIL:-}"
USER_EMAIL="${USER_EMAIL:-}"
EMAIL="${EMAIL:-}"
MDM_USER_EMAIL="${MDM_USER_EMAIL:-}"
NINJA_USER_EMAIL="${NINJA_USER_EMAIL:-}"
ADDIGY_USER_EMAIL="${ADDIGY_USER_EMAIL:-}"
MOSYLE_USER_EMAIL="${MOSYLE_USER_EMAIL:-}"
MANAGED_PREFS="/Library/Managed Preferences"
PLISTBUDDY="/usr/libexec/PlistBuddy"
# Kandji global variables plist β populated when Global Variables profile is deployed
KANDJI_GLOBAL_VARS_PLIST="/Library/Managed Preferences/io.kandji.globalvariables.plist"
# βββ Preflight checks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if [[ "$EUID" -ne 0 ]]; then
echo "ERROR: Script must run as root"
exit 1
fi
mkdir -p "$MANAGED_PREFS"
if [[ "$ORGANIZATION_KEY" == "PASS_ORGANIZATION_KEY" || -z "$ORGANIZATION_KEY" ]]; then
echo "ERROR: ORGANIZATION_KEY is not set"
exit 1
fi
# βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
log() {
echo "[Josys Extension Setup] $1"
}
backup_plist() {
local plist="$1"
if [[ -f "$plist" ]]; then
cp "$plist" "${plist}.backup.$(date +%Y%m%d%H%M%S)"
log "Backed up: $plist"
fi
}
create_plist_if_missing() {
local plist="$1"
if [[ ! -f "$plist" ]]; then
cat > "$plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
EOF
log "Created plist: $plist"
fi
chown root:wheel "$plist"
chmod 644 "$plist"
if ! plutil -lint "$plist" >/dev/null 2>&1; then
echo "ERROR: Invalid plist after creation: $plist"
exit 1
fi
}
plist_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
safe_set_string_key() {
local plist="$1"
local key="$2"
local value="$3"
local escaped_value
escaped_value="$(plist_escape "$value")"
create_plist_if_missing "$plist"
backup_plist "$plist"
"$PLISTBUDDY" -c "Set :$key \"$escaped_value\"" "$plist" 2>/dev/null || \
"$PLISTBUDDY" -c "Add :$key string \"$escaped_value\"" "$plist"
if ! plutil -lint "$plist" >/dev/null 2>&1; then
echo "ERROR: plist validation failed after writing '$key' to $plist"
exit 1
fi
chown root:wheel "$plist"
chmod 644 "$plist"
}
get_console_user() {
stat -f%Su /dev/console
}
extract_email() {
grep -Eo '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' | head -n 1
}
# βββ Email sources ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Source 1: Kandji global variables plist
get_kandji_email() {
if [[ ! -f "$KANDJI_GLOBAL_VARS_PLIST" ]]; then
return 1
fi
local email
email="$("$PLISTBUDDY" -c 'print :EMAIL' "$KANDJI_GLOBAL_VARS_PLIST" 2>/dev/null || true)"
if [[ "$email" == *@* ]]; then
log "Email source: Kandji global variables plist" >&2
echo "$email"
return 0
fi
return 1
}
# Source 2: NinjaRMM custom fields
get_ninja_custom_field_email() {
local ninja_cli=""
local email=""
local raw=""
for path in \
"/Applications/NinjaRMMAgent/ninjarmm-cli" \
"/Applications/NinjaRMMAgent/programdata/ninjarmm-cli"
do
if [[ -x "$path" ]]; then
ninja_cli="$path"
break
fi
done
[[ -z "$ninja_cli" ]] && return 1
email="$("$ninja_cli" get assigned_user_email 2>/dev/null | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: Ninja assigned_user_email" >&2
echo "$email"; return 0
fi
for field in USEREMAIL USER_EMAIL UserEmail; do
email="$("$ninja_cli" get custom_field "$field" 2>/dev/null | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: Ninja custom_field $field" >&2
echo "$email"; return 0
fi
done
raw="$("$ninja_cli" get custom_fields 2>/dev/null || true)"
email="$(echo "$raw" | grep -i 'email' | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: Ninja custom_fields grep" >&2
echo "$email"; return 0
fi
return 1
}
# Master email resolver β tries all sources in priority order
get_user_email() {
local user email candidate normalized_user
user="$(get_console_user)"
log "Logged-in user: $user" >&2
if [[ -z "$user" || "$user" == "root" || "$user" == "_mbsetupuser" ]]; then
log "No valid console user found" >&2
return 1
fi
# Priority 1: MDM/script-injected variables
for candidate in \
"$USEREMAIL" \
"$USER_EMAIL" \
"$EMAIL" \
"$MDM_USER_EMAIL" \
"$NINJA_USER_EMAIL" \
"$ADDIGY_USER_EMAIL" \
"$MOSYLE_USER_EMAIL"
do
if [[ "$candidate" == *@* ]]; then
log "Email source: MDM/script variable" >&2
echo "$candidate"; return 0
fi
done
# Priority 2: Kandji global variables plist
email="$(get_kandji_email || true)"
[[ "$email" == *@* ]] && { echo "$email"; return 0; }
# Priority 3: NinjaRMM
email="$(get_ninja_custom_field_email || true)"
[[ "$email" == *@* ]] && { echo "$email"; return 0; }
# Priority 4: macOS username looks like an email
if [[ "$user" == *@* ]]; then
log "Email source: macOS username" >&2
echo "$user"; return 0
fi
# Priority 5: dscl directory lookups
for attr in EMailAddress UserPrincipalName; do
email="$(dscl . -read "/Users/$user" "$attr" 2>/dev/null | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: dscl local $attr" >&2
echo "$email"; return 0
fi
done
for attr in EMailAddress UserPrincipalName; do
email="$(dscl /Search -read "/Users/$user" "$attr" 2>/dev/null | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: dscl search $attr" >&2
echo "$email"; return 0
fi
done
email="$(dscl . -read "/Users/$user" AuthenticationAuthority 2>/dev/null | extract_email || true)"
if [[ "$email" == *@* ]]; then
log "Email source: AuthenticationAuthority" >&2
echo "$email"; return 0
fi
# Priority 6: Construct from username + company domain
normalized_user="${user// /.}"
if [[ "$normalized_user" != *.* ]]; then
normalized_user="$(echo "$normalized_user" | sed -E 's/([a-z])([A-Z])/\1.\2/g')"
fi
normalized_user="$(echo "$normalized_user" | tr '[:upper:]' '[:lower:]')"
if [[ "$COMPANY_DOMAIN" == @* ]]; then
log "Email source: username + COMPANY_DOMAIN fallback" >&2
echo "${normalized_user}${COMPANY_DOMAIN}"; return 0
fi
return 1
}
# βββ Browser check ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
browser_installed() {
local browser="$1"
case "$browser" in
chrome)
[[ -d "/Applications/Google Chrome.app" ]] || \
[[ -d "/Applications/Google Chrome Beta.app" ]] || \
[[ -d "/Applications/Google Chrome Dev.app" ]]
;;
edge)
[[ -d "/Applications/Microsoft Edge.app" ]] || \
[[ -d "/Applications/Microsoft Edge Beta.app" ]] || \
[[ -d "/Applications/Microsoft Edge Dev.app" ]]
;;
*)
return 1
;;
esac
}
# βββ Force-install list βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Safely adds extension to ExtensionInstallForcelist without removing existing entries.
# Skips if already present (no duplicates).
ensure_force_install_extension() {
local browser="$1"
local plist="$2"
local extension_id="$3"
local update_url="$4"
local extension_string="${extension_id};${update_url}"
local index=0
local existing_value=""
create_plist_if_missing "$plist"
# Create the array key if it doesn't exist yet
if ! "$PLISTBUDDY" -c "Print :ExtensionInstallForcelist" "$plist" >/dev/null 2>&1; then
backup_plist "$plist"
"$PLISTBUDDY" -c "Add :ExtensionInstallForcelist array" "$plist"
log "Created ExtensionInstallForcelist array in $plist"
fi
# Check every existing entry for duplicate
while existing_value="$("$PLISTBUDDY" -c "Print :ExtensionInstallForcelist:$index" "$plist" 2>/dev/null)"; do
if [[ "$existing_value" == "$extension_string" ]]; then
log "$browser extension already in force-install list at index $index β skipping"
return 0
fi
index=$((index + 1))
done
# Safe to add β backup then append at next available index
backup_plist "$plist"
"$PLISTBUDDY" -c "Add :ExtensionInstallForcelist:$index string \"$extension_string\"" "$plist"
if ! plutil -lint "$plist" >/dev/null 2>&1; then
echo "ERROR: plist validation failed after adding $browser to force-install list"
exit 1
fi
chown root:wheel "$plist"
chmod 644 "$plist"
log "Added $browser extension to force-install list at index $index: $extension_string"
}
# βββ Extension policy (OrganizationKey + UserEmail) ββββββββββββββββββββββββββ
set_extension_policy() {
local plist="$1"
local user_email="$2"
safe_set_string_key "$plist" "OrganizationKey" "$ORGANIZATION_KEY"
safe_set_string_key "$plist" "UserEmail" "$user_email"
log "Extension policy plist updated: $plist"
}
# βββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DETECTED_EMAIL="$(get_user_email || true)"
if [[ -z "$DETECTED_EMAIL" || "$DETECTED_EMAIL" != *@* ]]; then
echo "ERROR: Could not determine user email"
exit 1
fi
log "Detected email: $DETECTED_EMAIL"
for browser in $BROWSER_LIST; do
echo ""
log "Processing browser: $browser"
if ! browser_installed "$browser"; then
log "Skipping $browser: browser app not installed"
continue
fi
case "$browser" in
chrome)
CHROME_POLICY_PLIST="$MANAGED_PREFS/com.google.Chrome.plist"
CHROME_EXTENSION_PLIST="$MANAGED_PREFS/com.google.Chrome.extensions.$CHROME_EXTENSION_ID.plist"
ensure_force_install_extension \
"Chrome" \
"$CHROME_POLICY_PLIST" \
"$CHROME_EXTENSION_ID" \
"$CHROME_UPDATE_URL"
set_extension_policy "$CHROME_EXTENSION_PLIST" "$DETECTED_EMAIL"
;;
edge)
EDGE_POLICY_PLIST="$MANAGED_PREFS/com.microsoft.Edge.plist"
EDGE_EXTENSION_PLIST="$MANAGED_PREFS/com.microsoft.Edge.extensions.$EDGE_EXTENSION_ID.plist"
ensure_force_install_extension \
"Edge" \
"$EDGE_POLICY_PLIST" \
"$EDGE_EXTENSION_ID" \
"$EDGE_UPDATE_URL"
set_extension_policy "$EDGE_EXTENSION_PLIST" "$DETECTED_EMAIL"
;;
*)
log "Skipping invalid browser entry: $browser"
;;
esac
done
# Flush preferences cache so Chrome/Edge picks up changes immediately
killall cfprefsd >/dev/null 2>&1 || true
echo ""
echo "**** Josys macOS Extension Policy Setup End ****"
exit 0
How to configure Josys macOS Extension script
Step 1: Log in to your MDM and go to the section where scripts can be configured to run on enrolled devices.
Configure the script to run as root and include the required variables. Replace your_org_key with the organization key found in Josys.
sudo ORGANIZATION_KEY="your_org_key" ./josys_extension_policy_setup.sh
Step 2: Create and configure a policy to deploy the script to the enrolled devices.