Covering the login screen with a message

rtrouton
Release Candidate Programs Tester

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

20 REPLIES 20

andrewseago
Contributor

You should be able to do that with the jamfHelper

andrewseago
Contributor

You should be able to do that with the jamfHelper

justinS
New Contributor
New Contributor

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.

golbiga
Contributor III
Contributor III

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

golbiga
Contributor III
Contributor III

I know that this wont display a message, but it will definitely lock the login window from being accessed.

rtrouton
Release Candidate Programs Tester

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

golbiga
Contributor III
Contributor III

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.

tlarkin
Honored Contributor

You could also cache the policy then install from cache at reboot

andrewseago
Contributor
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"

andrewseago
Contributor

-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.

andrewseago
Contributor
/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

stevewood
Honored Contributor II
Honored Contributor II

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.

andrewseago
Contributor

Well damn. What if you logged into a dummy user like the "Install Package at Reboot" option does?

justinS
New Contributor
New Contributor

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

tlarkin
Honored Contributor

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.

stevewood
Honored Contributor II
Honored Contributor II

Brilliant! Justin's method worked like a charm on one of my test machines.

justinS
New Contributor
New Contributor

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".

stevewood
Honored Contributor II
Honored Contributor II

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.

justinS
New Contributor
New Contributor

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:

  • I do 99% of my scripting in Ruby. I find it much more eloquent and understandable, especially when working with more involved scripts. I am not a Ruby guru, but I do know enough to know that true Rubyists may find my scripts distasteful (ex. while I understand the reasoning behind using variable names like 'this_is_a_variable', I really hate typing underscores all of the time and prefer the look of 'thisIsAVariable').
  • I make no promises that the scripts here will actually work flawlessly as I have modified them to try to reduce the complexity. My production scripts have all of the methods in the "Methods" sections separated out into classes in separate files so that they can be loaded and used by various scripts rather than repeated in the code of the scripts as I have done here. Also, my production scripts load a class that uses the Log4r ruby gem (which must be installed on all the Macs) to log output to both the console (which gets stored as the log in the JSS) at an "info" level and to a log file on the Mac at a "debug" level. The extra complexity of these things means a great deal more prep work to get things working. I am hoping the scripts that I provide here are useful as they are, but I have not actually tested them.

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...

GabeShack
Valued Contributor III

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

Gabe Shackney
Princeton Public Schools