Posted on 11-14-2011 01:42 PM
I'm working on a 10.7 update process using InstallLion.pkg. Following the update, I'd like to install a number of packages after the machine has been updated to 10.7.x.
My main concern is that I don't want anyone to try to log in while the packages are installing, as I'll be doing a reboot once the packages are installed. Does anyone have a solution on 10.7 for blocking access to the login window and displaying a message like "Updates in Progress"?
Thanks,
Rich
Posted on 11-14-2011 01:55 PM
You should be able to do that with the jamfHelper
Posted on 11-14-2011 01:55 PM
You should be able to do that with the jamfHelper
Posted on 11-14-2011 01:59 PM
I have not tried this with Mac OS X 10.7 yet, but with 10.6, jamfHelper (/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper) has been a great way to achieve this goal.
Type "/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -help" in the Terminal to get usage information.
The trick, however, is to use a LaunchAgent to either execute the call to jamfHelper directly or a script that calls jamfHelper, as I have been unable to get a script executing from a policy of any kind to display over the top of the login window. I believe this is a security feature of Mac OS X that will not allow untrusted apps/scripts to run over the login window. You will then need to delete the LaunchAgent before restarting or have logic in the script to keep it from covering the login window at every login.
As a side note, installing a real or "dummy" package as part of your configuration which has the "this package must be installed to the boot volume at imaging time" checkbox checked in Casper Admin will force a full-screen window to cover everything during the execution of the "first run" items in your configuration, if that is all you are looking for.
Posted on 11-14-2011 02:57 PM
Rich, I used the customizeInstallESD script included with the InstallLionPkg, and added my firstboot.pkg to the lion installer. Just make sure to unload com.apple.loginwindow at the beginning of your script (I know you do this already). Then you can install whatever apps you want via policy and there is no way for anyone to login until you reload the loginwindow.
Allen
Posted on 11-14-2011 03:10 PM
I know that this wont display a message, but it will definitely lock the login window from being accessed.
Posted on 11-14-2011 05:22 PM
It looks like the following command should do what I'm looking for:
sudo /Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType fs -title "Updating" -description "This Mac is being updated. Do not interrupt or power off." -icon /System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns
Posted on 11-14-2011 06:31 PM
Does that work at the loginwindow? Or do you have to be logged in? If it works at the loginwindow that is definitely something new.
Posted on 11-14-2011 06:33 PM
You could also cache the policy then install from cache at reboot
Posted on 11-14-2011 07:04 PM
JAMF Helper Help Page
Usage: jamfHelper -windowType [-windowPostion] [-title] [-heading] [-description] [-icon] [-button1] [-button2] [-defualtButton] [-cancelButton] [-showDelayOptions] [-alignDescription] [-alignHeading] [-alignCountdown] [-timeout] [-countdown] [-iconSize] [-lockHUD] [-startLaunchd] [-fullScreenIcon] [-kill]
-windowType [hud | utility | fs]
hud: creates an Apple "Heads Up Display" style window
utility: creates an Apple "Utility" style window
fs: creates a full screen window the restricts all user input
WARNING: Remote access must be used to unlock machines in this mode
-windowPosition [ul | ll | ur | lr]
Positions window in the upper right, upper left, lower right or lower left of the user's screen
If no input is given, the window defaults to the center of the screen
-title "string"
Sets the window's title to the specified string
-heading "string"
Sets the heading of the window to the specified string
-description "string"
Sets the main contents of the window to the specified string
-icon path
Sets the windows image filed to the image located at the specified path
-button1 "string"
Creates a button with the specified label
-button2 "string"
Creates a second button with the specified label
-defaultButton [1 | 2]
Sets the defualt button of the window to the specified button. The Default Button will respond to "return"
-cancelButton [1 | 2]
Sets the cancel button of the window to the specified button. The Cancel Button will respond to "escape"
-showDelayOptions "int, int, int,..."
Enables the "Delay Options Mode". The window will display a dropdown with the values passed through the string
-alignDescription [right | left | center | justified | natural]
Aligns the description to the specified alignment
-alignHeading [right | left | center | justified | natural]
Aligns the heading to the specified alignment
-alignCountdown [right | left | center | justified | natural]
Aligns the countdown to the specified alignment
-timeout int
Causes the window to timeout after the specified amount of seconds
Note: The timeout will cause the the defualt button, button 1 or button 2 to be selected (in that order)
-countdown
Displays a string notifying the user when the window will time out
-iconSize pixels
Changes the image frame to the specified pixel size
-lockHUD
Removes the ability to exit the HUD by selecting the close button
-startlaunchd
Starts the JAMF Helper as a launchd process
-kill
Kills the JAMF Helper when it has been started with launchd
-fullScreenIcon
Scales the "icon" to the full size of the window
Note: Only available in full screen mode
Return Values: The JAMF Helper will print the following return values to stdout...
0 - Button 1 was clicked
1 - The Jamf Helper was unable to launch
2 - Button 2 was clicked
3 - Process was started as a launchd task
XX1 - Button 1 was clicked with a value of XX seconds selected in the drop-down
XX2 - Button 2 was clicked with a value of XX seconds selected in the drop-down
239 - The exit button was clicked
240 - The "ProductVersion" in sw_vers did not return 10.5.X, 10.6.X or 10.7.X
243 - The window timed-out with no buttons on the screen
250 - Bad "-windowType"
254 - Cancel button was select with delay option present
255 - No "-windowType"
Posted on 11-14-2011 07:09 PM
-windowType [hud | utility | fs]
hud: creates an Apple "Heads Up Display" style window
utility: creates an Apple "Utility" style window
fs: creates a full screen window the restricts all user input
WARNING: Remote access must be used to unlock machines in this mode
fs sounds like what you want but you need to build in killall jamfHelper into your script between status info.
Posted on 11-14-2011 07:50 PM
/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType fs -title "Insert windowtitle" -description "insert body text here" -icon "path to icns" &
This is an example of a dialog with a basic Full Screen Message
if [ -d /Library/Application Support/JAMF/bin/jamfHelper.app ]; then
userDecision=`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType utility -title "Insert windowtitle" -description "insert body text here" -button2 "Accept" -button1 "Deny" -icon "path to icns" &`
else
echo "Could not find jamfHelper.app"
exit 0
fi
This is an example of a dialog with a simple 2 button user response
[script/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType hud -title "Insert windowtitle" -description "insert body text here" -icon "path to icns" &[/script]
This is an example of a dialog with a Growl like interface
Posted on 11-14-2011 07:57 PM
The jamfHelper binary will not work over the login window. OS X will only allow trusted apps to run over the login window. I've tried this method and it does not work.
Unless you can use a launch agent to call jamfHelper on restart, I don't see any other method than what Allen mentioned, unloading login window and then reloading it.
Posted on 11-14-2011 09:23 PM
Well damn. What if you logged into a dummy user like the "Install Package at Reboot" option does?
Posted on 11-15-2011 05:51 AM
I have not tested this with 10.7 nor have I used this particular method in a policy, but hopefully it is helpful to you. Used with Mac OS X 10.6.8, if the following file is in the specified directory with the correct permissions, it will display your desired message over the top of the login window as soon as the Mac loads the loginwindow (i.e. even if a user logs out after being logged in). While testing, you will want to have an SSH connection to the Mac or plan to send UNIX commands to the Mac using ARD, because the only way I have found to remove the message (and get back the ability to login) is to delete the file.
File: /Library/LaunchAgents/org.orgname.donotinterrupt.plist
Permissions: rw-r--r-- root:wheel
Contents: (sorry, not sure how to get the indentation to post correctly)
<?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>org.orgname.donotinterrupt</string>
<key>RunAtLoad</key>
<true/>
<key>LimitLoadToSessionType</key>
<string>LoginWindow</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper</string>
<string>-windowType</string>
<string>fs</string>
<string>-title</string>
<string>"Updating"</string>
<string>-description</string>
<string>"This Mac is being updated. Do not interrupt or power off."</string>
<string>-icon</string>
<string>"/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns"</string>
</array>
</dict>
</plist>
Use the following commands to disable the message from a remote SSH connection, ARD send UNIX command or in a policy that runs after all the updates are installed:
# sudo killall -m jamfHelper
# sudo rm /Library/LaunchAgents/org.orgname.donotinterrupt.plist
# sudo killall loginwindow
Posted on 11-15-2011 07:20 AM
This is what I would do, look at possibly using iHook? I think it can run policy/scripts at the login window and you can customize how it looks. However, what I do is I cache to my users then install from cache.
That is why when I presented an intro to bash I did not even bring up the jamfhelper because I mainly do not use it for this exact reason.
Posted on 11-15-2011 10:53 AM
Brilliant! Justin's method worked like a charm on one of my test machines.
Posted on 11-15-2011 12:28 PM
Glad to hear it!
I've actually been using this method myself, except instead of executing jamfHelper directly, my launch agent is executing a separate script to loop through as many restarts as necessary to get all Apple Software Updates installed and then triggers a custom trigger to run all of the policies assigned to the manual trigger "casperupdates".
Posted on 11-15-2011 12:31 PM
And I just tested on a 10.7 machine and it worked as expected there too.
Justin I'd be interested in how you are looping through to run all software updates.
Posted on 11-15-2011 09:07 PM
Updated the post using the newly implemented editing ability and correct posting of scripts with formatting intact. Woohoo!
Caveats
I will attempt to share here what I am doing with a few caveats:
The Basic Idea
So, the basic idea is that I have a launchagent that runs at every startup and calls an "InstallUpdates" script. This script checks a couple of values saved on the Mac and either puts up a full-screen message and installs updates or not depending on the values. If the value of "AppleUpdatesStatus" is "install" then the script will check for Apple Software updates, install any available Apple Software Updates, restart after all the updates have been installed and then the launchagent will kick off the script again at startup for another round. When there are no updates left to install, the value is changed and if the value of "CasperUpdatesStatus" is "install" then the script will change the value and then execute a manual trigger of the "casperupdates" trigger. The script will restart if either type of updates were installed.
With this in place, I can set these values with scripts in the configuration that gets installed to make sure the updates get installed on the first startup after imaging, I can have a Self Service policy available to users bring the Mac up-to-date on demand, I can have a policy that runs at certain times (weekends) to initiate the update process or I can execute commands from the terminal or ARD to set the values and then restart.
I also have an extension attribute set up to read the "AppleUpdateStatus" value and record the value this was left at after updating. The value "firmware" indicates firmware or EFI updates are left. The value "up-to-date" indicates all updates were installed.
The LaunchAgent
File: /Library/LaunchAgents/org.orgname.installupdates.plist
Permissions: rw-r--r-- root:wheel
Note: Needs to be packaged to get it installed on the Mac as part of the configuration.
<?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>Disabled</key>
<false/>
<key>Label</key>
<string>org.orgname.installupdates</string>
<key>LimitLoadToSessionType</key>
<string>LoginWindow</string>
<key>Program</key>
<string>/Library/Application Support/OrgName/Resources/bin/InstallUpdates.rb</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Application Support/OrgName/Resources/bin/InstallUpdates.rb</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
The scripts included in the configuration (separately, so that a "custom" configuration can be selected and either or both can be removed as desired)
Note: Scripts should be set to run "At Reboot"
File: SetToInstallAppleSoftwareUpdates.sh
#! /usr/bin/ruby
#
###################################################################################################
#
# Author: Justin Sako (justin.sako at gmail.com)
# Date: 2011-11-15
#
###################################################################################################
#
# ABOUT THIS SCRIPT
#
# Background/Goal:
# Need a method to install software updates at startup while the login window is obscured
# from the user to prevent login.
#
###################################################################################################
# Constants
SCRIPTNAME = "InstallUpdates"
SCRIPTVERSION = "1.0.1"
SCRIPTCHANGES = { # (oldest to newest)
"1.0.0" => "First version. [ 2011-11-xx ]",
"1.0.1" => "Modified for posting on JAMF Nation. [ 2011-11-15 ]"
}
DEFAULT_ACTION = "install"
SU_PREFS_DOMAIN = "/Library/Preferences/org.leanderisd.softwareupdates"
SU_LASTCHECK_FILE_PATH = "/Library/Preferences/org.leanderisd.softwareupdateresults.txt"
CASPER_UPDATE_MESSAGE = "Installing Third Party Software Updates.
" +
"Please do not interrupt this process by putting the Mac to sleep, turning " +
"off the power or disconnecting the network."
SYNOPSIS = "
#{SCRIPTNAME}.rb
Parameters: (* provided by Casper Suite)
* mountPoint mount point of the target drive
* computerName name of the computer
* loginUsername name of user currently logged in to the GUI (login or logout only)
action install (default) | help
"
DESCRIPTION = "
This script will install Apple Software Updates if the AppleUpdateStatus is set to 'install', set the
status to 'installing' and then restart. While the status is 'installing', it will continue to install all
available updates. If no updates are available or if the list of updates exactly matches the list
from the previous check (usually when all the updates remaining are firmware/EFI updates), the status
is set to 'up-to-date' or 'firmware' respectively and the script continues on.
Installs third party updates from the Casper Suite if the CasperUpdateStatus is set to 'install', sets
the status to 'up-to-date' and then restarts.
Actions:
install = performs install process detailed above
help = outputs this information
"
###################################################################################################
# DO NOT CHANGE ANYTHING BELOW THIS LINE!
###################################################################################################
###################################################################################################
# Variable Defaults / Installer Package Parameters
action = DEFAULT_ACTION
###################################################################################################
# Casper Suite Parameters
mountPoint = ARGV[0] if !ARGV[0].nil? && ARGV[0] != ""
computerName = ARGV[1] if !ARGV[1].nil? && ARGV[1] != ""
userName = ARGV[2] if !ARGV[2].nil? && ARGV[2] != ""
action = ARGV[3] if !ARGV[3].nil? && ARGV[3] != ""
###################################################################################################
# Methods
# Check to see if the JSS is available
# Returns:
# true | false - true if JSS is available, false otherwise
def jssAvailable?( )
result = false
10.times do
if `/usr/sbin/jamf checkJSSConnection`.match( /JSS is available/m ) then
result = true
break
else
sleep(3)
end
end
return result
end
# Read the key/value pair from the preference domain
# Parameters:
# key - key name to read from preferences
# Returns:
# string - value as a string or empty string if no value could be read
def readpref( key )
result = `/usr/bin/defaults read #{SU_PREFS_DOMAIN} "#{key.to_s}"`
result.match( /does not exist$/i ) ? ( "" ) : ( "#{result.chomp}" )
end
# Write the key/value pair to the preference domain
# Parameters:
# key - key name to write to preferences
# value - value to write to preferences
def writepref( key, value )
`/usr/bin/defaults write #{SU_PREFS_DOMAIN} "#{key.to_s}" "#{value.to_s}"`
end
# Present a full-screen window presenting a message to the user
# Options:
# :description - text to display in the window
# :kill - 'true' to remove any existing full-screen window ('false' is default)
def fullScreenMessage( description, optionParams = {} )
options = {
:kill => false
}.merge optionParams
`/usr/bin/killall -m jamfHelper` if options[ :kill ]
`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType fs -description "#{description}"`
end
# Checks for available Apple software updates on the Mac
# Parameters: (none)
def checkAppleSoftwareUpdates( )
# Load results of previous software update check if not starting a new cycle
previousResults = ""
if readpref( "AppleUpdateStatus" ) == "install" then
`/bin/rm -f "#{SU_LASTCHECK_FILE_PATH}"`
elsif File.exist?( "#{SU_LASTCHECK_FILE_PATH}" ) then
begin
previousResultsFile = File.new( SU_LASTCHECK_FILE_PATH )
previousResults = previousResultsFile.read
ensure
previousResultsFile.close
end
end
# Check for currently available software updates
x = Thread.new { fullScreenMessage( "Checking for Apple Software Updates...
", :kill => true ) }
checkResults = `/usr/sbin/softwareupdate -l`.chomp
# Strip out lines beginning with a date from the results
comparisonResults = ""
checkResults.collect do |e|
if !( e =~ /^(d{4}-d{2}-d{2})/ ) then
comparisonResults += e
end
end
# Write the software update status
if comparisonResults =~ (/No new software/m) then
writepref( "AppleUpdateStatus", "up-to-date" )
elsif comparisonResults == previousResults then
writepref( "AppleUpdateStatus", "firmware" )
elsif comparisonResults =~ (/Software Update found the following/m) then
writepref( "AppleUpdateStatus", "available" )
else
writepref( "AppleUpdateStatus", "error" )
end
# Write software update check results to file
writepref( "LastAppleUpdateCheck", "#{Time.new}" )
begin
suCheckResultsFile = File.new( SU_LASTCHECK_FILE_PATH, "w" )
suCheckResultsFile.write( "#{comparisonResults}" )
ensure
suCheckResultsFile.close
end
end
# Installs all available Apple software updates on the Mac
# Parameters: (none)
def installAppleSoftwareUpdates( )
if readpref( "AppleUpdateStatus" ) == "available" then
# Load results of previous software update check
if File.exist?( "#{SU_LASTCHECK_FILE_PATH}" ) then
begin
suCheckResultsFile = File.new( SU_LASTCHECK_FILE_PATH )
suCheckResults = suCheckResultsFile.read
# Install updates one at a time, updating status message
updates = suCheckResults.match( /Software Update found.*:s*
(.*)/mi )
if !updates.nil? then
writepref( "AppleUpdateStatus", "installing" )
updates[1].split( /^s**s+/m ).each do |update|
if details = update.match( /^(.+?)
s*(.+?),s+(d+K)s+([.+?])*$/m ) then
installDescription = details[1]
friendlyDescription = details[2]
updateMessage = "Installing #{friendlyDescription}...
" +
"Please do not interrupt this process by putting the Mac to sleep, turning " +
"off the power or disconnecting the network."
x = Thread.new { fullScreenMessage( "#{updateMessage}", :kill => true ) }
begin
`softwareupdate --install "#{installDescription}"`
rescue
puts "Error installing #{friendlyDescription} - #{installDescription}"
end
end
end
else
puts "Error parsing contents of #{SU_LASTCHECK_FILE_PATH}"
end
rescue
puts "Error reading file #{SU_LASTCHECK_FILE_PATH}"
ensure
suCheckResultsFile.close
end
end
end
end
###################################################################################################
#
# Main
#
###################################################################################################
restart = false
exitCode = 0
case action
when /^(help)$/i # Case insensitive match with "help"
helpText = "#{SYNOPSIS}#{DESCRIPTION}
Version Changes:
"
SCRIPTCHANGES.sort.reverse.each { |item|
helpText += " #{item[0]} - #{item[1]}
"
}
puts helpText
exit 0
when /^(install)$/i # Case insensitive match with "install"
# Verify that the network connection is working and the JSS is available
if jssAvailable? then
# Install Apple software updates
if readpref( "AppleUpdateStatus" ) =~ /install/ then # Match 'install' or 'installing'
restart = true
checkAppleSoftwareUpdates
installAppleSoftwareUpdates
end
if readpref( "AppleUpdateStatus" ) != "installing" then
# Install third party updates from the Casper Suite
if readpref( "CasperUpdateStatus" ) == "install" then
restart = true
x = Thread.new { fullScreenMessage( "#{CASPER_UPDATE_MESSAGE}", :kill => true ) }
writepref( "LastCasperUpdate", "#{Time.new}" )
writepref( "CasperUpdateStatus", "up-to-date" )
`/usr/sbin/jamf policy -trigger "casperupdates"`
end
end
else
puts "Error: The JSS could not be contacted--no updates were installed."
exitCode = 1
end
else
puts "Error: Invalid action (#{action}) specified."
exitCode = 1
end
puts "Restarting..." if restart
`/sbin/shutdown -r now > /dev/null` if restart
exit exitCode
The Self Service policy script
File: SoftwareUpdates.rb
Note: This file gets loaded into Casper Admin and used by a Self Service policy (or you can use it in another policy, too--I have a policy that executes on the Desktop Macs on the weekends to automatically update these machines each week) to start the process.
#! /usr/bin/ruby
#
###################################################################################################
#
# Author: Justin Sako (justin.sako at gmail.com)
# Date: 2011-11-15
#
###################################################################################################
#
# ABOUT THIS SCRIPT
#
# Background/Goal:
# Need a method to trigger software updates to be installed, either on demand by the
# user, on demand by an administrator (locally or remotely) or on a timetable.
#
# Casper Suite Info Description:
# Sets values on the Mac to trigger software updates to be installed at restart.
#
###################################################################################################
# Constants
SCRIPTNAME = "SoftwareUpdates"
SCRIPTVERSION = "1.0.1"
SCRIPTCHANGES = { # (oldest to newest)
"1.0.0" => "First version. [ 2011-11-xx ]",
"1.0.1" => "Modified for posting on JAMF Nation. [ 2011-11-15 ]"
}
DEFAULT_ACTION = "ask"
DEFAULT_TYPE = "both"
SYNOPSIS = "
#{SCRIPTNAME}.rb
Parameters: (* provided by Casper Suite)
* mountPoint mount point of the target drive
* computerName name of the computer
* loginUsername name of user currently logged in to the GUI (login or logout only)
action ask (default) | set | restart | help
updateType both (default) | apple | casper
"
DESCRIPTION = "
This script will set values on the Mac to trigger software updates to be installed at
restart, with an optional restart to kick off the process.
Actions:
ask = confirm with the user that they really want to restart, confirm which updates
(apple/casper) updates they wish to install, set the values to trigger the installs
at restart and then restart the Mac
set = set the values to trigger the installs at restart, but will not restart the Mac
restart = set the values to trigger the installs at restart and restarts the Mac
help = outputs this information
Update Types:
apple = Apple Software updates from the defined Apple Software Update Server
casper = Any policies defined to run when doing casper updates--updates to software
versions, new software, cleanup scripts, etc.
both = both Apple Software updates and Casper updates
"
SU_PREFS_DOMAIN = "/Library/Preferences/org.leanderisd.softwareupdates"
USER_HEADING_1 = "Install Updates on this Mac?"
USER_QUESTION_1 = "Do you wish to RESTART this Mac and install software updates?
" +
"The Mac should be plugged into power source and an ethernet cable before " +
"initiating the update process.
" +
"The process may involve the Mac restarting several times and could take " +
"a considerable amount of time. Please save your work before continuing."
USER_HEADING_2 = "Apple Software Updates?"
USER_QUESTION_2 = "Do you wish to install Apple Software updates?
" +
"Any available updates for Apple software on this Mac will be installed, " +
"including Mac OS, Safari, iTunes, iLife applications, iWorks applications, etc."
USER_HEADING_3 = "Third Party Updates?"
USER_QUESTION_3 = "Do you wish to install third party Software updates?
" +
"Any available updates for third party software on this Mac will be installed, " +
"including MS Office, Firefox, Google Earth, Silverlight, etc."
USER_HEADING_4 = "Restart and Install Updates Now?"
USER_QUESTION_4 = "Are you sure you want to RESTART this Mac and install software updates?
" +
"Please make sure the Mac is plugged into power source and an ethernet cable before " +
"clicking the RESTART button."
###################################################################################################
# DO NOT CHANGE ANYTHING BELOW THIS LINE!
###################################################################################################
###################################################################################################
# Variable Defaults / Installer Package Parameters
action = DEFAULT_ACTION
updateType = DEFAULT_TYPE
###################################################################################################
# Casper Suite Parameters
mountPoint = ARGV[0] if !ARGV[0].nil? && ARGV[0] != ""
computerName = ARGV[1] if !ARGV[1].nil? && ARGV[1] != ""
userName = ARGV[2] if !ARGV[2].nil? && ARGV[2] != ""
action = ARGV[3] if !ARGV[3].nil? && ARGV[3] != ""
updateType = ARGV[4] if !ARGV[4].nil? && ARGV[4] != ""
###################################################################################################
# Methods
# Read the key/value pair from the preference domain
# Parameters:
# key - key name to read from preferences
# Returns:
# string - value as a string or empty string if no value could be read
def readpref( key )
result = `/usr/bin/defaults read #{SU_PREFS_DOMAIN} "#{key.to_s}"`
result.match( /does not exist$/i ) ? ( "" ) : ( "#{result.chomp}" )
end
# Write the key/value pair to the preference domain
# Parameters:
# key - key name to write to preferences
# value - value to write to preferences
def writepref( key, value )
`/usr/bin/defaults write #{SU_PREFS_DOMAIN} "#{key.to_s}" "#{value.to_s}"`
end
# Present a dialog window asking the user to click a button
# Options:
# :title - text to display in the window title bar
# :heading - heading to display above description in the window
# :description - text to display in the window
# :button1 - name of the right button (optional)
# :button2 - name of the left button (optional)
# :defaultButton - integer number of the button to set as default
# :cancelButton - integer number of the button which signifies cancelling
# :windowStyle - style of window to display (optional) - [ hud | utility ]
# Returns:
# true | false - true if the default button was clicked, false otherwise
def showDialog( optionParams = {} )
options = {
:title => "",
:description => ""
}.merge optionParams
headingOption = options[ :heading ].nil? ? ( "" ) : ( "-heading "#{options[ :heading ]}" " )
buttonString1 = options[ :button1 ].nil? ? ( "" ) : ( " -button1 \"#{options[ :button1 ]}\"" )
buttonString2 = options[ :button2 ].nil? ? ( "" ) : ( " -button2 \"#{options[ :button2 ]}\"" )
defaultString = options[ :defaultButton ].nil? ? ( "" ) : ( " -defaultButton #{options[ :defaultButton ].to_i}" )
cancelString = options[ :cancelButton ].nil? ? ( "" ) : ( " -cancelButton #{options[ :cancelButton ].to_i}" )
windowStyle = options[ :windowStyle ].nil? ? ( "hud" ) : ( "#{options[ :windowStyle ]}" )
result = `/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType #{windowStyle} -title "#{options[ :title ]}" #{headingOption} -description "#{options[ :description ]}" #{buttonString1}#{buttonString2}#{defaultString}#{cancelString}`.to_i
return result == 0 ? ( true ) : ( false )
end
# Present a full-screen window presenting a message to the user
# Options:
# :description - text to display in the window
# :kill - 'true' to remove any existing full-screen window ('false' is default)
def fullScreenMessage( description, optionParams = {} )
options = {
:kill => false
}.merge optionParams
`/usr/bin/killall -m jamfHelper` if options[ :kill ]
`/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper -windowType fs -description "#{description}"`
end
###################################################################################################
#
# Main
#
###################################################################################################
exitCode = 0
restart = false
case action
when /^(help)$/i # Case insensitive match with "help"
helpText = "#{SYNOPSIS}#{DESCRIPTION}
Version Changes:
"
SCRIPTCHANGES.sort.reverse.each { |item|
helpText += " #{item[0]} - #{item[1]}
"
}
puts helpText
exit 0
when /^(ask)$/i # Case insensitive match with "ask"
if showDialog( :title => "Install Updates", :heading => "#{USER_HEADING_1}", :description => "#{USER_QUESTION_1}", :button1 => "Continue", :button2 => "Cancel", :defaultButton => 1, :cancelButton => 2 ) then
if showDialog( :title => "Apple Software Updates", :heading => "#{USER_HEADING_2}", :description => "#{USER_QUESTION_2}", :button1 => "Install", :button2 => "Skip", :defaultButton => 1, :cancelButton => 2 ) then
writeprefs( "AppleUpdateStatus", "install" )
end
if showDialog( :title => "Other Software Updates", :heading => "#{USER_HEADING_3}", :description => "#{USER_QUESTION_3}", :button1 => "Install", :button2 => "Skip", :defaultButton => 1, :cancelButton => 2 ) then
softwareupdatePrefs.write( "CasperUpdateStatus", "install" )
end
if showDialog( :title => "Restart and Install", :heading => "#{USER_HEADING_4}", :description => "#{USER_QUESTION_4}", :button1 => "RESTART", :button2 => "Cancel", :defaultButton => 2, :cancelButton => 2 ) then
restart = true
end
end
when /^(set)$/i, /^(restart)$/i # Case insensitive match with "set" or "restart"
if updateType =~ (/^(both)$/i) || updateType =~ (/^(apple)$/i) then
writepref( "AppleUpdateStatus", "install" )
end
if updateType =~ (/^(both)$/i) || updateType =~ (/^(casper)$/i) then
writepref( "CasperUpdateStatus", "install" )
end
if action =~ ( /^(restart)$/i ) then
restart = true
end
else
puts "Error: Invalid action (#{action}) specified."
exitCode = 1
end
puts "Restarting..." if restart
if restart then
sleep( 1 )
`/sbin/shutdown -r now > /dev/null`
end
exit exitCode
Extension Attributes
Name: Last Apple Update Status
#! /usr/bin/ruby
CONFIG_PREF_NAME = "AppleUpdateStatus"
value = `/usr/bin/defaults read /Library/Preferences/org.leanderisd.softwareupdates #{CONFIG_PREF_NAME}`
if value.nil? || value == "" then
puts "<result>Unknown</result>"
else
puts "<result>#{value}</result>"
end
Hopefully I haven't missed anything significant and the scripts are relatively understandable even to those without Ruby experience. Let me know if I need to correct or clarify anything...
Posted on 11-13-2014 06:10 AM
Thanks for this all!
I have finally cobbled together a series of commands from the above for doing an upgrade from self service that then calls the jamf agent over the login window once complete, then runs a policy (run at startup once per computer) based on pkg's installed by casper smart group (based on the launch agent I made). Getting the exact results I was looking for to make this as seamless as possible for the end user!
Thanks again!
Gabe Shackney
Princeton Public Schools