Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 1898 lines (1659 sloc) 62.7 KB
#!/bin/bash
#
# patchoo!
# ========
# Casper patching done right!
#
# https://github.com/patchoo/patchoo
#
# patchoo somewhat emulates munki workflows and user experience for JAMF's Casper.
#
###################################
#
# start configurable settings
#
###################################
name="patchoo"
version="0.9958"
# read only api user please!
apiuser="apiuser"
apipass="apipass"
datafolder="/Library/Application Support/patchoo"
pkgdatafolder="$datafolder/pkgdata"
prefs="$datafolder/com.github.patchoo"
cdialog="/Applications/Utilities/cocoaDialog.app" #please specify the appbundle rather than the actual binary
jamfhelper="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper"
# Where's the jamf binary stored? This is for SIP compatibility.
jamf_binary=`/usr/bin/which jamf`
if [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ ! -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/sbin/jamf"
elif [[ "$jamf_binary" == "" ]] && [[ ! -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/local/bin/jamf"
elif [[ "$jamf_binary" == "" ]] && [[ -e "/usr/sbin/jamf" ]] && [[ -e "/usr/local/bin/jamf" ]]; then
jamf_binary="/usr/local/bin/jamf"
fi
# a custom trigger to install cocoadialog if it's not present
installcdttrigger="installcd"
# if you are using a self signed cert for you jss, tell curl to allow it.
selfsignedjsscert=true
# users can defer x update prompts
defermode=true
defaultdeferthresold="10"
# REALLY forces a logout when defers run out
nastymode=true
# users running blocking apps will have x number of prompts delayed, ie will not run on prompt/remindto install until threshold is reached
blockingappmode=true
defaultblockappthreshold="2" # if missed at lunch, then 2x2 hours later... should prompt in afternoon?
# if these apps are in the foreground notifications will not be displayed, presentation apps ? (check with names- sleep 5; osascript -e 'tell application "System Events"' -e 'set frontApp to name of first application process whose frontmost is true' -e 'end tell')
blockingapps=( "Microsoft PowerPoint" "Keynote" )
# this order will correspond to the updatetriggers and asurelease catalogs
# eg. jssgroup[2]="patchooBeta"
# updatetrigger[2]="update-beta"
# asureleasecatalog[2]="beta"
#
# index 0 is the production group and is assumed unless the client is a member of any other groups
jssgroup[0]="----PRODUCTION----"
jssgroup[1]="patchooDev"
jssgroup[2]="patchooBeta"
# these triggers are run based on group membership, index 0 is run after extra group.
patchooswreleasemode=true
updatetrigger[0]="update"
updatetrigger[1]="update-dev"
updatetrigger[2]="update-beta"
# if using patchoo asu release mode these will be appended to computer's SoftwareUpdate server catalogs as per reposado forks -- if not using asu release mode the computer's SwUpdate server will remain untouched.
# eg. http://swupdate.your.domain:8088/content/catalogs/others/index-leopard.merged-1${asureleasecatalogs[i]}.sucatalog
patchooasureleasemode=true
asureleasecatalog[0]="prod"
asureleasecatalog[1]="dev"
asureleasecatalog[2]="beta"
#
# patchooDeploy settings
#
pdusebuildea=true
pdusesites=true
pdusedepts=true
pdusebuildings=true
pdsetcomputername=true # prompt to set computername
# the name of your ext attribute to use as the patchooDeploy build identfier - a populated dropdown EA.
pdbuildea="patchoo Build"
# do you want to prompt the console user to set attributes post enrollment? (not possible post casper imaging)
pdpromptprovisioninfo=true
# this api user requires update/write access to computer records (somewhat risky putting in here - see docs)
# leaving blank will prompt console user for a jss admin account during attribute set (as above)
pdapiadminname=""
pdapiadminpass=""
pddeployreceipt="/Library/Application Support/JAMF/Receipts/patchooDeploy" # this fake receipt internally, and to communicate back to the jss about different patchoo deploy states.
#########################################
#
# configure user prompts and feedback.
#
#########################################
msgtitlenewsoft="New Software Available"
msgnewsoftware="The following new software is available"
msginstalllater="(You can perform the installation later via Self Service)"
msgnewsoftforced="The following software must be installed now!"
msgbootstrap="Mac is provisioning. Do not interrupt or power off."
msgbootstapdeployholdingpattern="Awaiting provisioning information. Your admin has been notified."
msgpatchoodeploywelcome="Welcome to patchoo deploy.
We are gathering provisioning information"
msgshortfwwarn="
IMPORTANT: A firmware update will be installed.
Ensure you connect AC power before starting the update process."
msgshortoswarn="
IMPORTANT: A major OS X upgrade will be performed.
Ensure you connect AC power before starting the update process.
It could take up to 90 minutes to complete."
msgfirmwarewarning="
Firmware updates will be installed after your computer restarts.
Please ensure you are connected to AC Power! Do NOT touch any keys or the power button! A long tone will sound and your screen may be blank for up to 5 minutes.
IT IS VERY IMPORTANT YOU DO NOT INTERRUPT THIS PROCESS AS IT MAY LEAVE YOUR MAC INOPERABLE"
msgosupgradewarning="
Your computer is peforming a major OS X upgrade.
Please ensure you are connected to AC Power! Your computer will restart and the OS upgrade process will continue. It will take up to 90 minutes to complete.
IT IS VERY IMPORTANT YOU DO NOT INTERRUPT THIS PROCESS AS IT MAY LEAVE YOUR MAC INOPERABLE"
iconsize="72"
dialogtimeout="210"
lockscreenlogo="/System/Library/CoreServices/Installer.app/Contents/Resources/Installer.icns" # used for fauxLogout (ARD LockScreen will display this) and bootstrap
# log to the jamf log.
logto="/var/log/"
log="jamf.log"
##################################
##################################
##
## end of configurable settings
##
##################################
##################################
osxversion=$(sw_vers -productVersion | cut -f-2 -d.) # we don't need patch version
udid=$( ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { split($0, line, "\""); printf("%s\n", line[4]); }' )
OLDIFS="$IFS"
IFS=$'\n'
if [ ! -d "$cdialog" ]
then
echo "INFO: CocoaDialog is missing. Triggering JSS install policy."
$jamf_binary policy -trigger $installcdttrigger
fi
# command line paramaters
mode="$4"
prereqreceipt="$5"
prereqpolicy="$(echo "$6" | sed -e 's/ /\+/g')" # change out " " for +
option="$7"
spawned="$1" # used internally
if $selfsignedjsscert
then
curlopts="-k"
else
curlopts=""
fi
cdialogbin="${cdialog}/Contents/MacOS/cocoaDialog"
bootstrapagent="/Library/LaunchAgents/com.github.patchoo-bootstrap.plist"
jssgroupfile="$datafolder/$name-jssgroups.tmp"
# set and read preferences
computername=$(scutil --get ComputerName)
jssurl=$(defaults read /Library/Preferences/com.jamfsoftware.jamf "jss_url" 2> /dev/null)
daystamp=$(( $(date +%s) / 86400 )) # days since 1-1-70
# due to issue with cocoaDialog outside of user session, this check as been added
#case $osxversion in
# 10.5 | 10.6 | 10.7 | 10.8 )
# displayatlogout=true
# ;;
# *)
# displayatlogout=false
# ;;
#esac
# cocoaDialog issue fixed - using https://github.com/loceee/cocoadialog. we need to undo fauxLogout and change workflow back at some stage.
displayatlogout=true
# create the data folder if it doesn't exist
[ ! -d "$datafolder" ] && mkdir -p "$datafolder"
[ ! -d "$pkgdatafolder" ] && mkdir -p "$pkgdatafolder"
chmod 700 "$datafolder"
# if there is no receipt dir, best make one... derp.
[ ! -d "/Library/Application Support/JAMF/Receipts" ] && mkdir -p "/Library/Application Support/JAMF/Receipts"
# DEBUG STUFF
if [ -f "$datafolder/.patchoo-debug" ]
then
set -vx # DEBUG.
debugpath="$datafolder/debuglogs"
mkdir -p "$debugpath"
debuglogfile="$debugpath/patchoo${mode}-$(date "+%F_%H-%M-%S").log"
exec > "$debuglogfile" 2>&1
fi
# check and write installs avail
installsavail=$(defaults read "$prefs" InstallsAvail 2> /dev/null)
if [ "$?" != "0" ]
then
defaults write "$prefs" InstallsAvail -string "No"
installsavail="No"
fi
# set defaults for defer and blockingapp counts
# defer is the # of times a user can defer updates
deferthreshold=$(defaults read "$prefs" DeferThreshold 2> /dev/null)
if [ "$?" != "0" ]
then
defaults write "$prefs" DeferThreshold -int $defaultdeferthresold
deferthreshold=$defaultdeferthresold
fi
defercount=$(defaults read "$prefs" DeferCount 2> /dev/null)
if [ "$?" != "0" ]
then
defaults write "$prefs" DeferCount -int 0
defercount=0
fi
# blockingapp is the # of times a blocking app can block a prompt
blockappthreshold=$(defaults read "$prefs" BlockingAppThreshold 2> /dev/null)
if [ "$?" != "0" ]
then
defaults write "$prefs" BlockingAppThreshold -int $defaultblockappthreshold
blockappthreshold=$defaultblockappthreshold
fi
blockappcount=$(defaults read "$prefs" BlockingAppCount 2> /dev/null)
if [ "$?" != "0" ]
then
defaults write "$prefs" BlockingAppCount -int 0
blockappcount=0
fi
# if the bootstrap agent exists, set bootstrapmode
if [ -f "$bootstrapagent" ]
then
bootstrapmode=true
else
bootstrapmode=false
fi
# make tmp folder
patchootmp="$(mktemp -d -t patchoo)"
#
# common functions
#
secho()
{
# superecho - writes to log and will display a dialog to gui with timeout
message="$1"
timeout="$2"
title="$3"
icon="$4"
if [ "$timeout" != "" ]
then
echo "$name: USERNOTIFY: $title, $message"
echo "$(date "+%a %b %d %H:%M:%S") $computername $name-$version $mode: USERNOTIFY: $title, $message" >> "$logto/$log"
if [ "$(checkConsoleStatus)" == "userloggedin" ]
then
[ "$title" == "" ] && title="Message"
[ "$icon" == "" ] && icon="notice"
"$cdialogbin" bubble --title "$title" --text "$message" --icon "$icon" --timeout "$timeout" &
fi
else
echo "$name: $message"
echo "$(date "+%a %b %d %H:%M:%S") $computername $name-$version $mode: $message" >> "$logto/$log"
fi
}
displayDialog()
{
text="$1" # core message
title="$2" # menubar title
title2="$3" # bold title
icon="$4" # http://mstratman.github.io/cocoadialog/#documentation3.0/icons
button1="$5"
button2="$6"
button3="$7"
# show the dialog...
"$cdialogbin" msgbox --title "$title" --icon "$icon" --text "$title2" --informative-text "$text" --timeout "$dialogtimeout" --button1 "$button1" --button2 "$button2" --button3 "$button3" --icon-height "$iconsize" --icon-width "$iconsize" --width "500" --string-output
}
makeMessage()
{
message="$message
$1"
}
checkConsoleStatus()
{
userloggedin="$(who | grep console | awk '{print $1}')"
consoleuser="$(ls -l /dev/console | awk '{print $3}')"
screensaver="$(pgrep ScreenSaverEngine)"
if [ "$screensaver" != "" ]
then
# screensaver is running
echo "screensaver"
return
fi
if [ "$userloggedin" == "" ]
then
# no users logged in (at loginwindow)
echo "nologin"
return
fi
if [ "$userloggedin" != "$consoleuser" ]
then
# a user is loggedin, but we are at loginwindow or we have multiple users logged in with switching (too hard for now)
echo "loginwindow"
return
fi
if $blockingappmode
then
# get foreground app
fgapp=$(sudo -u "$userloggedin" osascript -e "tell application \"System Events\"" -e "return name of first application process whose frontmost is true" -e "end tell" 2> /dev/null) # avoid errors in log
# check for blocking apps
for app in ${blockingapps[@]}
do
if [ "$app" == "$fgapp" ]
then
echo "BlockingApp: $app"
return
fi
done
fi
# if we passed all checks, user is logged in and we are safe to prompt or display bubbles
echo "userloggedin"
}
checkProcess()
{
if [ "$(pgrep "$1")" != "" ]
then
return 0
else
return 1
fi
}
spawnScript()
{
# we use this so we can execute from self service.app and call a logout with out breaking policy execution.
# the script copies, then spawns itself
if [ "$spawned" != "--spawned" ]
then
tmpscript="$datafolder/$name-$RANDOM.sh"
cp "$0" "$tmpscript"
# spawn the script in the background
secho "spawned script $tmpscript"
"$tmpscript" --spawned '' '' $mode &
cleanUp
exit 0
fi
}
#
# the mains brains.
#
cachePkg()
{
# run after a pkg is cached in a policy
# - checks for prereqs and calls policies if receipts not found
# - gets pkg data from jss api and gives pkg friendly name in the gui
#
# find the latest addition to the Waiting Room
pkgname=$(ls -t "/Library/Application Support/JAMF/Waiting Room/" | head -n 1 | grep -v .cache.xml)
if [ ! -f "$pkgdatafolder/$pkgname.caspinfo" ] && [ "$pkgname" != "" ]
then
pkgext=${pkgname##*.} # handle zipped bundle pkgs
[ "$pkgext" == "zip" ] && pkgnamelesszip=$(echo "$pkgname" | sed 's/\(.*\)\..*/\1/')
# get pkgdata from the jss api
curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/packages/name/$(echo "$pkgname" | sed -e 's/ /\+/g')" -X GET > "$pkgdatafolder/$pkgname.caspinfo.xml"
# (error checking)
pkgdescription=$(cat "$pkgdatafolder/$pkgname.caspinfo.xml" | xpath //package/info 2> /dev/null | sed 's/<info>//;s/<\/info>//')
if [ "$pkgdescription" == "<info />" ] || [ "$pkgdescription" == "" ] # if it's no pkginfo in jss, set pkgdescription to pkgname (less ext)
then
if [ "$pkgext" == "zip" ]
then
pkgdescription=$(echo "$pkgname" | sed 's/\(.*\)\..*/\1/')
else
pkgdescription=$(echo "$pkgnamelesszip" | sed 's/\(.*\)\..*/\1/')
fi
fi
echo "$pkgdescription" > "$pkgdatafolder/$pkgname.caspinfo"
# if it's flagged as an OS Upgrade (using createOSXInstallPkg), add osupgrade flag
[ "$option" == "--osupgrade" ] && touch "$pkgdatafolder/.os-upgrade"
secho "jamf has cached $pkgname"
secho "$pkgdescription" 2 "Downloaded" "globe"
# flag that we need a recon
touch "$datafolder/.patchoo-recon-required"
if [ "$prereqreceipt" != "" ]
then
# we need to check for a prereq casper receipt
if [ ! -f "/Library/Application Support/JAMF/Receipts/$prereqreceipt" ]
then
# the receipt wasn't found
# query the JSS for the prereqpolicy
secho "$prereqreceipt is required and NOT found"
secho "querying jss for policy $prereqpolicy to install $prereqreceipt"
prereqpolicyid=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/policies/name/$(echo "$prereqpolicy" | sed -e 's/ /\+/g')" -X GET | xpath //policy/general/id 2> /dev/null | sed -e 's/<id>//;s/<\/id>//')
# (error checking)
# let's run the preq policy via id
# this is how we chain incremental updates
$jamf_binary policy -id "$prereqpolicyid"
fi
fi
else
secho "i couldn't find a new pkg in the waiting room. :("
fi
}
checkASU()
{
if [ -f "$pkgdatafolder/.os-upgrade" ]
then
secho "os upgrade is cached, skipping apple software updates.."
return
fi
if $patchooasureleasemode
then
getGroupMembership
setASUCatalogURL
fi
swupdateout="$patchootmp/swupdateout-$RANDOM.tmp"
secho "checking for apple software updates ..."
softwareupdate -la > "$swupdateout"
# check if there are any updates
if [ "$(cat "$swupdateout" | grep "\*")" != "" ]
then
# let's parse the updates
asupkgarray=( $(cat "$swupdateout" | grep "\*" | cut -c6- ) )
asudescriptarray=( $(cat "$swupdateout" | grep -A2 "\*" | grep -v "\*" | cut -f1 -d, | cut -c2- | sed 's/[()]//g' ) )
# first clean up any packages that were installed from the appstore - thanks galenrichards
find "$pkgdatafolder" -iname "*.asuinfo" | while read f
do
basefile=$(basename "$f" ".asuinfo")
if [[ ! " ${asupkgarray[@]//.} " =~ " ${basefile//.} " ]]; then
secho "$basefile not available or already installed. Removing..."
rm $f
fi
done
i=0
for asupkg in ${asupkgarray[@]}
do
if [ ! -f "$pkgdatafolder/$asupkg.asuinfo" ] # it hasn't been downloaded
then
secho "softwareupdate is downloading $asupkg"
softwareupdate -d "$asupkg"
# (insert error checking)
echo "${asudescriptarray[$i]}" > "$pkgdatafolder/$asupkg.asuinfo"
secho "${asudescriptarray[$i]}" 2 "Downloaded" "globe"
# flag that we need a recon
touch "$datafolder/.patchoo-recon-required"
else
secho "$asupkg already downloaded."
fi
(( i ++ ))
done
# check for restart required
if [ "$(cat "$swupdateout" | grep "\[restart\]")" != "" ]
then
touch "$pkgdatafolder/.restart-required"
fi
else
secho "no updates found."
fi
rm "$swupdateout"
}
setASUCatalogURL()
{
# in patchooasureleasemode mode patchoo takes care of re-writing client catalog urls so you can have dev/beta/prod catalogs based on jss group membership
# you set your catalogURL / swupdate server as you usually do in Casper, and it will re-write OS and branch specific URLs based on the local CatalogURL.
# it assumes that you have turned off updates via other mechanisms
currentswupdurl=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate CatalogURL 2> /dev/null)
if [ "$currentswupdurl" != "" ]
then
asuserver="$(echo "$currentswupdurl" | cut -f-3 -d/)"
case $osxversion in
10.5)
swupdateurl="$asuserver/content/catalogs/others/index-leopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
10.6)
swupdateurl="$asuserver/content/catalogs/others/index-leopard-snowleopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
10.7)
swupdateurl="$asuserver/content/catalogs/others/index-lion-snowleopard-leopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
10.8)
swupdateurl="$asuserver/content/catalogs/others/index-mountainlion-lion-snowleopard-leopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
10.9)
swupdateurl="$asuserver/content/catalogs/others/index-10.9-mountainlion-lion-snowleopard-leopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
10.10)
swupdateurl="$asuserver/content/catalogs/others/index-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1_${asureleasecatalog[$groupid]}.sucatalog"
;;
*)
secho "I can't do this OS X version.. sadface."
return
;;
esac
secho "setting asu CatalogURL to $swupdateurl"
defaults write /Library/Preferences/com.apple.SoftwareUpdate CatalogURL "$swupdateurl"
else
secho "no asu server set, using apple's ..."
fi
}
buildUpdateLists()
{
# make software install list tmp files for processing later
#
# don't expand a nullglob (if there are no matching *.xxx)
shopt -s nullglob
# casper pkgs
casppkginfo="$patchootmp/casppkginfo-$RANDOM.tmp"
for infofile in "$pkgdatafolder/"*.caspinfo
do
# parse the priority from casper xml
casppriority=$(cat "${infofile}.xml" | xpath //package/priority 2> /dev/null | sed 's/<priority>//;s/<\/priority>//')
casppkg=$(basename "$infofile") #get rid of path
casppkg="${casppkg%\.*}" #remove ext.
casppkgdescrip=$(cat "$infofile")
echo -e "${casppriority}\t${casppkg}\t${casppkgdescrip}" >> "$casppkginfo"
done
# if there is an OS Upgrade packge cached in the casper installs, skip the apple updates
if [ -f "$pkgdatafolder/.os-upgrade" ]
then
secho "osupgrade is in casper, skipping apple software updates"
else
# asu pkgs
asupkginfo="$patchootmp/asupkginfo-$RANDOM.tmp"
for infofile in "$pkgdatafolder/"*.asuinfo
do
# check for SMC, EFI and Firmware updates, flag if so
[ "$(echo "$infofile" | grep EFIUpdate)" != "" ] && touch "$pkgdatafolder/.fw-update"
[ "$(echo "$infofile" | grep SMCUpdate)" != "" ] && touch "$pkgdatafolder/.fw-update"
[ "$(echo "$infofile" | grep Firmware)" != "" ] && touch "$pkgdatafolder/.fw-update"
# set priorities for system and sec updates
asupriority=1
# OSX supplemental for 10.8.5 broke the rules, not OSXUpd... this could catch other things too... hmm.
[ "$(echo "$infofile" | grep OSX)" != "" ] && asupriority="98"
# if it's a security or OSX update make it 99
[ "$(echo "$infofile" | grep SecUpd)" != "" ] && asupriority="99"
[ "$(echo "$infofile" | grep OSXUpd)" != "" ] && asupriority="99"
asupkg=$(basename "$infofile") #get rid of path
asupkg="${asupkg%\.*}" #remove ext.
asupkgdescrip=$(cat "$infofile")
echo -e "${asupriority}\t${asupkg}\t${asupkgdescrip}" >> "$asupkginfo"
done
fi
[ -f "$casppkginfo" ] && sort "$casppkginfo" -o "$casppkginfo" # sort the file for priority
[ -f "$asupkginfo" ] && sort "$asupkginfo" -o "$asupkginfo" # sort the file for priority
if [ -f "$casppkginfo" ] || [ -f "$asupkginfo" ]
then
# installs are available, write pref, it also will be picked up by ext attribute to make a smart group.
defaults write "$prefs" InstallsAvail -string "Yes"
installsavail="Yes"
fi
# some output to the log file if not --quiet
if [ -f "$casppkginfo" ] && [ "$1" != "--quiet" ]
then
secho "Casper pkgs waiting to be installed"
secho "--------------------------------------"
while read line
do
secho "$(echo "$line" | cut -f2)"
done < "$casppkginfo"
secho "--------------------------------------"
fi
if [ -f "$asupkginfo" ] && [ "$1" != "--quiet" ]
then
secho "swupdate pkgs waiting to be installed"
secho "-------------------------------------"
while read line
do
secho "$(echo "$line" | cut -f2)"
done < "$asupkginfo"
secho "-------------------------------------"
fi
}
installCasperPkg()
{
caspline="$1"
casppkg=$(echo "$caspline" | cut -f2)
infofile="$pkgdatafolder/${casppkg}.caspinfo"
jamfinstallopts=""
# check if a reboot is required by casper package, flag if it is.
[ "$(cat "${infofile}.xml" | grep "<reboot_required>true</reboot_required>")" != "" ] && touch "$pkgdatafolder/.restart-required"
# check for fut and feu
[ "$(cat "/Library/Application Support/JAMF/Waiting Room/$casppkg.cache.xml" | grep "<fut>true</fut>")" != "" ] && jamfinstallopts="$jamfinstallopts -fut"
[ "$(cat "/Library/Application Support/JAMF/Waiting Room/$casppkg.cache.xml" | grep "<feu>true</feu>")" != "" ] && jamfinstallopts="$jamfinstallopts -feu"
secho "jamf is installing $casppkg"
$jamf_binary install "$jamfinstallopts" -package "$casppkg" -path "/Library/Application Support/JAMF/Waiting Room" -target /
# (insert error checking)
# remove from the waiting room
if [ -d "/Library/Application Support/JAMF/Waiting Room/$casppkg" ]
then
# non-flat pkg
rm -R "/Library/Application Support/JAMF/Waiting Room/$casppkg"
else
# flat pkg
rm "/Library/Application Support/JAMF/Waiting Room/$casppkg"
fi
rm "/Library/Application Support/JAMF/Waiting Room/$casppkg.cache.xml"
}
installSoftware()
{
secho "starting installation ..."
# generate the update list tmp files
buildUpdateLists --quiet
# install all software
if [ -s "$casppkginfo" ] # there are casper updates waiting
then
if $bootstrapmode
then
# bootstrap mode doesn't need cocoadialog progress
while read line
do
installCasperPkg "$line"
done < "$casppkginfo"
else
(
# use cocoadialog for gui
currentpercent=0
casptotal=$(cat "$casppkginfo" | wc -l)
total=$(( casptotal * 100 ))
while read line
do
casppkgdescrip=$(echo "$line" | cut -f3)
installCasperPkg "$line" & # background the jamf install, we'll fudge a progressbar
caspinstallpid=$!
# we are fudging a progress bar, count up to 100, increase bar, until done, then
for (( perfectcount=1; perfectcount<=100; perfectcount++ ))
do
percent=$(( ( (perfectcount + currentpercent) * 100 ) / total ))
(( percent == 100 )) && percent=99 # we don't want out progressbar to finish prematurely
echo "$percent Installing $casppkgdescrip ..."
kill -0 "$caspinstallpid" 2> /dev/null
[ "$?" != "0" ] && break # if it's done, break
sleep 1
done
wait "$caspinstallpid" # if we have run out progress bar, wait for pid to complete.
currentpercent=$(( currentpercent + 100 )) # add another 100 for each completed install
done < "$casppkginfo"
echo "100 Installation complete"
sleep 1
[ -f "$pkgdatafolder/.restart-required" ] && echo "100 Restart is required"
sleep 1
) | "$cdialogbin" progressbar --icon installer --float --title "Installing Software" --text "Starting Installation..." --icon-height "$iconsize" --icon-width "$iconsize" --width "500" --height "114"
fi
fi
if [ -s "$asupkginfo" ] # there are apple updates waiting
then
asucount=0
# bootstrap mode, no progress bars
if $bootstrapmode
then
while read line
do
asupkg=$(echo "$line" | cut -f2)
asupkgdescrip=$(echo "$line" | cut -f3)
secho "softwareupdate is installing $asupkg ..."
softwareupdate -v -i "$asupkg"
done < "$asupkginfo"
else
(
currentpercent=0
asutotal=$(cat "$asupkginfo" | wc -l)
total=$(( asutotal * 100 ))
while read line
do
asupkg=$(echo "$line" | cut -f2)
asupkgdescrip=$(echo "$line" | cut -f3)
secho "softwareupdate is installing $asupkg ..."
# spawn the update process, and direct output to tmpfile for parsing (we probably should use a named pipe here... future...)
swupdcmd="softwareupdate -v -i $asupkg"
swupdateout="$patchootmp/swupdateout-$RANDOM.tmp"
softwareupdate -v -i "$asupkg" > "$swupdateout" &
softwareupdatepid=$!
# wait for the software update to finish, parse output of softwareupdate
while kill -0 "$softwareupdatepid" > /dev/null 2>&1
do
sleep 1
# get percent to update progressbar
percentout=$(cat "$swupdateout" | grep "Progress:" | tail -n1 | awk '{print $2}' | sed 's/\%//g')
percent=$(( ( (percentout + currentpercent) * 100 ) / total ))
echo "$percent Installing $asupkgdescrip ..."
done
currentpercent=$(( currentpercent + 100 )) # add another 100 for each completed install
rm "$swupdateout"
done < "$asupkginfo"
echo "100 Installation complete"
sleep 1
[ -f "$pkgdatafolder/.restart-required" ] && echo "100 Restart is required"
sleep 1
) | "$cdialogbin" progressbar --icon installer --float --title "Installing Apple Software Updates" --text "Starting Installation..." --icon-height "$iconsize" --icon-width "$iconsize" --width "500" --height "114"
fi
fi
# flag for a recon since we've installed
touch "$datafolder/.patchoo-recon-required"
# check for restart
if [ -f "$pkgdatafolder/.restart-required" ]
then
secho "restart is required by pkg"
touch /tmp/.patchoo-restart
fi
# if there was an OS upgrade installed, flush out apple updates from system.
if [ -f "$pkgdatafolder/.os-upgrade" ]
then
secho "flushing apple software updates..."
rm -R /Library/Updates/*
touch /tmp/.patchoo-restart
fi
# reset defer counters and flush pkgdata
defaults write "$prefs" DeferCount -int 0
defaults write "$prefs" InstallsAvail -string "No"
installsavail="No"
rm -R "$pkgdatafolder"
rm /tmp/.patchoo-install
}
promptInstall()
{
promptmode="$1"
# build the lists of updates avail
if $bootstrapmode
then
# if boostrapping, build updatelists (also sets installsavail)
buildUpdateLists --quiet
return
else
buildUpdateLists
fi
# if there are no updates
if [ "$installsavail" != "Yes" ]
then
secho "nothing to install"
return
fi
# prompt in self service mode (no defer, and remove flag)
if [ -f "$datafolder/.patchoo-selfservice-check" ]
then
promptmode="--selfservice"
rm "$datafolder/.patchoo-selfservice-check"
fi
# there are waiting updates ... make a message for the user prompt
secho "prompting user..."
message=""
if [ -f "$casppkginfo" ]
then
while read line
do
makeMessage "$(echo "$line" | cut -f3)" # 3rd column is pkg descript
done < "$casppkginfo"
fi
if [ -f "$asupkginfo" ]
then
while read line
do
makeMessage "$(echo "$line" | cut -f3)"
done < "$asupkginfo"
fi
# add warnings if there are firmware/os upgrade pkgs
addWarnings
case $promptmode in
"--logoutinstallsavail" )
#
# logout reminder prompt flags and 'returns' as we are already at a logout, so we can install directly within this session.
#
makeMessage ""
makeMessage "$msginstalllater"
answer=$(displayDialog "$message" "$msgtitlenewsoft" "$msgnewsoftware" "package" "Install and Restart..." "Install and Shutdown..." "Later")
case $answer in
"Install and Restart..." )
secho "user selected install and restart"
touch /tmp/.patchoo-install
touch /tmp/.patchoo-restart
preInstallWarnings
return
;;
"Install and Shutdown..." )
secho "user selected install and shutdown"
touch /tmp/.patchoo-install
touch /tmp/.patchoo-shutdown
preInstallWarnings
return
;;
"Later" )
secho "user selected install later"
return
;;
"timeout" )
secho "timeout... will install and shutdown, the user is probably going home"
touch /tmp/.patchoo-install
touch /tmp/.patchoo-shutdown
preInstallWarnings
return
;;
esac
;;
"--selfservice" )
#
# self service we don't tell people to use self service, we don't update the defer counter
#
answer=$(displayDialog "$message" "$msgtitlenewsoft" "$msgnewsoftware" "package" "Logout and Install..." "Cancel" )
;;
*)
#
# this is the general prompt for the end of the update trigger run
#
# check to see if we can display a prompt
consolestatus="$(checkConsoleStatus)"
# some users just have blocking apps running constantly (Powerpoint / Keynote)
# we need to prompt them at some stage.
if $blockingappmode
then
if [ "$(echo "$consolestatus" | grep "BlockingApp:")" != "" ]
then
blockremain=$(( blockappthreshold - blockappcount ))
if [ $blockremain -eq 0 ]
then
# blockingapp threshold exceeded, we will prompt user ...
consolestatus="userloggedin"
defaults write "$prefs" BlockingAppCount -int 0
else
(( blockappcount ++ ))
secho "$consolestatus - preventing prompt to install."
secho "blockingapp counter: $blockappcount, blockappthreshold: $blockappthreshold"
defaults write "$prefs" BlockingAppCount -int $blockappcount
fi
fi
fi
# is userloggedin, we should display a prompt
if [ "$consolestatus" == "userloggedin" ]
then
if $defermode
then
# check to see if they are allowed to defer anymore
deferremain=$(( deferthreshold - defercount ))
if [ $deferremain -eq 0 ] || [ $deferremain -lt 0 ]
then
# if the defercounter has run out, FORCED INSTALLATION! set timeout to 30 minutes
dialogtimeout="1830"
answer=$(displayDialog "$message" "$msgtitlenewsoft" "$msgnewsoftforced" "package" "Logout and Install...")
# if it's nastymode (tm) we Logout and Install no matter what
[ $nastymode ] && answer="Logout and Install..."
secho "FORCING INSTALL!"
else
# prompt user with defer option
makeMessage ""
makeMessage "$msginstalllater"
answer=$(displayDialog "$message" "$msgtitlenewsoft" "$msgnewsoftware" "package" "Later ($deferremain remaining)" "Logout and Install...")
secho "deferral counter: $defercount, defer thresold: $deferthreshold"
fi
else
# if we don't have deferals enabled
makeMessage ""
makeMessage "$msginstalllater"
answer=$(displayDialog "$message" "$msgtitlenewsoft" "$msgnewsoftware" "package" "Later" "Logout and Install...")
fi
else
# there something preventing a dialog, don't display anything, return the consolestatus
answer="$consolestatus"
fi
;;
esac
# process the answer.
case $answer in
"Logout and Install..." )
# this flags for install, and logs out the user, logout policy picks up the install flag and does installations.
secho "user selected install and logout..."
# we need to logout the user
touch /tmp/.patchoo-install
echo $$ > /tmp/.patchoo-install-pid
preInstallWarnings
fauxLogout & # we spawn it, so if anything goes awry during install, the fauxLogout is a killswitch to put the mac back into line.
# wait for the fauxlogout to do it's thing
while [ ! -f /tmp/.patchoo-logoutdone ]
do
sleep 1
done
installSoftware
rm /tmp/.patchoo-logoutdone
return # once this finishes fauxLogut will handle logout
;;
"Later ($deferremain remaining)" )
# this decreases counter and displays a notification bubble.
secho "user selected install later, incrementing deferal counter.."
(( defercount ++ ))
defaults write "$prefs" DeferCount -int $defercount
deferremain=$(( deferthreshold - defercount ))
if [ $deferremain -eq 0 ]
then
secho "You cannot defer the installation any further. It will be forced on next notice" 8 "Installion Deferred" "caution"
else
secho "You can defer the installation $deferremain more times" 8 "Installion Deferred" "notice"
fi
;;
"Later" )
secho "user chose later" # no deferals
;;
"Cancel" )
secho "user cancelled installation" # only available from self service
;;
* )
# timeout, there are no logins, we are at the loginwindow, or screensaver is running / screen locked, or an app is blocking
# we flag for a run on next notify run, and throw a recon
secho "user missed this prompt - reason: $answer, flagged for reminder..."
touch "$pkgdatafolder/.prompt-missed-$daystamp"
;;
esac
# if we've cached new updates, recon the mac so it falls into the correct smart groups
[ -f "$datafolder/.patchoo-recon-required" ] && jamfRecon
}
addWarnings()
{
if [ -f "$pkgdatafolder/.os-upgrade" ]
then
makeMessage "$msgshortoswarn"
return # we don't want other warnings
fi
if [ -f "$pkgdatafolder/.fw-update" ]
then
makeMessage "$msgshortfwwarn"
fi
}
preInstallWarnings()
{
if [ -f "$pkgdatafolder/.os-upgrade" ]
then
displayDialog "$msgosupgradewarning" "OS Upgrade" "IMPORTANT NOTICE!" "caution" "I Understand... Install and Restart"
touch /tmp/.patchoo-restart-forced
return # we don't want other warnings
fi
if [ -f "$pkgdatafolder/.fw-update" ]
then
displayDialog "$msgfirmwarewarning" "Firmware Update Warning" "IMPORTANT NOTICE!" "stop" "I Understand... Install and Restart"
touch /tmp/.patchoo-restart-forced
fi
}
logoutUser()
{
secho "sending logout..."
osascript -e "ignoring application responses" -e "tell application \"loginwindow\" to $(printf \\xc2\\xab)event aevtrlgo$(printf \\xc2\\xbb)" -e "end ignoring"
}
# fauxLogout - added to workaround cocoaDialog not running outside a user session on mavericks+ - https://github.com/patchoo/patchoo/issues/16
# thanks to Jon Stovell - bits inspired and stolen from quit script - http://jon.stovell.info/
# loops through all user visible apps, quits, writes lsuielement changes to cocoa (prevent dock showing), uses ARD lockscreen to blank screen out.
getAppList()
(
applist="$(sudo -u "$user" osascript -e "tell application \"System Events\" to return displayed name of every application process whose (background only is false and displayed name is not \"Finder\")")"
echo "$applist"
)
quitAllApps()
(
applist=$(getAppList)
applistarray=$(echo "$applist" | sed -e 's/^/\"/' -e 's/$/\"/' -e 's/, /\" \"/g')
eval set "$applistarray"
for appname in "$@"
do
secho "trying to quit: $appname ..."
sudo -u "$user" osascript -e "ignoring application responses" -e "tell application \"$appname\" to quit" -e "end ignoring"
done
)
fauxLogout()
(
secho "starting faux logout..."
user=$(who | grep console | awk '{print $1}')
waitforlogout=30
tryquitevery=3
while [ "$(getAppList)" != "" ]
do
for (( c=1; c<=(( waitforlogout / tryquitevery )); c++ ))
do
quitAllApps
#check if all apps are quit break if so, otherwise fire every $tryquitevery
[ "$(getAppList)" == "" ] && break
sleep $tryquitevery
done
if [ "$(getAppList)" != "" ]
then
# if we still haven't quit all Apps
dialogtimeout=60
secho "apps are still running after $waitforlogout seconds, prompting user and trying quit loop again.."
displayDialog "Ensure you have saved your documents and quit any open applications. You can Force Quit applications that aren't responding by pressing CMD-SHIFT-ESC." "Logging out" "The Logout process has stalled" "caution" "Continue Logout"
quitAllApps
fi
done
# thanks mm2270 (https://github.com/mm2270) for this technique!
# move the standard lock logo out of lockscreen app (we don't want a big padlock)
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.backup
if [ -f "$lockscreenlogo" ]
then
sips -s format png --resampleWidth 512 "$lockscreenlogo" --out "$patchootmp/Lock.jpg" 2> /dev/null # it will throw an error about and png being name jpg
mv "$patchootmp/Lock.jpg" /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg
fi
# lock screen
/System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen & 2> /dev/null
sleep 1
# makes changes to cocoaDialog
defaults write "${cdialog}/Contents/Info.plist" LSUIElement -int 0
defaults write "${cdialog}/Contents/Info.plist" LSUIPresentationMode -int 3
chmod 644 "${cdialog}/Contents/Info.plist"
# put the lock screen logo back, in case system update does something to remotemanagement bundles
mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.backup /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg
touch /tmp/.patchoo-logoutdone
installpid=$(cat /tmp/.patchoo-install-pid)
secho "fauxlogout done, screen locked (my pid: $$) waiting installs (pid: $installpid)..."
while ps -p "$installpid" > /dev/null
do
sleep 1
done
# putting it back order
rm /tmp/.patchoo-install-pid
rm /tmp/.patchoo-logoutdone
# undo changes to cocoaDialog
defaults write "${cdialog}/Contents/Info.plist" LSUIElement -int 1
defaults write "${cdialog}/Contents/Info.plist" LSUIPresentationMode -int 0
chmod 644 "${cdialog}/Contents/Info.plist"
# unlock and logout
killall LockScreen
logoutUser # the logout policy will handle recon / restart / shutdown requests
)
processLogout()
{
if [ "$installsavail" == "Yes" ]
then
# if <10.9 we can use this can prompt outside a user session with cdialog
if $displayatlogout
then
promptInstall --logoutinstallsavail
if [ -f /tmp/.patchoo-install ]
then
# user chose to install updates
preInstallWarnings
installSoftware
else
# user chose later
return
fi
fi
fi
# process a restart or shutdown
if [ -f "$datafolder/.patchoo-recon-required" ]
then
if [ -f /tmp/.patchoo-restart ] || [ -f /tmp/.patchoo-shutdown ]
then
# run on recon on reboot
secho "flagged for a post boot recon"
else
# otherwise no restart required, we can do it in the background whilst user logs back in
jamfRecon &
fi
fi
if [ -f /tmp/.patchoo-restart-forced ]
then
touch /tmp/.patchoo-restart # forced restart to trump shutdown, need restarts for FW and OS Upgrades
[ -f /tmp/.patchoo-shutdown ] && rm /tmp/.patchoo-shutdown # we don't want fwupdates to install and shutdown
rm /tmp/.patchoo-restart-forced
fi
if [ -f /tmp/.patchoo-shutdown ]
then
secho "shutting down now!"
[ -f /tmp/.patchoo-restart ] && rm /tmp/.patchoo-restart # remove this, shutdown trumps restart request by a pkg
rm /tmp/.patchoo-shutdown
shutdown -h now &
fi
if [ -f /tmp/.patchoo-restart ]
then
secho "restarting now!"
rm /tmp/.patchoo-restart
shutdown -r now &
fi
}
jamfPolicyUpdate()
{
# if we are using the swrelease triggers, get groups and run the trigger
if $patchooswreleasemode
then
getGroupMembership
if [ "$groupid" != "0" ]
then
secho "jamf is firing ${updatetrigger[$groupid]} trigger ..."
$jamf_binary policy -trigger "${updatetrigger[$groupid]}"
fi
fi
# once we've got run our group trigger, run the standard...
secho "jamf is firing ${updatetrigger[0]} trigger ..."
$jamf_binary policy -trigger "${updatetrigger[0]}"
}
getGroupMembership()
{
groupid=0
# jss group file, we cache this in a central location so we can minimise number of hits on the jss for an update session.
if [ ! -f "$jssgroupfile" ]
then
secho "getting computer group membership ..."
curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid" | xpath //computer/groups_accounts/computer_group_memberships[1] 2> /dev/null | sed -e 's/<computer_group_memberships>//g;s/<\/computer_group_memberships>//g;s/<group>//g;s/<\/group>/\n/g' > "$jssgroupfile"
fi
for checkgroup in ${jssgroup[@]}
do
# we don't check against the production
if [ "$checkgroup" != "----PRODUCTION----" ]
then
# if find matching group, return out
[ "$(cat "$jssgroupfile" | grep "$checkgroup")" != "" ] && return
fi
(( groupid ++ ))
done
# if we get to here we haven't matched a group, this mac is just production
groupid=0
}
patchooStart()
{
secho "starting triggered patchoo run!"
jamfPolicyUpdate
}
checkUpdatesSS()
{
spawnScript
touch "$datafolder/.patchoo-selfservice-check"
secho "You will be notified if any installations are available" 4 "Checking for new software" "notice"
jamfPolicyUpdate
if [ -f "$datafolder/.patchoo-selfservice-check" ] # the flag is still here, no promptforupdates!
then
displayDialog "There is no new software available at this time." "No New Software Available" "" "info" "Thanks anyway"
rm "$datafolder/.patchoo-selfservice-check"
fi
}
promptInstallSS()
{
spawnScript # spawn so policy can finish and Self Service.app doesn't block logout.
promptInstall --selfservice
}
remindInstall()
{
# run this on every120, scoped to a smart group
if [ "$installsavail" == "Yes" ]
then
if $defermode
then
# naughty users realised they could ignore the prompts... no more... if you miss all prompts in a day, the defer counter is increased the next day. bam.
yesterdaystamp=$(( daystamp - 1 ))
if [ -f "$pkgdatafolder/.prompt-missed-$yesterdaystamp" ]
then
(( defercount ++ ))
secho "user missed prompts yesterday, increasing defer count to $defercount"
defaults write "$prefs" DeferCount -int $defercount
rm "$pkgdatafolder/.prompt-missed-$yesterdaystamp"
fi
if [ -f "$pkgdatafolder/.prompt-missed-$daystamp" ]
then
# if there is missed prompt flag for today, bring up the reminder
rm "$pkgdatafolder/.prompt-missed-$daystamp"
promptInstall
else
# otherwise, a notify bubble
deferremain=$(( deferthreshold - defercount ))
if [ $deferremain -eq 0 ]
then
# no deferrals left. you gotta do it on the next notice!
secho "You can not defer the installation further. Launch Self Service and select Install New Software as soon as possible" 8 "$msgtitlenewsoft" "caution"
else
secho "Please launch Self Service and select Install New Software" 8 "$msgtitlenewsoft" "notice"
fi
fi
else
# no defer mode to process... just show a bubble
secho "Please launch Self Service and select Install New Software" 8 "$msgtitlenewsoft" "notice"
fi
fi
}
startup()
{
# post reboot after install, recon on startup.
[ -f "$datafolder/.patchoo-recon-required" ] && jamfRecon
}
updateHandler()
{
jamfRecon
jamfPolicyUpdate
installsavail=$(defaults read "$prefs" InstallsAvail 2> /dev/null) # check if updates are avaialble
while [ "$installsavail" == "Yes" ]
do
installSoftware
if [ -f /tmp/.patchoo-restart ]
then
secho "restarting now!"
rm /tmp/.patchoo-restart
reboot &
return
fi
# we will either reboot and pickup again at loginwindow
# or run another update and install loop
jamfRecon
jamfPolicyUpdate
installsavail=$(defaults read "$prefs" InstallsAvail 2> /dev/null)
done
# no more updates stop bootstrap
if [ -f "${pddeployreceipt}" ]
then
touch "${pddeployreceipt}.updated" # touch a receipt, can be used for smartgroup notification
jamfRecon
fi
secho "update process complete!"
sleep 10
rm "$bootstrapagent"
rm /Library/Scripts/patchoo.sh
killall jamfHelper
# all done loginwindow is unlocked
}
#
# deploy functions
#
checkAndReadProvisionInfo()
{
secho "reading provisioning info from jss..."
pdprovisiontmp="$patchootmp/patchooprovisioninfo.tmp"
[ -f "$pdprovisiontmp" ] && rm "$pdprovisiontmp"
# read the values as required, if missing, return false
pdprovisioninfo=true
if $pdusebuildea
then
patchoobuild=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid/subset/extension_attributes" | xpath "//*[name='$pdbuildea']/value/text()" 2> /dev/null)
# error checking
echo "patchoobuild: $patchoobuild" >> "$pdprovisiontmp"
[ "$patchoobuild" == "" ] && pdprovisioninfo=false
fi
if $pdusesites
then
sites=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid/subset/location" | xpath "//computer/location/sites/text()" 2> /dev/null)
# error checking
echo "site: $sites" >> "$pdprovisiontmp"
[ "$sites" == "" ] && pdprovisioninfo=false
fi
if $pdusedepts
then
department=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid/subset/location" | xpath "//computer/location/department/text()" 2> /dev/null)
# error checking
echo "department: $department" >> "$pdprovisiontmp"
[ "$department" == "" ] && pdprovisioninfo=false
fi
if $pdusebuildings
then
building=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid/subset/location" | xpath "//computer/location/building/text()" 2> /dev/null)
# error checkingx
echo "building: $building" >> "$pdprovisiontmp"
[ "$building" == "" ] && pdprovisioninfo=false
fi
if $pdprovisioninfo
then
return 0
else
return 1
fi
}
promptAndSetComputerName()
{
# this computer must existing in the JSS... as we've been enrolled!
computername=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/udid/$udid/subset/general" | xpath "//computer/general/name/text()" 2> /dev/null)
if $pdsetcomputername
then
secho "current computername is $computername"
validcomputername=false
until $validcomputername
do
choice=$( "$cdialogbin" inputbox --title "Computer Name" --informative-text "Please confirm this Mac's computername" --text $computername --icon-file "$lockscreenicon" --string-output --float --button1 "Confirm and Set" )
newcomputername=$( echo $choice | awk '{ print $4 }' )
if [ "$newcomputername" == "" ]
then
"$cdialogbin" msgbox --title "Alert" --informative-text "The computer name cannot be blank" --icon-file "$lockscreenicon" --float --timeout 90 --button1 "Oops"
continue
else
# lookup jss to ensure computername isn't in use
udidlookup=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/computers/name/$(echo "$newcomputername" | sed -e 's/ /\+/g')/subset/general" | xpath "//computer/general/mac_address/text()" 2> /dev/null | sed 's/:/./g' | tr '[:upper:]' '[:lower:]')
if [ "$udidlookup" == "" ] || [ "$udidlookup" == "$udid" ] # no entry, or our entry - ok to go
then
computername="$newcomputername"
validcomputername=true
else
# another computer with this name exists in the JSS
"$cdialogbin" msgbox --title "Alert" --icon-file "$lockscreenicon" --informative-text "A Mac named $newcomputername already exists in the JSS." --float --timeout 90 --button1 "Oops"
fi
fi
done
# set the computername with scutil
secho "setting computername to $computername"
#scutil --set ComputerName "$computername"
#scutil --set LocalHostName "$computername"
#scutil --set HostName "$computername"
$jamf_binary -setComputerName -name "$computername"
fi
}
promptProvisionInfo()
{
promptAndSetComputerName
if checkAndReadProvisionInfo
then
secho "prompting user to change provision info..."
provisiondetails=$(cat "$pdprovisiontmp")
changeprovisioninfoprompt=$( "$cdialogbin" msgbox --title "Mac Provisioning Information" --float --icon-file "$lockscreenlogo" --text "This Mac has the following provisioning information:" --informative-text "$provisiondetails" --button1 "Continue" --button2 "Change" --string-output )
if [[ "$changeprovisioninfoprompt" == "Continue" ]];
then
deployready="true"
return 0
fi
else
secho "provisioning information incomplete..."
skipprompt=$( "$cdialogbin" msgbox --title "Alert" --float --icon-file "$lockscreenlogo" --informative-text "This Mac has incomplete provisioning information" --string-output --timeout 90 --button1 "Configure" --button2 "Skip" )
if [[ "$skipprompt" == "Skip" ]];
then
deployready="true"
return 0
fi
fi
if [[ $pdusesites = "true" ]];
then
#read sites from jss
siteschoicearray=$(curl $curlopts -H "Accept: application/xml" -s -u ${apiuser}:${apipass} --request GET ${jssurl}JSSResource/sites | xpath //sites 2> /dev/null | sed -e 's/<name>//g' | sed -e $'s/<\/name>/\\\n/g' | tr '\n' ',')
siteschoicearray=$( echo $siteschoicearray | sed 's/..$//' )
OIFS=$IFS; IFS=','; SITES_ARRAY=( $siteschoicearray ); IFS=$OIFS
# pop up choices dialog box. strip button report as we only want the building name.
sitesvalue=$( "$cdialogbin" dropdown --icon-file "$lockscreenlogo" --float --width 500 --height 140 --title "Site" --text "Please Choose:" --items "${SITES_ARRAY[@]}" --string-output --button1 "Ok" )
sitesvalue=$( echo $sitesvalue | cut -d " " -f2- )
secho "Site value: $sitesvalue"
fi
if [[ $pdusebuildea = "true" ]];
then
#read patchoobuilds
ea=$( echo "$pdbuildea" | sed -e 's/ /\+/g' )
patchoobuildchoicearray=$( curl $curlopts -H "Accept: application/xml" -s -u ${apiuser}:${apipass} --request GET ${jssurl}JSSResource/computerextensionattributes/name/$ea )
patchoobuildchoicearray=$( echo $patchoobuildchoicearray | xpath //computer_extension_attribute/*/popup_choices/* 2> /dev/null )
patchoobuildchoicearray=$( echo $patchoobuildchoicearray | sed -e 's/<choice>//g' | sed -e $'s/<\/choice>/\\\n/g' | tr '\n' ',' )
patchoobuildchoicearray=$( echo $patchoobuildchoicearray | sed 's/..$//' )
OIFS=$IFS; IFS=','; BUILD_ARRAY=( $patchoobuildchoicearray ); IFS=$OIFS
# pop up choices dialog box. strip button report as we only want the department name.
patchoobuildvalue=$( "$cdialogbin" dropdown --icon-file "$lockscreenlogo" --float --title "Deployment EA" --text "Please Choose:" --items "${BUILD_ARRAY[@]}" --string-output --button1 "Ok" )
patchoobuildvalue=$( echo $patchoobuildvalue | cut -d " " -f2- )
secho "Build value: $patchoobuildvalue"
fi
if [[ $pdusedepts = "true" ]];
then
#read dept choices
deptchoicearray=$(curl $curlopts -H "Accept: application/xml" -s -u ${apiuser}:${apipass} --request GET ${jssurl}JSSResource/departments | xpath //departments/department/name 2> /dev/null | sed -e 's/<name>//g' | sed -e $'s/<\/name>/\\\n/g' | tr '\n' ',')
deptchoicearray=$( echo $deptchoicearray | sed 's/..$//' )
OIFS=$IFS; IFS=','; DEPT_ARRAY=( $deptchoicearray ); IFS=$OIFS
# pop up choices dialog box. strip button report as we only want the department name.
deptvalue=$( "$cdialogbin" dropdown --icon-file "$lockscreenlogo" --float --height 140 --title "Department" --text "Please Choose:" --items "${DEPT_ARRAY[@]}" --string-output --button1 "Ok" )
deptvalue=$( echo $deptvalue | cut -d " " -f2- )
secho "Department value: $(echo "$deptvalue")"
fi
if [[ $pdusebuildings = "true" ]];
then
#read building choices
buildingchoicearray=$(curl $curlopts -H "Accept: application/xml" -s -u ${apiuser}:${apipass} --request GET ${jssurl}JSSResource/buildings | xpath //buildings/building/name 2> /dev/null | sed -e 's/<name>//g' | sed -e $'s/<\/name>/\\\n/g' | tr '\n' ',')
buildingchoicearray=$( echo $buildingchoicearray | sed 's/..$//' )
OIFS=$IFS; IFS=','; BUILDING_ARRAY=( $buildingchoicearray ); IFS=$OIFS
# pop up choices dialog box. strip button report as we only want the building name.
buildingvalue=$( "$cdialogbin" dropdown --icon-file "$lockscreenlogo" --float --height 140 --title "Building" --text "Please Choose:" --items "${BUILDING_ARRAY[@]}" --string-output --button1 "Ok" )
buildingvalue=$( echo $buildingvalue | cut -d " " -f2- )
secho "Building value: $(echo "$buildingvalue" )"
fi
while true
do
if [ "$pdapiadminname" == "" ]
then
entry=$( "$cdialogbin" inputbox --title "Username" --icon-file "$lockscreenlogo" --float --informative-text "Please enter your username:" --string-output --button1 "Ok" )
tmpapiadminuser=$( echo $entry | awk '{ print $2 }' )
else
tmpapiadminuser="$pdapiadminname"
fi
if [ "$pdapiadminpass" == "" ]
then
entry=$( "$cdialogbin" inputbox --title "Password" --icon-file "$lockscreenlogo" --float --informative-text "Please enter your password:" ‑‑no‑show --string-output --button1 "Ok" )
tmpapiadminpass=$( echo $entry | awk '{ print $2 }' )
else
tmpapiadminpass="$pdapiadminpass"
fi
# put the xml to api
retryauth=false
if $pdusebuildea
then
putresult=$(curl $curlopts "${jssurl}JSSResource/computers/udid/$udid" --user "$tmpapiadminuser:$tmpapiadminpass" --silent -H "Content-Type: text/xml" -X PUT -d "<computer><extension_attributes><extension_attribute><name>$pdbuildea</name><value>$patchoobuildvalue</value></extension_attribute></extension_attributes></computer>" | grep "requires user authentication")
[ "$putresult" != "" ] && retryauth=true
fi
if $pdusesites
then
putresult=$(curl $curlopts "${jssurl}JSSResource/computers/udid/$udid" --user "$tmpapiadminuser:$tmpapiadminpass" --silent -H "Content-Type: text/xml" -X PUT -d "<computer><general><site><name>$sitesvalue</name></site></general></computer>" | grep "requires user authentication")
[ "$putresult" != "" ] && retryauth=true
fi
if $pdusedepts
then
putresult=$(curl $curlopts "${jssurl}JSSResource/computers/udid/$udid" --user "$tmpapiadminuser:$tmpapiadminpass" --silent -H "Content-Type: text/xml" -X PUT -d "<computer><location><department>$deptvalue</department></location></computer>" | grep "requires user authentication")
[ "$putresult" != "" ] && retryauth=true
fi
if $pdusebuildings
then
putresult=$(curl $curlopts "${jssurl}JSSResource/computers/udid/$udid" --user "$tmpapiadminuser:$tmpapiadminpass" --silent -H "Content-Type: text/xml" -X PUT -d "<computer><location><building>$buildingvalue</building></location></computer>" | grep "requires user authentication")
[ "$putresult" != "" ] && retryauth=true
fi
if $retryauth
then
entry=$( "$cdialogbin" msgbox --width 400 --height 140 --title "Alert" --float --informative-text "The admin username or password was incorrect" --string-output --icon hazard --float --timeout 90 --button1 "Ok" --button2 "Skip" )
tryagain=$( echo $entry | awk '{ print $2 }' )
[ "$tryagain" == "Ok" ] && continue # loop again
fi
# if we are here, all good to go
deployready=true
break
done
}
deploySetup()
{
# run on enrollment complete, setups up deploy process
secho "setting up patchoo deploy .."
touch "$pddeployreceipt"
deployready=false
"$cdialogbin" msgbox --width 400 --height 140 --title "Deployment" --float --informative-text "$msgpatchoodeploywelcome" --string-output --float --timeout 5 --button1 "Ok"
until $deployready
do
if $pdpromptprovisioninfo
then
# prompt the console user and update the jss
promptProvisionInfo
else
# we don't prompt, this mac will go into the holding pattern on reboot if it doesn't have provision info
deployready=true
fi
done
bootstrapSetup # setup bootstrap bits
secho "patchoo deploy is ready"
if [ "$(checkConsoleStatus)" == "userloggedin" ] # if a user is logged in, prompt and restart... otherwise we'll sort that via a launchd or other method
then
"$cdialogbin" msgbox --width 400 --height 140 --title "Provisioning" --float --informative-text "Ready to provison. This Mac will restart in 2 minutes" --string-output --float --timeout 120 --button1 "Restart"
#logoutUser
#sleep 10 # not pretty
reboot &
fi
}
deployHandler()
{
# start patchooDeploy, read provision info and loop until we have it
secho "starting deployment..."
until checkAndReadProvisionInfo
do
# if we don't have provisioning info, check flag
if [ ! -f "${pddeployreceipt}.holdingpattern" ]
then
# write receipt, and recon so mac lands in holding patt group and admin gets notification
touch "${pddeployreceipt}.holdingpattern"
jamfRecon
fi
secho "$msgbootstapdeployholdingpattern"
sleep 60 # waiting 60 secs and try again
done
#remove the flag
[ -f "${pddeployreceipt}.holdingpattern" ] && rm "${pddeployreceipt}.holdingpattern"
secho "provision information complete, starting deployment ..."
sleep 3
# run recurring trigger before we start deploy, in case we have stuff we need to do on that - once per computer etc.
secho "firing recurring checkin trigger ..."
$jamf_binary policy
secho "firing deploy trigger ..."
$jamf_binary policy -trigger "deploy"
if $pdusebuildea
then
secho "firing deploy-${patchoobuild} trigger ..."
$jamf_binary policy -trigger "deploy-${patchoobuild}" # calling our build specific trigger eg. deploy-management, deploy-studio
fi
installsavail=$(defaults read "$prefs" InstallsAvail 2> /dev/null) # are installations cached by patchoo polcies, during our deploy?
if [ "$installsavail" == "Yes" ]
then
installSoftware
fi
# deploy finished, rebooting to start update
secho "deployment process has finished, restarting to start update process ..."
rm "$pddeployreceipt"
touch "${pddeployreceipt}.done"
sleep 10
reboot &
}
deployGroup()
{
secho "running deploygroup items..."
while [[ $# > 0 ]]
do
triggerorpolicy="$1"
shift
if [ "$triggerorpolicy" != "" ]
then
secho "looking up $triggerorpolicy against jss..."
policyid=$(curl $curlopts -H "Accept: application/xml" -s -u "$apiuser:$apipass" "${jssurl}JSSResource/policies/name/$(echo "$triggerorpolicy" | sed -e 's/ /\+/g')" -X GET | xpath //policy/general/id 2> /dev/null | sed -e 's/<id>//;s/<\/id>//')
if [ "$policyid" != "" ]
then
secho "jamf calling policy id $policyid"
$jamf_binary policy -id "$policyid"
else
# if there's no id, lets call a trigger
secho "jamf firing trigger $triggerorpolicy"
$jamf_binary policy -trigger "$(echo "$triggerorpolicy" | sed -e 's/ /\_/g')"
fi
fi
done
}
bootstrap()
{
spawnScript
# this starts the bootstrap deploy and update process.
if [ -f "$pddeployreceipt" ] && [ ! -f "${pddeployreceipt}.done" ]
then
deployHandler
else
updateHandler
fi
}
bootstrapSetup()
{
# write out a launchagent to call bootstrap helper
bootstrappagentplist='<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.github.patchoo-bootstrap</string>
<key>RunAtLoad</key>
<true/>
<key>LimitLoadToSessionType</key>
<string>LoginWindow</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Scripts/patchoo.sh</string>
<string>''</string>
<string>''</string>
<string>''</string>
<string>--bootstraphelper</string>
</array>
</dict>
</plist>'
echo "$bootstrappagentplist" > "$bootstrapagent"
# set permissions for agent
chown root:wheel "$bootstrapagent"
chmod 644 "$bootstrapagent"
# copy the script to local drive
cp "$0" /Library/Scripts/patchoo.sh
chown root:wheel /Library/Scripts/patchoo.sh
chmod 700 /Library/Scripts/patchoo.sh
# unset any loginwindow autologin
defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser ""
secho "bootstrap setup done, you need to restart"
}
bootstrapHelper()
{
# patchoo bootstrap helper
#
# lock immediately
"$jamfhelper" -windowType fs -description "$msgbootstrap" -icon "$lockscreenlogo" &
# the local helper will handle caffination and enforcing management in case jss not reachable immediately
# caffeinate this mac (? 10.7 ?)
caffeinate -d -i -m -u &
caffeinatepid=$!
secho "waiting for jss.."
until jamf checkJSSConnection
do
sleep 2
done
killall jamfHelper
# trigger the bootstrap policy
$jamf_binary policy -trigger bootstrap &
# these messages will be ignored in the jamf.log, the previous entry will be displayed at the lockscreen
ignoremessages=("The management framework will be enforced" "Checking for policies triggered by")
while [ -f "$bootstrapagent" ] # whilst the agent exists, we are in bootstrap mode
do
tailvalue=1
newmessage=""
while [ "$newmessage" == "" ]
do
newmessage="$(tail -n${tailvalue} /var/log/jamf.log | head -n1 | cut -d' ' -f 4- | sed -e $'s/: /\\\n\\\n/g')"
for ignoremessage in ${ignoremessages[@]}
do
if [ "$(echo "$newmessage" | grep "$ignoremessage")" != "" ]
then
# we've found an ignore message, bounce up an entry.
newmessage=""
(( tailvalue ++ ))
fi
done
done
if [ "$message" != "$newmessage" ] # if the message has updated, change login screen
then
message="$newmessage"
displaymsg="$msgbootstrap
$message"
killall jamfHelper
"$jamfhelper" -windowType fs -description "$displaymsg" -icon "$lockscreenlogo" &
fi
sleep 3 # check for new message every 3 seconds
done
# uncaffeninate
kill "$caffeinatepid"
}
jamfRecon()
{
secho "jamf is running a recon ..."
if [ "$1" == "--feedback" ]
then
( $jamf_binary recon ) | "$cdialogbin" progressbar --icon sync --float --indeterminate --title "Casper Recon" --text "Updating computer inventory..." --icon-height "$iconsize" --icon-width "$iconsize" --width "500" --height "114"
else
$jamf_binary recon
fi
# if there is flag, remove it
[ -f "$datafolder/.patchoo-recon-required" ] && rm "$datafolder/.patchoo-recon-required"
secho "recon finished"
}
cleanUp()
{
rm -R "$patchootmp"
[ -f "$jssgroupfile" ] && rm "$jssgroupfile" # cached group membership
[ "$spawned" == "--spawned" ] && rm "$0" #if we are spawned, eat ourself.
IFS=$OLDIFS
}
###########
echo "$name $version $mode - $(date "+%a %b %d %H:%M:%S")"
# parse modes
case $mode in
"--cache" )
# run after caching package in policy to add metadata.
cachePkg
;;
"--checkasu" )
# run periodically on update trigger
checkASU
;;
"--promptinstall" )
# install mode prompts user to install, called post cache.
promptInstall
;;
"--promptinstallss" )
# prompt from selfservice
promptInstallSS
;;
"--checkupdatess" )
# trigger update from selfservice
checkUpdatesSS
;;
"--remind" )
# run on every120
remindInstall
;;
"--startup" )
#run on startup, recon if we've just installed updates that required a reboot
startup
;;
"--logout" )
# this is triggered by the logout hook, we do out installs on logout
processLogout
;;
"--patchoostart" )
# this starts the patchoo update process, triggered by -trigger patchoo
patchooStart
;;
"--bootstrapsetup" )
bootstrapSetup
# setups up the bootstrap launchagent, copies script and turns off autologin
;;
"--bootstraphelper" )
# used internally by launchagent for loginwindow session, drives the loginwindow messages
bootstrapHelper
;;
"--bootstrap" )
# called by the launch agent, drives the bootstrap process (deploy and updates)
bootstrap
;;
"--deploysetup" )
# setups up deployment, run on enrollment complete
deploySetup
;;
"--deploygroup" )
# group deploy policies together - paramters are executed - pass either policies or triggers
deployGroup "$5" "$6" "$7" "$8" "$9" "${10}" "${11}"
;;
*)
secho "malfunction. :/ - i don't know how to $mode"
;;
esac
# tidy up any leftovers
cleanUp
exit 0
You can’t perform that action at this time.