User Templates and Dock.app default.plist

yr_joelbruner
New Contributor III

To celebrate the 4th of July I offer you this...
Freedom from the tyranny of old code and poor plist parsing in regards to Dock Items :]

Issue(s): 1) Adding icons via the JSS "Dock Icon" method, User Templates are not affected unless a com.apple.dock.plist exists there, but they haven't since 10.5 . Verbose output of policy adding icons:

verbose: No dock found in //System/Library/User Template/English.lproj/Library/Preferences/

2) If you copy in the default.plist dock file from Dock.app where they now live, the JSS will obliterate every entry within when it writes to it! Why? It overwrites persistent-apps and persistent-others if a GUID element is not present on the first item of a category. This is an artificial limitation in the plist parsing code.

3) Even if you put a GUID on every item, duplication is also possible because file-label is checked rather than the _CFURLString.

Solution: setUserTemplateDefaultDock - this will: 1) Iterate through all or some of the lproj folders found within /System/Library/User Template/*/Library/Preferences looking for com.apple.dock.plist. If it is not found it will copy in the corresponding plist from /System/Library/CoreServices/Dock.app/Contents/Resources/*.lproj/default.plist.

2) Add a GUID of 0 to every item to avoid trouncing by JSS Dock Items. While adding to the first element alone seems to be sufficient, an extra few seconds to add it to every item seems worth it.

3) Adds file-label to every item also, otherwise icon duplication will occur if the icon is added in some other policy.

So even if you use dockUtil for your Dock needs, this is a nice addition since it preps the default Dock plist to have what the JSS expects while also putting the default dock plist in the User Templates rather than Dock.app. Why is that important? OS updates or upgrades might overwrite default.plist and destroy your work, so let's just stay out of Dock.app and not worry about that. Run it at imaging time and get everything all set up first before any icons are added or removed.

So let's celebrate freedom - from the tyranny of poor plist parsing ;)
Cheers, Joel

When added as a script to the JSS Argument 4 label should read:
"Locale folder(s) or Blank for All"

Arguments can be none and it will fix all localizations or put in a space or comma separated list of the locales from /System/Library/User Template/ you'd like to fix, for example: "English.lproj Spanish.lproj es_MX.lproj"

Here's sample output:
d9202e15714b4c979bdce9a87d677651

#!/bin/bash
#Joel Bruner (aka brunerd)

#for debugging output: touch /tmp/debug
#to turn off: rm /tmp/debug
[ -f /tmp/debug ] && set -x

#############
# VARIABLES #
#############
#can specify space or comma separated locales
templateLocaleList="$4"

JSSDockRemediationFlag=1

#############
# FUNCTIONS #
#############

function getTemplateDockList
{
local thisLocale;
local dockList_CSV;
local thisDockPath;
local templateLocaleList="$1"

#if nothing is specified do ALL template folders
if [ -z "$templateLocaleList" ]; then
    #a way to get all the lproj locales in User Template
    local templateLocaleList=$(ls -1d /System/Library/User Template/*lproj | tr / $'
' | grep lproj)
fi

#can be just space seperated but just in case
IFS=$' ,
	'
#loop through one or more locales
for thisLocale in $templateLocaleList; do
    if [ -d "/System/Library/User Template/${thisLocale}" ] ;then
        #make a path to where the pref would live in the User Template
        thisDockPath="/System/Library/User Template/${thisLocale}/Library/Preferences/com.apple.dock.plist"
        #add this Dock to List
        dockList_CSV+="$thisDockPath,"
    else
        echo "[NO_LOCALE] ${thisLocale}" > /dev/stderr
    fi

done
#return list of User Template Docks
echo "$dockList_CSV"
}

function ensureDockUserTemplateExists
{
local dockList_CSV="$1"
local thisDockPath;

#set IFS to recognize commas and omit spaces
IFS=$',
	'
for thisDockPath in $dockList_CSV; do
    #if localized user template does not exist for some reason, let's copy it in
    if [ ! -f "$thisDockPath" ]; then             
        #get the local from the path
        thisLocale=$(tr / $'
' <<< "$thisDockPath" | grep lproj)

        #translate some ye olde User Template locales to the modern two letter xx.lproj format
        case $thisLocale in
        Dutch.lproj)
        thisLocale=nl.lproj
        ;;
        English.lproj)
        thisLocale=en.lproj
        ;;
        French.lproj)
        thisLocale=fr.lproj
        ;;
        German.lproj)
        thisLocale=de.lproj
        ;;
        Italian.lproj)
        thisLocale=it.lproj
        ;;
        Japanese.lproj)
        thisLocale=ja.lproj
        ;;
        Spanish.lproj)
        thisLocale=es.lproj
        ;;
        esac

        #copy in default dock from Dock.app
        dockAppDefaultPath=/System/Library/CoreServices/Dock.app/Contents/Resources/"$thisLocale"/default.plist
        if [ -f "$dockAppDefaultPath" ] ;then
            echo "[COPYING] $dockAppDefaultPath to $thisDockPath"
            cp "$dockAppDefaultPath" "$thisDockPath"
        else
            echo "[NOT FOUND] $dockAppDefaultPath"
        fi
    fi
done
}


function jssDockRemediator 
{
#the dock file
local dockFilePath="$1"

#when the item is found set this
local itemPosition;
#tally up the total number of item
local itemCount;

echo "[EXAMINING] $dockFilePath"

#look through both dock categories (right and left side)
for itemCategory in persistent-apps persistent-others; do
    #just in case a loop occurs let's limit it to 100 items
    for (( dockItemID=0; dockItemID < 100; dockItemID++ )); do 

        #get dockItem name from _CFURLString
        local dockItem=$(/usr/libexec/PlistBuddy "$dockFilePath" -c "print $itemCategory:$dockItemID:tile-data:file-data:_CFURLString" 2> /dev/null);
        exitCode=$?

        #if empty try reading 'home directory relative' (used by template default.plist)
        if [ -z "$dockItem" ]; then
            #test and pipe stderr to stdout for tesing
            dockItem=$(/usr/libexec/PlistBuddy "$dockFilePath" -c "print $itemCategory:$dockItemID:tile-data:'home directory relative'" 2>&1);
            exitCode=$?
        fi

        #might be at the end so return now if that's the case
        #PlistBuddy used to not return error codes so the double brackets let's us test for wildcard strings
        if [ -z "$dockItem" -o "$exitCode" -ne 0 ] || [[ "$dockItem" == *Does Not Exist ]]; then 
            break;
        fi

        #JSS Dock Item flaw, we must ensure a GUID exists for first element other Dock is obliterated do all the be sure
        #value can be 0, Dock.app will be correct and make unique
        local GUID=$(/usr/libexec/PlistBuddy "$dockFilePath" -c "print $itemCategory:$dockItemID:GUID" 2>/dev/null)
        if [ -z "$GUID" ]; then
            echo "[JSSFIX] GUID 0 added to item $dockItem ($itemCategory:$dockItemID)"
            /usr/libexec/PlistBuddy "$dockFilePath" -c "add $itemCategory:$dockItemID:GUID integer 0"
        fi

        local fileLabel=$(/usr/libexec/PlistBuddy "$dockFilePath" -c "print $itemCategory:$dockItemID:tile-data:file-label" 2>/dev/null)
        #write label if the label is empty and not just ~
        if [ -z "$fileLabel" -a "$dockItem" != '~' ]; then
        local fileBaseName="$(basename "$dockItem")"
        fileLabel="${fileBaseName%.*}"
            echo "[JSSFIX] Missing File Label "$fileLabel" added to item $dockItem ($itemCategory:$dockItemID)"
            /usr/libexec/PlistBuddy "$dockFilePath" -c "add $itemCategory:$dockItemID:tile-data:file-label string $fileLabel"
        fi      
    done
done
}

##########
## MAIN ##
##########

#get list of all paths to where all User Template com.apple.dock.plist would live
dockList_CSV="$(getTemplateDockList "$templateLocaleList")"
#ensure they all exist, copying in the default.plist from Dock.app if necessary
ensureDockUserTemplateExists "$dockList_CSV"

#hopefully we can remove this one day
#currently the Dock Item code on the JSS makes assumptions on the com.apple.dock.plist that aren't true if it is the default.plist from Dock.app
#If a GUID is not present on the 1st elements of persistent-others and persistent-apps it will be overwritten completely
#file-label is required to avoid duplication, even though _CFURLString would be a truly unique value
#since template plists have never been touch by Dock.app they lack both these things - we can "fix" that
if [ "$JSSDockRemediationFlag" -eq 1 ]; then
    #set IFS to recognize commas and omit spaces
    IFS=$',
	'

    #go through each dock plist
    for dockFilePath in $dockList_CSV; do
        jssDockRemediator "$dockFilePath"
    done
fi
2 REPLIES 2

lucas_vance
New Contributor
New Contributor

Hi yr_joelbruner,

I have seen some issues with our policy in creating dock items. I do agree there is something going on where the persistent-apps and persistent-others arrays are being damaged. I have found a viable solution to address this issue. Below is a copy of a bash script I am using to leverage defaults write to build out my dock. Because the JSS binary is doing the heavy lifting, we have to inform the JSS that we want the user to perform the write, so it writes to the correct com.apple.dock.plist. We understand this causing a lot of frustration and also is breaking our end users docks. Please see the sample script below and I hope this helps out until we can address this issue from the binary level.

!/bin/bash

#### Clean Dock Up #####

sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults delete com.apple.dock persistent-apps
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults delete com.apple.dock persistent-others

#### Add Apps to the Dock ###

sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/System Preferences.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/App Store.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/Google Chrome.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/Safari.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-apps -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/iTunes.app</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'

#### Add Folders to the Dock ####
#### User variable ####

user=ls -l /dev/console | awk '{print $3}'
documents='<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Users/'$user'/Documents</string><key>_CFURLStringType</key><integer>0</integer></dict><key>showas</key><integer>2</integer></dict><key>tile-type</key><string>directory-tile</string></dict>'
downloads='<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Users/'$user'/Downloads</string><key>_CFURLStringType</key><integer>0</integer></dict><key>showas</key><integer>2</integer></dict><key>tile-type</key><string>directory-tile</string></dict>'

sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-others -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-others -array-add '<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>/Applications/Utilities</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>'
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-others -array-add $documents
sudo -u $(ls -l /dev/console | awk '{print $3}') /usr/bin/defaults write com.apple.dock persistent-others -array-add $downloads

killall -HUP Dock

tcam
Contributor

My solution was to use Apple Workgroup Manager to create MCX of the dock I wanted. And then export/import that MCX into the JSS.