├── LICENSE.md ├── README.md ├── Update_Core_Apps.sh ├── create_ARD_computer_list.sh ├── create_SelfService_Plug-in.sh ├── depreciated ├── installLatestFlashPlayer-v1.sh ├── install_Latest_AdobeReader.sh ├── install_Latest_GoogleChrome-SelfService.sh ├── repair_permissions.sh └── template ├── download_jss_scripts.sh ├── install_select_SS_plug-ins.sh ├── offer2AddIcon-v4.sh ├── post_restart_recon_control.sh ├── reboot_scheduler.sh └── selectable_SoftwareUpdate.sh /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mike Morales 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JamfProScripts 2 | ================== 3 | 4 | A collection of scripts I have worked on to be used with Jamf Pro (formerly 'Casper Suite'), and in some cases, which can be used with other Mac management tools. 5 | 6 | ### Current scripts 7 | [create_ARD_computer_list.sh](#create_ard_computer_listsh) 8 | [reboot_scheduler.sh](#reboot_schedulersh) 9 | [create_SelfService_Plug-in.sh](#create_selfservice_plug-insh) 10 | [install_select_SS_plug-ins.sh](#install_select_ss_plug-inssh) *(Companion script for create_SelfService_Plug-in.sh)* 11 | [offer2AddIcon-v4.sh](#offer2addicon-v4sh) 12 | [post_restart_recon_control.sh](#post_restart_recon_controlsh) 13 | [selectable-SoftwareUpdate.sh](#selectable-softwareupdatesh) 14 | [download_jss_scripts.sh](#download_jss_scriptssh) 15 | 16 | [Update_Core_Apps.sh] - deprecated 17 | [install_Latest_GoogleChrome-SelfService.sh] - deprecated 18 | [repair_permissions.sh] - deprecated 19 | 20 | #### **create_ARD_computer_list.sh**
21 | **create_ARD_computer_list.sh** was designed to assist with converting a Casper Suite Smart or Static Computer group into an Apple Remote Desktop computer list. 22 | The script will present an Applescript dialog with a listing of all computer groups from your JSS to select from. Your selection will be accessed using the JSS API, pulled down into an xml file, then converted into an ARD computer list plist file for import into Apple Remote Desktop. 23 | The API account used with this script must have the following read access at a minimum to function: 24 | - Computers 25 | - Smart Computer Groups 26 | - Static Computer Groups 27 | 28 | No "Create", "Update" or "Delete" access needs to be given to the API account to use this. It only reads these objects. 29 | 30 | Special note: Because Smart and Static Computer groups don't contain the last reported IP address for computers in them, the script must loop over a list of all JSS computer IDs from the group chosen to get each Mac's IP address for the plist file. Because of this, the script can take several minutes to complete, even with modest sized computer groups. Its not recommended to use this on very large computer groups, such as one that has 1000 or more members in it. 31 | 32 | ##### **Basic usage** 33 | 1. Edit the required items in the script for API Username, API Password and JSS URL. 34 | 2. Save the script and ensure it is executable: `chmod +x /path/to/create_ARD_computer_list.sh` 35 | 3. Run the script in Terminal or by other means and follow the instructions. 36 | 37 | Feel free to report any issues. 38 |
39 | 40 | #### **reboot_scheduler.sh**
41 | **reboot_scheduler.sh** was designed to be used in instances where system updates have been installed silently on a Mac that require a reboot of the Mac. 42 | Instead of simply rebooting the Mac immediately, or only allowing a single option for reboot (for ex. "Your Mac will reboot in 5 minutes") which could interrupt a user while they are in the middle of a presentation or some other important business, the script allows you to send up options for the user to schedule the reboot at a later time, or optionally reboot soon. 43 | 44 | ##### Requirements: 45 | - The latest beta version of cocoaDialog (uses radio button and standard msgbox dialog styles) 46 | 47 | ##### Synopsis: 48 | The script works in two modes: 49 | 50 | 1. If no value (integer) in minutes is passed to the script in Parameter 4 when its run, it will send up a dialog with cocoaDialog with pre-defined reboot options that the user can choose from. For example, you may give the user the option of rebooting "2 hours from now" "30 minutes from now" or "5 minutes from now" 51 | 2. If a value (integer) in minutes is passed to the script in Parameter 4, it will instead auto schedule the reboot accordingly in the future exactly the number of minutes that was passed in the parameter. 52 | * In either case, the schedule is created dynamically with a LaunchDaemon that uses the user selected value (when no pre-defined minutes value is passed), or with the pre-defined minutes value, and also creates a companion script, both of which are created at the time the script runs. 53 | * The script is then called by the LaunchDaemon at the appointed time and presents a final 5 minute countdown when the Mac is going to reboot. This gives the user a final grace period to close out any open applications and save unsaved work before reboot time occurs. 54 | * If the script is ever run in any way prior to the StartCalendarInterval schedule in the LaunchDaemon, it checks to see if the scheduled reboot time has arrived or has recently passed. If it has not, the script will log this in the companion rdlog.log file and exit silently. This prevents any unwanted premature reboots from occurring if the script gets run accidentally. If the scheduled reboot time has arrived it displays the final 5 minute countdown to the user. 55 | * If the Mac is rebooted manually prior to the scheduled reboot time, the LaunchDaemon and script are automatically cleaned up from the Mac, thus preventing another (unnecessary) reboot from occuring. 56 | * If the dialog is quit by the user without selecting a value, the longest deferral option is automatically assigned and the LaunchDaemon / script are created and the user is notified of this. 57 | * If no user is logged in at the time the script runs, it will start an immediate reboot of the Mac to satisfy the reboot requirement without needing to schedule it for a later time. 58 | 59 | ##### Using the script: 60 | Basic usage 61 | `sudo /path/to/reboot_scheduler.sh` 62 | 63 | When the script is added to a policy, you can optionally add a value in minutes to Paramater 4 ($4) to pass to it at run time. 64 | You may also edit the values in the script for the reboot time options (currently on lines 75 thru 78) 65 | 66 | An example usage simulating a policy with a value passed to parameter 4 (using the jamf binary) 67 | `sudo jamf runScript -script reboot_scheduler.sh -path /path/to/script/ -p1 120` 68 | 69 | The above would auto schedule a reboot to occur 120 minutes from the runtime of the script, and display an alert showing the exact date and time the reboot has been scheduled to the current user. 70 | As an example, if the script is run using a value of '120' passed to Parameter 4, and the current date and time is: 71 | `June 10, 2015 11:47 PM` 72 | the script will create a LaunchDaemon with a CalendarStartInterval setting of: 73 | `June 10, 2015 01:47 PM` 74 | and display this date and time in the dialog. 75 | 76 | For more details on usage, please read through the script comments. 77 | 78 | ##### What it creates: 79 | The LaunchDaemon is created in the path: `/Library/LaunchDaemons/com.org.rd.plist` 80 | The script is created in the path: `/private/var/rtimer.sh` 81 | A log file that captures information about the process is created and updated at `/private/var/log/rdlog.log` 82 |
83 | 84 | #### **create_SelfService_Plug-in.sh**
85 | This script can be used to create Casper Suite Self Service Plug-ins on the fly, without needing to create them first within the JSS, then pulling them down with the management framework. Useful for quick testing when creating new Plug-ins, before actually setting them up within the JSS. Also useful for environments that wish to 'scope' URL Plug-ins and not auto deploy all new Plug-ins to all managed Macs. 86 | 87 | Details on the script: 88 | 89 | 1. The script must be run as root (sudo) 90 | 2. The script is interactive. It will 'guide' you on what you need to enter each step of the way. 91 | 3. The script clearly indicates what items are **Required** versus those that are **Optional**. 92 | 4. The script can accept images to use for the icon and convert them into the correct binary format 93 | 5. The script will create SS URL plug-ins with unique IDs that start in the 1000+ range. This is done so (hopefully) none of the ones you create with the script will conflict with any you created in your JSS. 94 | *Note: the JSS will start with ID 1 and increment up, even if you delete any plug-ins later (IDs don't get reused).* 95 | 6. The script will create the necessary folder hierarchy on a Casper managed Mac, and save it to the appropriate location, making it immediately available in Self Service.app. 96 | * If used on a non managed Mac, it will save the resulting plug-in plist to your Desktop 97 | 7. The script notes the resulting Plug-in's ID (same as file name) and save path, so it should be easy to locate and wrap into a package later for deployment. 98 | 99 | ##### Basic usage 100 | `sudo /path/to/create_SelfService_Plug-in.sh` 101 | Enter your administrator password, and follow the on screen instructions 102 |
103 |
104 | 105 | #### **install_select_SS_plug-ins.sh**
106 | This script is a companion script to [create_SelfService_Plug-in.sh](#create_selfservice_plug-insh), and is intended to be used from a Casper Suite Self Service policy to allow end users to select the URL plug-ins they wish to install. 107 | 108 | To effectively use this script, the following workflow is recommended: 109 | 110 | 1. Create any Self Service URL Plug-ins you wish to offer for installation. You can use any method you want for this, but it is recommended to use the [create_SelfService_Plug-in.sh](#create_selfservice_plug-insh) script to make them. 111 | 2. Create a new directory in `/private/tmp/` called **plug-ins_for_install** 112 | 3. Copy the URL Plug-ins you created in Step 1 from `/Library/Application\ Support/JAMF/Self\ Service/Plug-ins/` to the folder you created in `/private/tmp/` 113 | 4. Using Composer.app, or the packaging tool of your choice, create a deployable package (.pkg or .dmg) of the **plug-ins_for_install** directory and the plists inside it. 114 | 5. Upload the package to your Casper repository as you would any new package. 115 | 6. Create a new script in your Casper Suite JSS using the **install_select_SS_plug-ins.sh** as the code source. 116 | 7. Create a Self Service policy with the package created in Step 4 and the script created in Step 6. Set the script to run as "After". 117 |
118 | When the policy is run, the package is downloaded and installed. The installation creates the directory with the URL Plug-ins in `/private/tmp/` 119 | The script runs next and reads the information from each plug-in plist and generates the appropriate dialog for the user running the policy. 120 | The choices made by the user are captured and only the selected URL plug-ins are copied to `/Library/Application\ Support/JAMF/Self\ Service/Plug-ins/`. They become available immediately in Self Service. 121 |
122 | 123 | 124 | #### **offer2AddIcon-v4.sh**
125 | This script was originally written in 2012 and subsequently updated in 2013, then again in 2014. Its being published here due to some interest in the script expressed by others who may be looking for a similar solution. 126 | 127 | The script is designed to be used in conjunction with Casper Suite Self Service policies that install an application which can be added to a user's Dock. The script should get run in an "After" state. Instead of using the built in Dock icon functionality in a policy, which forces the icon in the user's Dock, the script will utilize cocoaDialog or jamfHelper to prompt the client if they would like the icon for the just installed application in their Dock. 128 | 129 | **Requirements for the script:** 130 | - Parameter 4 (Application Name only) 131 | - Parameter 5 (Dock icon ID from the JSS - used as a backup if dockutil is not installed) 132 | - Parameter 6 (Full application path for the icon to be added) 133 | 134 | **Optional items:** 135 | - Parameter 7 (Add "after" icon - determines if the icon should try to be added after an existing icon in the Dock) 136 | - dockutil installed on the Mac - this provides the best experience, but its possible to use just the built in Casper Suite Dock icon ID and the jamf binary 137 | 138 | **How it works** 139 | The script does some basic checking before sending up a dialog. 140 | First, it checks to see if the target application (passed to the script in Parameter 6 ($6) is present, then checks to see if the icon already exists in the current user's Dock.plist. If its already present it will simply display a dialog that the installation (presumably an update to an existing installation) has completed. 141 | If the icon *isn't* present in their Dock, it will offer to add the icon for them. The user can click **Yes** or **No**. If they click "No", the script exits and leaves their Dock as is. If they click "Yes", it will attempt to add the icon for the application to their Dock. If dockutil is installed and the path to the binary has been set up within the script, it will use that binary to add the Dock icon. If dockutil isn't installed or the script cannot find it, it falls back to using the jamf binary's update Dock functionality. This is where Parameter 6 comes in, since it will use the Dock icon ID from the JSS for this. 142 | Paramater 7 is an optional item that can be passed to the script which will use dockutil's ability to add an icon after an existing one in the Dock. For example, if using the script with a Firefox or Chrome installation policy, you may want to pass "Safari" to Parameter 7, which will tell dockutil to try to add the icon after Safari if its present in the Dock. In cases where the icon passed to Parameter 7 isn't in the Dock, it defaults to adding the icon at the end of the Dock. 143 | 144 | I developed this script because I felt that a user's Dock is a personal item. Everyone manages their Dock differently. I didn't feel comfortable with forcing an icon to a user's Dock without giving them to the option to bypass it. However, we did want to give clients the ability to automatically add the newly installed application to their Dock if they wanted it. The Casper Suite doesn't have built in functionality to prompt the user for a choice for Dock icons unless its run as a separate policy along with User Interaction options set up. 145 | 146 |
147 | 148 | #### **post_restart_recon_control.sh**
149 | 150 | Purpose: to deploy (create) a LaunchDaemon and companion local script to a Mac that can be called into action later thru the use of a control plist file 151 | 152 | #### How it works: 153 | - A local script and LaunchDaemon are both created using the included information in this script. 154 | - The scripts name is set to "postrestart.recon.sh" and is created in /private/var/ 155 | - It is partially customized at the time of creation by using the 2 variables in the script, 'yourOrg' and 'maxAttempts' 156 | - The LaunchDaemon's identifier is partially customized on creation using the 'yourOrg' variable within the script 157 | - The LaunchDaemon is not loaded after creation (it will load automatically on the next reboot) 158 | 159 | 160 | #### How the post reboot recon process works once in place: 161 | - The LaunchDaemon calls the script on initial run (only runs once), which typically means after a restart. 162 | - The script looks for a local plist file (/Library/Preferences/com.$yourOrg.postrestart.reconcontrol.plist) The plist has a simple boolean value for a post reboot recon. 163 | - If the value is set to TRUE or 1, the script will attempt to connect to the machine's Jamf Pro server and perform a recon. (See point 4 for alternate response) 164 | - It will loop up to the max times specified by the `maxAttempts` variable in the initial creation script (default is 30), pausing 1 second between each attempt while trying to connect to the Jamf Pro server. If it cannot connect in the maximum number of attempts, it exits. 165 | - If connection is successful, the recon is performed and the plist value is changed to FALSE or 0. 166 | - If the plist does not exist, the script performs the same steps as above, a recon is performed and then creating a new plist and setting the value to FALSE or 0. 167 | 168 | After initial deployment, and first use, the LaunchDaemon remains active, and the LaunchDaemon and script remain on the computer. They only spring into action if the control plist is modified through some other means, such as a command run at the completion of a Jamf Pro policy. 169 | 170 | #### Usage: 171 | The plist value can be changed with a simple shell command added to a policy to set the plist value to TRUE, which means a recon will be attempted after the next restart. 172 | 173 | Example of shell command to enable a post reboot recon (entered into the EXECUTE COMMAND field in a Jamf Pro policy): 174 | `/usr/bin/defaults write /Library/Preferences/com.acme.postrestart.reconcontrol.plist PerformRecon -bool TRUE` 175 | 176 | Note: the 'acme' portion of the plist name must be changed to the shortname of your organization entered in the 'yourOrg' value in the script 177 |
178 |
179 | 180 | 181 | #### **selectable-SoftwareUpdate.sh**
182 | 183 | - Requires the current beta release of cocoaDialog to be installed on the target Mac. 184 | - Displays a checkbox dialog with available Software Updates to be installed. 185 | - Provides feedback on installations as they run with a moving progress bar. 186 | 187 | 188 |
189 | 190 | 191 | #### **download_jss_scripts.sh**
192 | This script, which is designed to be used with a Casper Suite JSS version 9.x, can be used to download all scripts located on the JSS into a directory. Each script is downloaded with the display name as shown for it in the JSS. The script contents are cleaned after saving, to remove any web formatted characters which would prevent the script from being usable. 193 | 194 | ##### Basic usage: 195 | The script can be run directly in Terminal, or via the jamf binary. To use it you must pass an API read username and password to it to use for API commands. A third parameter that can be passed is the JSS URL. This is optional if running the script from a Mac that is currently enrolled in the target JSS. 196 | 197 | ##### Examples: 198 | `sudo jamf runScript -script download_jss_scripts.sh -path /Users/me/Desktop/ -p1 apiuser -p2 apipassword [optional] -p3 https://my.jss.org:8443` 199 | 200 | Or 201 | 202 | `sudo /Users/me/Desktop/download_jss_scripts.sh -a apiuser -p apipassword -s https://my.jss.org:8443` 203 | 204 | ##### To show a help page for the script, in Terminal: 205 | `/path/to/script/download_jss_scripts.sh -h` 206 |
207 |
208 | 209 | #### **installLatestFlashPlayer-v1.sh**
210 | (This script has been replaced by Update_Core_Apps.sh) 211 | 212 | #### **install_Latest_AdobeReader.sh** (_New_)
213 | (This script has been replaced by Update_Core_Apps.sh) 214 | 215 | 216 | #### **Update_Core_Apps.sh**
217 | **IMPORTANT: This script has been depreciated and is no longer being maintained. Most of it will not work on current versions of macOS or is no longer relevant. Use at your own risk!**
218 | The Update_Core_Apps script can be used to update many common free applications and Plug-ins. Despite the word "Update" in its name, it can also be used to install most of these applications and Plug-ins new on a target Mac. 219 | 220 | Details on the script are as follows: 221 | - Requires the current beta release of cocoaDialog to be installed on the target Mac if using Self Service mode (see below) 222 | - Can update any of the following applications and plug-ins: 223 | * **Adobe Reader** 224 | * **Adobe Flash Player** 225 | * **Cyberduck** 226 | * **Dropbox** 227 | * **Firefox** 228 | * **Firefox ESR** 229 | * **Flip Player** (free version) 230 | * **Microsoft Lync** (updates only) 231 | * **Microsoft Office 2011** (updates only) 232 | * **Oracle Java** 233 | * **Silverlight** 234 | * **VLC** 235 | 236 | - Can be used in a "silent" mode to update apps/Plug-ins silently on a target Mac, or "Self Service" mode to prompt an end user to install an update and show them both download and install progress, new version information, and success or failure notifications. 237 | - Has built in version checking against installed applications (if its installed), by comparing it to the latest release from the vendor. The version checking can handle odd version naming conventions, so that it ensures it is only "upgrading" a client, not downgrading it. 238 | - Office 2011 updates utilize a noquit.xml file to suppress the built in quit apps function of these updates. This allows these updates to install either silently, or via Self Service, without forcing the client to shut down the open applications. In both silent and Self Service modes, a dialog will alert the client of any applications that were open that should be quit and relaunched after installation. 239 | - The script accepts two Parameters passed to it from a Casper Suite policy: 240 | * Parameter 4 ($4) is mandatory, and accepts a number of different strings for the app or Plug-in to check for updates. (For a full listing of acceptable strings, see how to display the help page for the script below). 241 | * Parameter 5 ($5) is optional, and can accept any string to enable Self Service mode. 242 | * Strings are case insensitive. 243 | - The script replaces both the **installLatestFlashPlayer-v1.sh** and **install_Latest_AdobeReader.sh** scripts. 244 | 245 | ##### Basic usage 246 | 1. To test the script from Terminal on a Casper Suite managed Mac: 247 | `sudo jamf runScript -script Update_Core_Apps.sh -path /path/to/script/ -p1 "app or plugin name"` _(mandatory)_ `-p2 "any string to enable Self Service"` _(optional)_ 248 | 249 | 2. When adding the script to a Casper Suite policy, add a string to Parameter 4 and optionally Paramater 5. 250 | 251 | ##### To show a help page for the script, in Terminal: 252 | `/path/to/script/Update_Core_Apps.sh` 253 |
254 | 255 | 256 | #### **install_Latest_GoogleChrome-SelfService.sh**
257 | **IMPORTANT: This script has been depreciated and is no longer being maintained. Most of it will not work on current versions of macOS or is no longer relevant. Use at your own risk!**
258 | This script is intended to be used within Self Service. The script will operate in one of three ways, dynamically determined based on conditions. 259 | - If the Google Chrome browser is already installed on the Mac in the standard `/Applications/` path, it will attempt to locate the Google Software Update mechanism and run it as the user to check for, and install, any updates to Chrome. A final dialog will display if the browser was updated, or if it was already up to date. 260 | - If it cannot locate the Google Software Update tools on the Mac, it will offer to download the latest release and install it. 261 | - If Google Chrome is not installed or not located in `/Applications/`, it will offer to download the latest release and install it. 262 | 263 | Progress is shown when appropriate. In all cases, the final success dialogs will display the installed or updated version of Google Chrome to the user running the policy. 264 | 265 | Requirements: 266 | - Current beta release of cocoaDialog to be installed on the target Mac 267 |
268 | 269 | 270 |
271 | #### **repair_permissions.sh**
272 | **IMPORTANT: This script has been depreciated and is no longer being maintained. Most of it will not work on current versions of macOS or is no longer relevant. Use at your own risk!**
273 | - Requires the current beta release of cocoaDialog to be installed on the target Mac. 274 | - Optionally displays a 'preamble' message to the user before running the disk permissions repair. 275 | - Optionally allows the user to 'opt out' of future preamble messages with a checkbox. 276 | - When disk permissions repair is run, accurate progress is displayed with a cocoaDialog progress bar. 277 | - At the completion of the disk permissions repair, a final textbox style dialog appears with the repair results. 278 | - If any repair problems are detected, it brings this to the attention of the user in the textbox dialog heading. 279 | - If the option is enabled within the script with a variable, and problems are detected, an email can be sent to an admin or group email address with details on the Mac that ran the policy, plus the results of the repair. Note that this function uses the standard Unix mail function. This may not always work in all environments depending on firewall restrictions. 280 | 281 | Please read the notes contained within the script for instructions on how to use the various options, and be sure to add a valid email address to it before deploying. 282 | Currently this script does not use Casper Suite script parameters. If I receive enough feedback on wanting this functionality, I will add it in. In the interim, feel free to modify the script to use passed parameters for some of the options. 283 | -------------------------------------------------------------------------------- /create_ARD_computer_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: create_ARD_computer_list.sh 4 | ## Author: Mike Morales (mm2270) 5 | ## Last Modified: 2015-Sept-04 6 | 7 | ## Special Notes: This script was designed to work with a Casper Suite server's API functions, 8 | ## to create a valid Apple Remote Desktop computer group plist file that can be 9 | ## imported into the application. 10 | ## The script allows you to choose a Smart or Static Computer Group from your JSS 11 | ## to use for the conversion process into an ARD plist. 12 | 13 | ## How to use: Edit the API_USER, API_PASS and JSS_URL variables below to match your environment's. 14 | ## Save the script, ensure it is executable, then run it from Terminal and follow the instructions. 15 | 16 | 17 | ## VARIABLES 18 | 19 | ## Set the API Username, Password and your JSS URL below (Note: leave off trailing slash in the URL) 20 | API_USER="apiuser" 21 | API_PASS="apipass" 22 | JSS_URL="https://your.jss.address.com:8443" 23 | 24 | 25 | ## START OF SCRIPT 26 | 27 | ## Get the logged in username 28 | loggedInUser=$(stat -f%Su /dev/console) 29 | 30 | ## Get all JSS computer groups 31 | GROUP_LIST=$(curl -H "Accept: text/xml" -sfku "${API_USER}:${API_PASS}" "${JSS_URL}/JSSResource/computergroups" -X GET 2>/dev/null | xmllint --format - | awk -F'>|<' '//{print $3}') 32 | 33 | if [ ! -z "$GROUP_LIST" ]; then 34 | ## Prompt for selection of group name 35 | GROUP_NAME=$(/usr/bin/osascript << EOF 36 | set list_contents to do shell script "echo \"$GROUP_LIST\"" 37 | set selected_group to paragraphs of list_contents 38 | tell application "System Events" 39 | activate 40 | choose from list selected_group with prompt "Choose a Computer Group to create an ARD list from" 41 | end tell 42 | EOF) 43 | 44 | else 45 | echo "JSS Computer Groups could not be accessed. Make certain the API Username/Password and JSS URLs entered are correct and have proper read access to computer groups" 46 | exit 1 47 | fi 48 | 49 | if [ "$GROUP_NAME" == "false" ]; then 50 | exit 0 51 | else 52 | 53 | ## html encode the group name 54 | GROUP_NAME_WEB="$(perl -MURI::Escape -e 'print uri_escape($ARGV[0]);' "$GROUP_NAME")" 55 | 56 | ## Get the JSS group ID 57 | JSS_GROUP_ID=$(curl -H "Accept: text/xml" -sfku "${API_USER}:${API_PASS}" "${JSS_URL}/JSSResource/computergroups/name/${GROUP_NAME_WEB}" -X GET | xmllint --format - | awk '//,//{print}' | awk -F'>|<' '//{print $3}') 58 | 59 | ## Pull down the entire Smart or Static Group xml file into /private/tmp using the group ID 60 | curl -H "Accept: text/xml" -sfku "${API_USER}:${API_PASS}" "${JSS_URL}/JSSResource/computergroups/id/${JSS_GROUP_ID}" -X GET | xmllint --format - > /private/tmp/JSS_GROUP_${JSS_GROUP_ID} 61 | 62 | if [ "$?" == 0 ]; then 63 | xmlpresent="yes" 64 | fi 65 | fi 66 | 67 | if [ "xmlpresent" ]; then 68 | 69 | ## Extract the group name from the xml to use as the ARD group name 70 | ARD_GROUP_NAME=$(awk '//,//{print}' /private/tmp/JSS_GROUP_${JSS_GROUP_ID} | awk -F'>|<' '//{print $3}') 71 | 72 | ## Strip the xml file down to only the computer records 73 | sed -i "" '//,/<\/criteria>/d' "/private/tmp/JSS_GROUP_${JSS_GROUP_ID}" 74 | 75 | ## Get all the JSS computer IDs from the file 76 | JSS_IDS=$(awk -F'>|<' '//{print $3}' /private/tmp/JSS_GROUP_${JSS_GROUP_ID}) 77 | 78 | ## Create the initial plist file contents 79 | echo " 80 | 81 | 82 | 83 | items 84 | " > "/private/tmp/${ARD_GROUP_NAME}.plist" 85 | 86 | 87 | function appendCompData () 88 | { 89 | 90 | echo " 91 | hardwareAddress 92 | ${MAC_ADDRESS} 93 | name 94 | ${COMP_NAME} 95 | networkAddress 96 | ${IP_ADDRESS} 97 | networkPort 98 | 3283 99 | vncPort 100 | 5900 101 | " >> "/private/tmp/${ARD_GROUP_NAME}.plist" 102 | 103 | } 104 | 105 | ## Loop over each JSS ID and get the MAC Address, Computer Name and IP Address for each 106 | ## Run the above function to create individual dict computer entries into the plist file 107 | 108 | echo "$JSS_IDS" | while read JSSID || [ -n "$JSSID" ]; do 109 | MAC_ADDRESS=$(grep -A2 "$JSSID" /private/tmp/JSS_GROUP_${JSS_GROUP_ID} | awk -F'>|<' '//{print $3}') 110 | COMP_NAME=$(grep -A2 "$JSSID" /private/tmp/JSS_GROUP_${JSS_GROUP_ID} | awk -F'>|<' '//{print $3}') 111 | IP_ADDRESS=$(curl -H "Accept: text/xml" -sfku "${API_USER}:${API_PASS}" "${JSS_URL}/JSSResource/computers/id/${JSSID}/subset/general" -X GET | xmllint --format - | awk -F'>|<' '//{print $3}') 112 | 113 | appendCompData 114 | done 115 | 116 | ## Generate a random UUID string 117 | UUID=$(python -c 'import uuid; print uuid.uuid1()' | tr '[:lower:]' '[:upper:]') 118 | 119 | ## Finalize the plist file 120 | echo " 121 | listName 122 | ${ARD_GROUP_NAME} 123 | uuid 124 | ${UUID} 125 | 126 | " >> "/private/tmp/${ARD_GROUP_NAME}.plist" 127 | 128 | ## IF the plist creation was successful, attempt to move the final plist file to the logged in user's Desktop 129 | if [ "$?" == "0" ]; then 130 | mv "/private/tmp/${ARD_GROUP_NAME}.plist" "/Users/${loggedInUser}/Desktop/${ARD_GROUP_NAME}.plist" 131 | 132 | if [ "$?" == "0" ]; then 133 | echo "ARD group plist named \"${ARD_GROUP_NAME}.plist\" was created successfully and moved to your Desktop" 134 | exit 0 135 | else 136 | echo "ARD group plist named \"${ARD_GROUP_NAME}.plist\" was created successfully. It could not be moved to your Desktop. It can be found in /tmp/" 137 | exit 0 138 | fi 139 | else 140 | echo "An error occurred. Could not create the ARD plist file." 141 | exit 1 142 | fi 143 | else 144 | echo "Failed to pull down the initial xml file. Please check the JSS group ID and API credentials" 145 | exit 1 146 | fi 147 | -------------------------------------------------------------------------------- /create_SelfService_Plug-in.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script: create_SelfService_Plug-in.sh 4 | ## Author: Mike Morales 5 | ## 6 | ## Change log: 7 | ## 2015-08-06: 8 | ## Added lines to process several user passed variables to ensure they conform to xml standards 9 | ## 2015-01-12: 10 | ## Changed to use sips to check image aspect ratio and pre-convert image (to tmp file) to 128x128 pixels to use for base64 encoding 11 | 12 | 13 | function createPlugIn () 14 | { 15 | 16 | echo "Starting Plug-in creation..." 17 | echo "Checking for existing Plug-ins..." 18 | sleep 0.5 19 | 20 | ## Process some of the passed strings to make sure they conform to xml standards 21 | URL=$(echo "$URL" | sed -e 's/"/"/g;s/&/&/g;s/>/>/g;s//>/g;s//>/g;s/ 999 {print}') 34 | 35 | ## If any are in the 1000+ range, determine the next available ID 36 | if [ ! -z "$installedPlugIns" ]; then 37 | lastID=$(ls "/Library/Application Support/JAMF/Self Service/Plug-ins" | sed 's/.plist//g' | sort -g | awk '$1 > 999 {print}' | tail -1) 38 | 39 | nextID=$((lastID+1)) 40 | else 41 | ## If none are in the 1000+ range, set the ID to 1000 42 | nextID="1000" 43 | fi 44 | else 45 | ## If there is no Plug-Ins folder, set the ID to 1000 46 | nextID="1000" 47 | 48 | echo "The Self Service Plug-ins folder doesn't exist on this Mac. Creating it now..." 49 | 50 | mkdir -p "/Library/Application Support/JAMF/Self Service/Plug-ins" 51 | chown root:admin "/Library/Application Support/JAMF/Self Service/Plug-ins" 52 | chmod -R 755 "/Library/Application Support/JAMF/Self Service/Plug-ins" 53 | 54 | pluginFolderExists="yes" 55 | destDir="/Library/Application Support/JAMF/Self Service/Plug-ins" 56 | 57 | sleep 0.5 58 | fi 59 | else 60 | ## The 'JAMF' directory does not exist on this Mac. Therefore, its an un-enrolled system 61 | echo -e "No '/Library/Application Support/JAMF' directory was found on this Mac.\nThe Plug-in will be saved to your Desktop." 62 | 63 | nextID="1000" 64 | destDir="/Users/$loggedInUser/Desktop" 65 | 66 | sleep 0.5 67 | fi 68 | 69 | ## If an image was chosen, convert it to binary data 70 | if [ ! -z "$ICON" ]; then 71 | ## Resize to 128 x 128 max height and width if the image is larger in either dimension 72 | 73 | if [[ "$imageH" || "$imageW" -gt 128 ]]; then 74 | sips -z 128 128 2>&1 >/dev/null "$ICON" -o "/private/tmp/$ICON_NAME" 2>&1 >/dev/null 75 | IMAGE_PATH="/private/tmp/$ICON_NAME" 76 | else 77 | IMAGE_PATH="$ICON" 78 | fi 79 | 80 | echo "Converting image file to binary data..." 81 | sleep 0.5 82 | 83 | imageData=$(cat "$IMAGE_PATH" | base64) 84 | fi 85 | 86 | id="$nextID" 87 | url="$URL" 88 | title="$TITLE" 89 | subtitle="$SUBTITLE" 90 | priority="$PRIORITY" 91 | openInBrowser="$OPENINBROWSER" 92 | icon="$imageData" 93 | 94 | echo "Creating $TITLE Plug-in..." 95 | sleep 0.5 96 | 97 | ## Create the Plug-in plist file 98 | echo " 99 | 100 | 101 | 102 | id 103 | $id 104 | version 105 | 1 106 | url 107 | $url 108 | title 109 | $title 110 | priority 111 | $priority 112 | subtitle 113 | $subtitle 114 | openInBrowserAutomatically 115 | <$openInBrowser/> 116 | image 117 | $imageData 118 | 119 | " > "${destDir}/${id}.plist" 120 | 121 | if [ "$?" == "0" ]; then 122 | if [ "$pluginFolderExists" == "yes" ]; then 123 | echo -e "The Plug-in has been saved to the location: ${destDir}/${id}.plist. Self Service should now show the Plug-in\n" 124 | 125 | ## Clean up the tmp image file 126 | rm "/private/tmp/$ICON_NAME" 2>/dev/null 127 | exit 0 128 | else 129 | echo -e "The Plug-in has been saved to the location: ${destDir}/${id}.plist. If necessary, you can package this plug-in plist and deploy it to other Macs.\n" 130 | ## Clean up the tmp image file 131 | rm "/private/tmp/$ICON_NAME" 2>/dev/null 132 | exit 0 133 | fi 134 | else 135 | echo "We ran into an error while creating the Plug-in. 136 | However, its possible the Plug-in was successfully saved. If its not showing up in Self Service, 137 | try running this script again to create it." 138 | 139 | ## Clean up the tmp image file 140 | rm "/private/tmp/$ICON_NAME" 2>/dev/null 141 | exit 1 142 | fi 143 | } 144 | 145 | function finalSteps () 146 | { 147 | 148 | if [ -z "$SUBTITLE" ]; then 149 | SUBTITLE_PRINT="* None chosen *" 150 | else 151 | SUBTITLE_PRINT="$SUBTITLE" 152 | fi 153 | 154 | if [ -z "$ICON" ]; then 155 | ICON_PRINT="* None chosen *" 156 | else 157 | ICON_PRINT="$ICON" 158 | fi 159 | 160 | finalText="Looks like we have everything we need. 161 | Check the settings you chose below and press Enter or Return to create the Self Service URL Plug-in: 162 | 163 | URL: $URL 164 | Title: $TITLE 165 | Description: $SUBTITLE_PRINT 166 | Priority: $PRIORITY 167 | Icon: $ICON_PRINT 168 | Open in browser: $OPENINBROWSER 169 | 170 | If everything above looks good, press 'Enter' or 'Return' to create the Plug-in. 171 | If you would like to start over, type in REDO and press Enter or Return" 172 | 173 | echo "$finalText" 174 | 175 | read PROCESS 176 | 177 | if [ -z "$PROCESS" ]; then 178 | echo -e "Processing...\n" 179 | createPlugIn 180 | 181 | elif [[ "$PROCESS" == "REDO" || "redo" ]]; then 182 | echo -e "Starting over...\n" 183 | sleep 0.5 184 | 185 | askForURL 186 | fi 187 | 188 | } 189 | 190 | 191 | function askBrowserPref () 192 | { 193 | 194 | browserPrefText="Step 6: Choose if you would like the Plug-in to open in a default browser, or load into Self Service. 195 | Type in \"yes\" for opening in an external browser, or press Enter/Return to have it open in Self Service." 196 | 197 | echo "$browserPrefText" 198 | 199 | read BROWSER 200 | 201 | shopt -s nocasematch 202 | 203 | if [[ ! -z "$BROWSER" ]] && [[ "$BROWSER" == "yes" ]]; then 204 | OPENINBROWSER="true" 205 | 206 | echo -e "Setting chosen was: \"Open in browser\" Continuing...\n" 207 | shopt -u nocasematch 208 | 209 | sleep 0.5 210 | finalSteps 211 | 212 | else 213 | OPENINBROWSER="false" 214 | 215 | echo -e "Setting chosen was: \"Open in Self Service\" Continuing...\n" 216 | 217 | sleep 0.5 218 | finalSteps 219 | fi 220 | 221 | } 222 | 223 | 224 | function askForIcon () 225 | { 226 | 227 | if [ -z "$iconText" ]; then 228 | iconText="Step 5: Drag and drop an image file from the Finder to be used for the Plug-in icon (Optional but Recommended) and press Enter/Return 229 | Preferred format is PNG at 128x128 pixels, but can also accept .GIF, .TIF or .JPG formats. 230 | Also, square images (pixel dimensions) work best, otherwise the icon will get squished disportionately within Self Service.app." 231 | fi 232 | 233 | echo "$iconText" 234 | 235 | read ICON 236 | 237 | if [ -z "$ICON" ]; then 238 | echo -e "Icon selected: None. This Self Service Plug-in will be created without an icon. Continuing...\n" 239 | askBrowserPref 240 | else 241 | ## Test to make sure we have a GIF, PNG, TIFF or JPEG file for the image 242 | imageFormat=$(sips -g format "$ICON" | awk '/format/{print $NF}') 243 | 244 | ## Get the name of just the image file 245 | ICON_NAME=$(basename "$ICON") 246 | 247 | case "$imageFormat" in 248 | png|tiff|gif|jpeg) 249 | imageH=$(sips -g pixelHeight "$ICON" | awk '{getline; print $NF}') 250 | imageW=$(sips -g pixelWidth "$ICON" | awk '{getline; print $NF}') 251 | 252 | if [[ "$imageH" -ne "$imageW" ]]; then 253 | echo "*** NOTE: This image is not in a square aspect ratio. The image may become distorted when converted ***" 254 | fi 255 | echo -e "Icon selected: $ICON_NAME. Continuing...\n" 256 | 257 | askBrowserPref ;; 258 | *) 259 | iconText="The image ${ICON_NAME} appears to be in $imageFormat format. This is not one of the accepted formats. Please use only a png, gif, tif or jpeg and try again:" 260 | 261 | askForIcon ;; 262 | esac 263 | 264 | fi 265 | 266 | } 267 | 268 | function askForPriority () 269 | { 270 | 271 | if [ -z "$priorityText" ]; then 272 | priorityText="Step 4: Enter a numeric value (1-20) for the priority of the Plug-in (Optional) and press Enter or Return 273 | Lower values mean the Plug-in will appear before others in the Self Service sidebar. 274 | Note that you can leave a default value of 5 by pressing Enter or Return:" 275 | 276 | fi 277 | 278 | echo "$priorityText" 279 | 280 | read PRIORITY 281 | 282 | if [ -z "$PRIORITY" ]; then 283 | echo -e "No value assigned. Using a default of \"5\"\n" 284 | 285 | PRIORITY="5" 286 | 287 | sleep 0.5 288 | askForIcon 289 | 290 | else 291 | ## Test to make sure we received an integer value 292 | test=$(echo "$PRIORITY / $PRIORITY" | bc) 293 | if [ "$test" == "1" ]; then 294 | if [[ "$PRIORITY" -gt 0 ]] && [[ "$PRIORITY" -lt 21 ]]; then 295 | echo -e "Value entered: \"$PRIORITY\" Continuing...\n" 296 | sleep 0.5 297 | askForIcon 298 | else 299 | priorityText="Oops! We can only accept a number value between 1-20. Please try again, or press Enter or Return to use the default value:" 300 | 301 | askForPriority 302 | fi 303 | else 304 | priorityText="Oops! We can only accept a number value between 1-20. No letters or punctuation. Please try again, or press Enter or Return to use the default value:" 305 | 306 | askForPriority 307 | fi 308 | fi 309 | 310 | } 311 | 312 | function askForSubtitle () 313 | { 314 | 315 | subTitleText="Step 3: Enter a description for the Plug-in (Optional), then Press Enter or Return 316 | (Note: If you don't want a description, simply press Enter or Return to continue:" 317 | 318 | echo "$subTitleText" 319 | 320 | read SUBTITLE 321 | 322 | if [ -z "$SUBTITLE" ]; then 323 | echo -e "No description was specified. Continuing...\n" 324 | sleep 0.5 325 | askForPriority 326 | else 327 | echo -e "Description entered: \"$SUBTITLE\" Continuing...\n" 328 | sleep 0.5 329 | askForPriority 330 | fi 331 | 332 | } 333 | function askForTitle () 334 | { 335 | 336 | if [ -z "$titleText" ]; then 337 | titleText="Step 2: Enter a title for the Plug-in (Required), then press Enter or Return:" 338 | fi 339 | 340 | echo "$titleText" 341 | 342 | read TITLE 343 | 344 | if [ -z "$TITLE" ]; then 345 | titleText="Oops! A Title is required! Enter a title for the Plug-in, then press Enter or Return:" 346 | askForTitle 347 | else 348 | echo -e "Title entered: \"$TITLE\" Continuing...\n" 349 | sleep 0.5 350 | askForSubtitle 351 | fi 352 | 353 | } 354 | 355 | 356 | function askForURL () 357 | { 358 | 359 | if [ -z "$urlText" ]; then 360 | urlText="Step 1: Enter a URL to use for the Plug-in (Required), then press Enter or Return:" 361 | fi 362 | 363 | echo "$urlText" 364 | 365 | read URL 366 | 367 | if [ -z "$URL" ]; then 368 | urlText="Oops! A URL is required! Please enter a URL and press Enter or Return:" 369 | askForURL 370 | else 371 | echo -e "URL entered was: \"$URL\" Continuing...\n" 372 | sleep 0.5 373 | askForTitle 374 | fi 375 | 376 | } 377 | 378 | 379 | function initialUsage () 380 | { 381 | 382 | startMessage="This script will guide you in generating a Self Service URL Plug-in. 383 | By default, new Self Service URL plug-ins are generated with an ID in the 1000 plus range 384 | so as to avoid any conflict with Plug-ins that may be set up on your JSS. 385 | 386 | When ready to get started, just press 'Enter' or 'Return'. Otherwise, type 'exit' and press 'Return' to exit the script." 387 | 388 | echo "$startMessage" 389 | 390 | read BEGIN 391 | 392 | if [ "$BEGIN" == "" ]; then 393 | echo -e "Starting...\n" 394 | sleep 0.5 395 | askForURL 396 | elif [ "$BEGIN" == "exit" ]; then 397 | echo "Exiting. Good-bye!" 398 | exit 0 399 | else 400 | initialUsage 401 | fi 402 | 403 | } 404 | 405 | function checkForRoot () 406 | { 407 | 408 | if [[ $EUID -ne 0 ]]; then 409 | echo -e "This script must be run as root. Please use 'sudo /path/to/script.sh' and try again\n" 410 | exit 1 411 | else 412 | initialUsage 413 | fi 414 | 415 | } 416 | 417 | checkForRoot 418 | -------------------------------------------------------------------------------- /depreciated/installLatestFlashPlayer-v1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: installLatestFlashPlayer.sh 4 | ## Script author: Mike Morales (@mm2270 on JAMFNation) email: mm2270 [at] me [dot] com 5 | ## Last Change: 2014-05-16 6 | ## 7 | ## Acknowledgements: 8 | ## This script is based on the general concept developed by Rich Trouton in his script at: 9 | ## https://github.com/rtrouton/rtrouton_scripts/blob/master/rtrouton_scripts/install_latest_adobe_flash_player/install_latest_adobe_flash_player.sh 10 | ## 11 | ## The XML URL for determining the current release Flash Player version was taken from the work 12 | ## done by the AutoPkg team in their FlashPlayer recipe. 13 | ## 14 | ## Description: 15 | ## This script will update FlashPlayer to the latest public release from Adobe by getting the 16 | ## release version information from Adobe's site, and comparing it to the installed version 17 | ## (even if its not installed), and if necessary, pull down the latest install DMG from Adobe, 18 | ## silently mounting and running the pkg install and finally, comparing the end results to 19 | ## ensure the installation succeeded. 20 | ## 21 | ## Notes: 22 | ## 1) If you prefer not to install Flash Player on a system that does not currently have any 23 | ## version installed, simply set the "installNew" flag appropriately. See description below. 24 | ## 25 | ## 2) For any Macs that may actually have a newer version of FlashPlayer installed than the public 26 | ## release, such as anyone signed up with Adobe to test beta releases, the script will skip 27 | ## installing an older version, thus avoiding downgrading the client. 28 | ## 29 | ## 3) This script makes use of the Adobe Flash Player distribution URL for downloading a 30 | ## deployable pkg installer. You must sign up with Adobe for a license to use this installation at: 31 | ## http://www.adobe.com/products/players/flash-player-distribution.html 32 | ## IMPORTANT: I am not responsible for your use of this script WITHOUT signing up for Adobe's 33 | ## distribution license. It is YOUR responsibility to make sure you are remaining legal. 34 | 35 | ## Start of script 36 | 37 | ## Set the flag for installing Flash Player "new" on systems rather than just upgrades. 38 | ## Usage: Set the flag to "yes" (case sensitive) to allow new FlashPlayer installations. 39 | ## Set the flag to "no" (case insensitive), leave it blank, or enter any other string 40 | ## besides "yes" if you would like to skip new installs 41 | ## 42 | installNew="yes" 43 | 44 | ## Function section for downloading the latest Flash Player installer and running the installation 45 | function downloadFP () 46 | { 47 | 48 | # Download latest Flash Player DMG to a file in /tmp/ 49 | echo "Downloading Flash Player DMG..." 50 | /usr/bin/curl -s "$FP_downloadURL" -o /tmp/InstallFlashPlayer.dmg 51 | 52 | ## Mount the downloaded disk image and capture the mounted volume name as a variable we can use for the next steps 53 | echo "Silently mounting Flash Player Installer disk image..." 54 | FPInstallVol=$( /usr/bin/hdiutil attach /tmp/InstallFlashPlayer.dmg -nobrowse -noverify -noautoopen 2>&1 | awk -F'[\t]' '/\/Volumes/{ print $NF }' ) 55 | 56 | echo "Silently installing Flash Player from pkg..." 57 | 58 | ## Install the FlashPlayer pkg while reading output from installer 59 | ## Check for the successful upgrade line to set the installation status 60 | installStatus=1 61 | while read line; do 62 | echo " $line" 63 | if [[ $( echo "$line" | egrep "The upgrade was successful|The install was successful" ) ]]; then 64 | installStatus=0 65 | fi 66 | done < <(/usr/sbin/installer -pkg "${FPInstallVol}/Install Adobe Flash Player.pkg" -tgt / 2>&1) 67 | 68 | ## Pause 2 seconds to allow installation to finish out 69 | sleep 2 70 | 71 | ## Now check the installation results 72 | if [[ "$installStatus" == "0" ]]; then 73 | echo "Flash Player installation was successful. Checking new version for confirmation..." 74 | 75 | FP_newVers=$( /usr/bin/defaults read /Library/Internet\ Plug-Ins/Flash\ Player.plugin/Contents/Info CFBundleShortVersionString ) 76 | 77 | if [[ "${FP_newVers}" == "${FP_releasedVers}" ]]; then 78 | echo "Confirmed current version is now ${FP_releasedVers}..." 79 | exit_status=0 80 | else 81 | echo "New version and latest version do not match. Installation may have failed..." 82 | exit_status=1 83 | fi 84 | else 85 | echo "Installation exited with an error code. Install failed..." 86 | exit_status=1 87 | fi 88 | 89 | ## Clean up (we do this regardless of the installation result so as not to leave downloads around in /tmp/) 90 | 91 | echo "Cleaning up. Force ejecting the 'Flash Player' volume..." 92 | /usr/bin/hdiutil eject -force "${FPInstallVol}" 93 | 94 | echo "Deleting downloaded disk image..." 95 | rm -rf "/tmp/InstallFlashPlayer.dmg" 96 | 97 | exit $exit_status 98 | 99 | } 100 | 101 | 102 | ## Get the current version for Flash from the Adobe website 103 | echo "Getting the current version of FlashPlayer from Adobe..." 104 | FP_releasedVers=$( curl -s http://fpdownload.macromedia.com/get/flashplayer/update/current/xml/version_en_mac_pl.xml | xpath /XML/update[1] 2>&1 | awk -F'"' '{print $2}' | sed -e '/^$/d;s/,/./g' ) 105 | 106 | echo "Current Flash Player version from Adobe's site is: ${FP_releasedVers}..." 107 | 108 | ## Extract the major version number from the long version string 109 | echo "Getting the major FlashPlayer version number..." 110 | FP_majVers=$( echo "$FP_releasedVers" | cut -d. -f1 ) 111 | 112 | ## Set the download URL 113 | echo "Setting the FlashPlayer download URL..." 114 | FP_downloadURL="http://fpdownload.macromedia.com/get/flashplayer/current/licensing/mac/install_flash_player_${FP_majVers}_osx_pkg.dmg" 115 | echo "Download URL set to ${FP_downloadURL}..." 116 | 117 | echo "Checking the installed version of Flash Player on this Mac..." 118 | ## Get the currently installed version of FlashPlayer 119 | if [[ -e "/Library/Internet Plug-Ins/Flash Player.plugin" ]]; then 120 | FP_installedVers=$( /usr/bin/defaults read "/Library/Internet Plug-Ins/Flash Player.plugin/Contents/Info" CFBundleShortVersionString ) 121 | echo "Installed Flash Player plug-in version is: ${FP_installedVers}..." 122 | else 123 | FP_installedVers="0" 124 | fi 125 | 126 | ## Here we generate two normalized version strings that can be used in an integer comparison later. 127 | ## These variables help account for some Adobe numbering oddities that would otherwise make it impossible to do a correct version comparison 128 | FP_installedNormalized=$( printf "%s%02d\n" $(echo "$FP_installedVers" | sed -e 's/00//;s/[.]//g') | cut -c 1-7 ) 129 | FP_releasedNormalized=$( printf "%s%02d\n" $(echo "$FP_releasedVers" | sed -e 's/00//;s/[.]//g') | cut -c 1-7 ) 130 | 131 | echo "Normalized installed version is: $FP_installedNormalized" 132 | echo "Normalized released version is: $FP_releasedNormalized" 133 | 134 | ## Check the Flash Player version and take appropriate next steps 135 | echo "Determining any version difference..." 136 | 137 | ## Using the normalized version strings, check to see if the installed version is somehow higher than the current public release version. 138 | ## This can happen if someone is signed up with Adobe Labs to install beta versions of FlashPlayer. We don't want to downgrade them ;) 139 | if [[ "${FP_installedNormalized}" -gt "${FP_releasedNormalized}" ]]; then 140 | echo "Installed version, ${FP_installedVers} is higher than the public version, ${FP_releasedVers}. This Mac may be running a beta release. Exiting..." 141 | exit 0 142 | fi 143 | 144 | ## If the version is "0", then Flash Player is not installed 145 | if [[ "${FP_installedVers}" == "0" ]]; then 146 | ## Check to see what the installNew flag is set to 147 | if [[ "$installNew" == "yes" ]]; then 148 | ## installNew flag is set, so download and install it 149 | echo "Flash Player is not currently installed on this Mac. Downloading and installing..." 150 | downloadFP 151 | else 152 | ## installNew flag was not set, so exit 153 | echo "Flash Player is not currently installed on this Mac, but we are instructed to skip new installs. Exiting..." 154 | exit 0 155 | fi 156 | fi 157 | 158 | 159 | if [[ "${FP_installedNormalized}" -lt "${FP_releasedNormalized}" ]]; then 160 | ## Flash Player is installed, but is not up to date. Download and install 161 | echo "Flash Player is not up to date. Downloading and installing..." 162 | downloadFP 163 | fi 164 | 165 | if [[ "${FP_installedVers}" == "${FP_releasedVers}" ]]; then 166 | ## Flash Player is installed, but matches the current release. Up to date, so exit 167 | echo "Flash Player is installed and already up to date. Exiting..." 168 | exit 0 169 | fi 170 | -------------------------------------------------------------------------------- /depreciated/install_Latest_AdobeReader.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script Name: install_Latest_AdobeReader.sh 4 | ## Author: Mike Morales 5 | ## Last Change: 2014-05-16 6 | ## Compatibility: Intel OS X (10.6.x - 10.9.x) 7 | 8 | ## IMPORTANT: This script assumes installation of the Intel version of Adobe Reader on an Intel based Mac. 9 | ## It does not do architecture checking in the script, although I may possibly include such a check in a future version. 10 | 11 | ## Set this flag to "yes" if you would like new installs of Adobe Reader on systems that do not currently have 12 | ## any version of Adobe Reader installed. If you leave it blank or set it to 'no' it will skip the installation. 13 | installNew="yes" 14 | 15 | ## Function section for downloading the latest Flash Player installer and running the installation 16 | function downloadAR () 17 | { 18 | 19 | # Download latest Adobe Reader DMG to a file in /tmp/ 20 | echo "Download URL set to: http://ardownload.adobe.com/pub/adobe/reader/mac/${ARCurrMajVers}.x/${ARCurrVersFull}/misc/${AR_DMG}" 21 | echo "Downloading Adobe Reader DMG..." 22 | 23 | ## Download the DMG using curl and the URL set 24 | curl -s -f "http://ardownload.adobe.com/pub/adobe/reader/mac/${ARCurrMajVers}.x/${ARCurrVersFull}/misc/${AR_DMG}" -o "/tmp/${AR_DMG_DL}" 25 | 26 | ## Check the exit status of the curl command 27 | if [[ "$?" != "0" ]]; then 28 | echo "Curl operation failed. Site may be blocked or unavailable right now. Exiting with code 1..." 29 | exit 1 30 | fi 31 | 32 | ## Mount the downloaded disk image and capture the mounted volume name as a variable we can use for the next steps 33 | echo "Silently mounting Adobe Reader Installer disk image..." 34 | ARInstallVol=$( /usr/bin/hdiutil attach "/tmp/${AR_DMG_DL}" -nobrowse -noverify -noautoopen 2>&1 | awk -F'[\t]' '/\/Volumes/{ print $NF }' ) 35 | 36 | ## Check the exit status of the mount operation 37 | if [[ "$?" == "0" ]]; then 38 | ## Get the pkg name from the mounted volume 39 | AR_PKG=$( ls "${ARInstallVol}" | grep ".pkg$" ) 40 | 41 | echo "Silently installing Adobe Reader from pkg..." 42 | 43 | ## Install the Adobe Reader pkg while reading output from installer 44 | ## Check for the successful upgrade line to set the installation status 45 | installStatus=1 46 | while read line; do 47 | echo " $line" 48 | if [[ $( echo "$line" | egrep "The upgrade was successful|The install was successful" ) ]]; then 49 | installStatus=0 50 | fi 51 | done < <(/usr/sbin/installer -pkg "${ARInstallVol}/${AR_PKG}" -tgt / 2>&1) 52 | 53 | ## Pause 2 seconds to allow installation to finish out 54 | sleep 2 55 | 56 | ## Now check the installation results 57 | if [[ "$installStatus" == "0" ]]; then 58 | echo "Adobe Reader installation was successful. Checking new version for confirmation..." 59 | 60 | ## Get the new version number from disk to ensure it matches the expected current version 61 | AR_newVers=$( /usr/bin/defaults read "/Applications/Adobe Reader.app/Contents/Info" CFBundleShortVersionString ) 62 | 63 | if [[ "${AR_newVers}" == "${ARCurrVersFull}" ]]; then 64 | echo "Confirmed current version is now ${ARCurrVersFull}..." 65 | exit_status=0 66 | else 67 | echo "New version and latest version do not match. Installation may have failed..." 68 | exit_status=1 69 | fi 70 | else 71 | ## If we didn't get a status 0 returned from the installation, exit with an error code 72 | echo "Installation exited with an error code. Install failed..." 73 | exit_status=1 74 | fi 75 | 76 | ## Clean up (we do this regardless of the installation result so as not to leave downloads around in /tmp/) 77 | 78 | echo "Cleaning up. Force ejecting the Adobe Reader install volume..." 79 | /usr/bin/hdiutil eject -force "${ARInstallVol}" 80 | 81 | echo "Deleting downloaded disk image..." 82 | rm -rf "/tmp/${AR_DMG_DL}" 83 | 84 | exit $exit_status 85 | 86 | else 87 | 88 | ## If mounting the disk image failed, clean up partial/broken download and exit until next run 89 | echo "Could not mount the downloaded disk image. Cleaning up and exiting with status 1..." 90 | rm -rf "/tmp/${AR_DMG_DL}" 91 | exit 1 92 | fi 93 | 94 | } 95 | 96 | 97 | ## Script starts here 98 | 99 | ## Get OS version and adjust for use with the URL string 100 | OSvers_URL=$( sw_vers -productVersion | sed 's/[.]/_/g' ) 101 | 102 | ## Set the User Agent string for use with curl 103 | userAgent="Mozilla/5.0 (Macintosh; Intel Mac OS X ${OSvers_URL}) AppleWebKit/535.6.2 (KHTML, like Gecko) Version/5.2 Safari/535.6.2" 104 | 105 | ## Get the current release version from Adobe by curling the site as a browser 106 | ARCurrVersFull=$( curl -s -A "$userAgent" http://get.adobe.com/reader/ | grep "Version" | awk -F'[(|)]' '{print $2}' ) 107 | 108 | ## Get the installed version string 109 | ARVersFull=$( defaults read /Applications/Adobe\ Reader.app/Contents/Info CFBundleShortVersionString 2> /dev/null ) 110 | 111 | ## Check to see if we got any version returned. If not, set it to "0" 112 | if [[ -z "${ARVersFull}" ]]; then 113 | ARVersFull="0" 114 | fi 115 | 116 | ## Get the first set of digits from the current version string 117 | ARCurrMajVers=$( echo "${ARCurrVersFull}" | cut -d. -f1 ) 118 | echo "The Adobe Reader major version number is: $ARCurrMajVers" 119 | 120 | ## Echo back what we pulled 121 | echo "The Adobe Reader current released version is: $ARCurrVersFull" 122 | echo "The Adobe Reader current installed version on disk is: $ARVersFull" 123 | 124 | ## Normalize the version strings for use in integer comparison 125 | ARCurrVersNormalized=$( echo "$ARCurrVersFull" | sed 's/[.]//g' ) 126 | ARVersNomalized=$( echo "$ARVersFull" | sed 's/[.]//g' ) 127 | 128 | ## Debug lines 129 | echo "${ARCurrVersNormalized}" 130 | echo "${ARVersNomalized}" 131 | 132 | ## Set the DMG name based on the available information and the file name we will curl to 133 | AR_DMG="AdbeRdrUpd${ARCurrVersNormalized}.dmg" 134 | AR_DMG_DL="AdobeReader.dmg" 135 | 136 | ## Check to see if the version was set to "0" meaning not installed and also if we set the installNew flag and take appropriate next steps 137 | echo "Determining any version difference..." 138 | 139 | ## Using the normalized version strings, check to see if the installed version is somehow higher than the current public release version. 140 | ## This can happen if someone is signed up with Adobe Labs to install beta versions of FlashPlayer. We don't want to downgrade them ;) 141 | if [[ "${ARVersNomalized}" -gt "${ARCurrVersNormalized}" ]]; then 142 | echo "Installed version, ${ARVersNomalized} is higher than the public version, ${ARCurrVersNormalized}. This Mac may be running a beta release. Exiting..." 143 | exit 0 144 | fi 145 | 146 | ## If the version is "0", then Flash Player is not installed 147 | if [[ "${ARVersFull}" == "0" ]]; then 148 | ## Check to see what the installNew flag is set to 149 | if [[ "$installNew" == "yes" ]]; then 150 | ## installNew flag is set, so download and install it 151 | echo "Adobe Reader is not currently installed on this Mac. Downloading and installing..." 152 | downloadAR 153 | else 154 | ## installNew flag was not set, so exit 155 | echo "Adobe Reader is not currently installed on this Mac, but we are instructed to skip new installs. Exiting..." 156 | exit 0 157 | fi 158 | fi 159 | 160 | if [[ "${ARVersNomalized}" -lt "${ARCurrVersNormalized}" ]]; then 161 | ## Adobe Reader is installed, but is not up to date. Download and install 162 | echo "Adobe Reader is not up to date. Downloading and installing..." 163 | downloadAR 164 | fi 165 | 166 | if [[ "${ARVersFull}" == "${ARCurrVersFull}" ]]; then 167 | ## Adobe Reader is installed, but matches the current release. Up to date, so exit 168 | echo "Adobe Reader is installed and already up to date. Exiting..." 169 | exit 0 170 | fi 171 | -------------------------------------------------------------------------------- /depreciated/install_Latest_GoogleChrome-SelfService.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: install_Latest_GoogleChrome-SelfService.sh 4 | ## Author: Mike Morales 5 | ## Last modified: 2015-01-14 6 | 7 | ## Path to cocoaDialog. Used for all dialogs and progress bars 8 | cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 9 | 10 | ## Common variable strings used throughout dialogs 11 | MsgTitle="IT - Application Installer" 12 | properName="Google Chrome" 13 | dmg_icon="/System/Library/CoreServices/DiskImageMounter.app/Contents/Resources/diskcopy-doc.icns" 14 | 15 | ## URL to the latest version of Google Chrome DMG 16 | download_url="https://dl.google.com/chrome/mac/stable/GGRM/googlechrome.dmg" 17 | 18 | ## Path to install location for Google Chrome. Also used in case of existing install 19 | appPath="/Applications/Google Chrome.app" 20 | 21 | ## Logged in user 22 | loggedInUser=$( ls -l /dev/console | awk '{print $3}' ) 23 | 24 | 25 | function checkVersion () 26 | { 27 | 28 | ## This function runs when an installed version of Chrome is present 29 | ## and checks for updates by calling the Google Software Update mechanism. 30 | 31 | ## Get the version of Google Chrome AFTER running the GoogleSoftwareUpdate command 32 | ChVersAfter=$( defaults read "${appPath}/Contents/Info" CFBundleShortVersionString ) 33 | 34 | ## Shut down the progress bar 35 | exec 10>&- 36 | rm -f /tmp/hpipe 37 | 38 | ## Check the before and after versions to determine if an update was installed 39 | if [[ "${ChVersBefore}" != "${ChVersAfter}" ]]; then 40 | echo "Google Chrome was updated from version ${ChVersBefore} to version ${ChVersAfter}" 41 | "$cdPath" msgbox --title "${MsgTitle}" --text "An update was installed" \ 42 | --informative-text "Your copy of Google Chrome was just updated to version ${ChVersAfter}, and is now up-to-date." \ 43 | --button1 " OK " --width 400 --height 150 --posY top --icon info --quiet 44 | else 45 | echo "There were no new versions of Google Chrome to update to." 46 | "$cdPath" msgbox --title "${MsgTitle}" --text "No new updates" \ 47 | --informative-text "Looks like your version of Google Chrome, ${ChVersAfter}, is the latest up-to-date version." \ 48 | --button1 " OK " --width 400 --height 150 --posY top --icon info --quiet 49 | fi 50 | 51 | exit 0 52 | 53 | } 54 | 55 | function RUNGSU () 56 | { 57 | 58 | exec 10>&- 59 | rm -f /tmp/hpipe 60 | mkfifo /tmp/hpipe 61 | sleep 0.2 62 | 63 | "$cdPath" progressbar --indeterminate --title "" --text "Please wait. Checking for updates to your Chrome browser" --width 500 --icon info --icon-height 40 --icon-width 40 --posY top < /tmp/hpipe & 64 | 65 | ## Send progress through the named pipe 66 | exec 10<> /tmp/hpipe 67 | 68 | /bin/launchctl bsexec "${loggedInPID}" sudo -iu "${loggedInUser}" "\"${GSUEXE}\" -runMode oneshot -userInitiated YES \"$@\" 2> /dev/null" 69 | 70 | ## Run the version check function after checking for updates 71 | checkVersion 72 | 73 | } 74 | 75 | 76 | function findGSU () 77 | { 78 | 79 | ## This function runs when an installed version of Google Chrome is installed and attempts to locate the 80 | ## Google Software Update framework so we can call it manually 81 | 82 | ## Get the logged in user, since we will need to check for the GoogleSoftwareUpdate.bundle in their home Library dir 83 | loggedInUser=$( ls -l /dev/console | awk '{print $3}' ) 84 | loggedInPID=$( ps -axj | awk "/^$loggedInUser/ && /Dock.app/ {print \$2;exit}" ) 85 | 86 | ## Set the base path to the GoogleSoftwareUpdateAgent 87 | GSUAGENT="Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/GoogleSoftwareUpdateAgent.app/Contents/MacOS/GoogleSoftwareUpdateAgent" 88 | 89 | ## Next, check to make sure Chrome is installed on this Mac. For now, we're only looking in Applications 90 | 91 | ## If its installed, capture the current version, before any update, into a variable 92 | ChVersBefore=$( defaults read "/Applications/Google Chrome.app/Contents/Info" CFBundleShortVersionString ) 93 | 94 | ## Find out if the GoogleSoftwareUpdate agent is located in the current user's Library folder 95 | if [[ -x "/Users/$loggedInUser/Library/${GSUAGENT}" ]]; then 96 | echo "Agent found in current user Library directory" 97 | ## Set the full path to the executable if found 98 | GSUEXE="/Users/$loggedInUser/Library/${GSUAGENT}" 99 | ## Run the update function 100 | RUNGSU 101 | else 102 | ## If not in the user's Library folder, check the main one 103 | echo "Agent not found in user Library directory" 104 | if [[ -x "/Library/${GSUAGENT}" ]]; then 105 | echo "Agent found in root Library directory" 106 | ## Set the full path to the executable if found 107 | GSUEXE="/Library/${GSUAGENT}" 108 | ## Run the update function 109 | RUNGSU 110 | else 111 | ## No agent found, so we exit 112 | "${cdPath}" msgbox --title "${MsgTitle}" --text "Google Software Update not found" \ 113 | --informative-text "Oops. It looks like we couldn't locate the Google Software Updater on your Mac. We'll need to download the latest version and install it instead." \ 114 | --button1 " OK " --width 400 --height 150 --posY top --icon info 115 | echo "No GoogleSoftwareUpdate agent was found. Alerting user and moving to dlLatest function..." 116 | dlLatest 117 | fi 118 | fi 119 | 120 | } 121 | 122 | 123 | function cleanUpAction_Success () 124 | { 125 | 126 | ## Description: This function runs on a successful installation of the app. 127 | ## It will display a successful message to the user and clean up the downloaded files and other items as necessary. 128 | 129 | headText="Installation successful" 130 | mainText="The installation of ${properName} was successful. The version installed was ${updatedVers}." 131 | 132 | ## Delete the downloaded disk image 133 | rm -Rf "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" 134 | 135 | ## If there is a renamed application bundle... 136 | if [[ -d "${appPath}_old" ]]; then 137 | ## delete it now that the new version is installed 138 | rm -Rfd "${appPath}_old" 139 | fi 140 | 141 | ## Show the successful install message to the end user 142 | "$cdPath" msgbox --title "${MsgTitle}" --text "$headText" --informative-text "$mainText" \ 143 | --button1 " OK " --width 400 --posY top --icon info --quiet 144 | 145 | ## If the app was running during installation send up a message alerting user to relaunch it. 146 | #if [[ "$appRunning" ]]; then 147 | # wasOpenMsg="${properName} was just updated to version ${currVers}. The application was running during the upgrade. Please close ${properName} and relaunch it to start using the new version." 148 | # "$cdPath" msbox --title "${MsgTitle}" --text "${properName} was updated" \ 149 | # --informative-text "$wasOpenMsg" --button1 " OK " \ 150 | # --width 400 --posY top --icon info --quiet 151 | # exit $exit_status 152 | #else 153 | # exit $exit_status 154 | #fi 155 | 156 | } 157 | 158 | 159 | function cleanUpAction_Failure () 160 | { 161 | 162 | ## Description: This function runs on a failed installation of the app or package. 163 | ## It will display a failure message to the user (if SelfService is set) and clean up the downloaded files and other items as necessary. 164 | 165 | ## Now close the progress bar 166 | exec 20>&- 167 | rm -f /tmp/hpipe 168 | 169 | if [[ "$exit_status" == "1" ]]; then 170 | mainTextFail="The installation of ${properName} has failed. Your original application was left in place. 171 | 172 | You can try running the policy again later. If you continue to encounter problems, contact the Help Desk for assistance and mention error code $exit_status" 173 | 174 | elif [[ "$exit_status" == "2" ]]; then 175 | mainTextFail="An installable package couldn't be found in the disk image. It may have been corrupted, or there was a problem with the script that needs to be corrected. 176 | 177 | You can try running the policy again later. If you continue to encounter problems, contact the Help Desk for assistance and mention error code $exit_status" 178 | 179 | elif [[ "$exit_status" == "3" ]]; then 180 | mainTextFail="The disk image could not be mounted to install the update. It may have been corrupted during the download. 181 | 182 | You can try running the policy again later. If you continue to encounter problems, contact the Help Desk for assistance and mention error code $exit_status." 183 | 184 | fi 185 | 186 | ## Delete the downloaded disk image from /Library/Application Support/ITUpdater/Downloads/ 187 | echo "Deleting downloaded disk image..." 188 | rm -f "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" 189 | 190 | ## If we previously renamed the target application, 191 | ## reset the name and make it visible again 192 | if [[ -d "${appPath}_old" ]]; then 193 | mv "${appPath}_old" "${appPath}" 194 | chflags nohidden "${appPath}" 195 | fi 196 | 197 | "$cdPath" msgbox --title "${MsgTitle}" --text "Installation failed" --informative-text "$mainTextFail" --button1 " OK " --width 400 --height 175 --posY top --icon caution 198 | 199 | exit $exit_status 200 | 201 | } 202 | 203 | 204 | function copyAPPUpdate2 () 205 | { 206 | 207 | ## Description: This function is called when the specified app is in an app bundle format. 208 | ## It gets called from copyAPPUpdate1 and determines if the application is running if the SelfService flag is set.. 209 | ## If SelfService is set and the app is running, it prompts the user to quit the app before proceeding, or allows the user to cancel the operation. 210 | 211 | ## Check to see if the application is running 212 | AppProc=$( ps axc | grep -i "${properName}" ) 213 | 214 | AppOpenText="Please quit ${properName}, then click Continue to proceed with the installation." 215 | if [[ "$AppProc" != "" ]]; then 216 | echo "0 ${properName} is running..." >&20 217 | quitAppMsg=$( "$cdPath" msgbox --title "$MsgTitle" --text "${properName} is running" \ 218 | --informative-text "$AppOpenText" --button1 " Continue " --button2 " Cancel " \ 219 | --width 400 --icon caution --posY center ) 220 | 221 | if [[ "$quitAppMsg" == "1" ]]; then 222 | copyAPPUpdate2 223 | else 224 | echo "100 Installation has been cancelled..." >&20 225 | hdiutil detach -force "${updateVolName}" 226 | sleep 1 227 | rm -f "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" 228 | exit 0 229 | fi 230 | else 231 | echo "0 Checking for existing installation..." >&20 232 | echo "Renaming any previous installation" 233 | mv "${appPath}" "${appPath}_old" 2> /dev/null 234 | chflags hidden "${appPath}_old" 2> /dev/null 235 | 236 | sleep 1 237 | 238 | echo "Copying ${updateAPPName} to /Applications/" 239 | 240 | ## Loop while copying app to Applications, calculating percentage complete. Update progress bar 241 | while read line; do 242 | if [[ $(echo "$line" | grep "^copying") ]]; then 243 | dlSize=$(du -sk "/Applications/${updateAPPName}" | awk '{print $1}' 2>/dev/null) 244 | if [[ ! -z "$dlSize" ]]; then 245 | pct=$(expr "${dlSize}" \* 100 / "${origSize}") 2>/dev/null 246 | echo "$pct ${pct}% - Please wait. Installing ${properName}..." >&20 247 | fi 248 | fi 249 | done < <(ditto -V "${updateVolName}/${updateAPPName}" "/Applications/${updateAPPName}" 2>&1) 250 | 251 | sleep 1 252 | 253 | ## If the app is not in /Applications/ then the copy failed 254 | if [[ ! -d "${appPath}" ]]; then 255 | echo "${properName} app could not be copied to the Applications folder" 256 | exit_status=1 257 | cleanUpAction_Failure 258 | fi 259 | 260 | ## Continue if successful 261 | echo "20 Fixing permissions on the application..." >&20 262 | sleep 0.5 263 | echo "Adjusting permissions on ${appPath}, and removing quarantine flag" 264 | echo "40 Fixing permissions on the application..." >&20 265 | chown -R root:admin "${appPath}" 266 | chmod -R 755 "${appPath}" 267 | if [[ $(xattr -l "${appPath}" | grep "com.apple.quarantine") ]]; then 268 | echo "Removing quarantine flag on ${appPath}" 269 | xattr -d com.apple.quarantine "${appPath}" 270 | fi 271 | sleep 0.5 272 | echo "60 Checking application..." >&20 273 | sleep 0.5 274 | echo "80 Installation complete. Please wait..." >&20 275 | sleep 0.5 276 | echo "Install done. Cleaning up..." 277 | echo "90 Cleaning up..." >&20 278 | echo "Unmounting volume..." 279 | hdiutil detach -force "${updateVolName}" 280 | sleep 0.5 281 | echo "100 Checking new version for ${prpperName}..." >&20 282 | sleep 0.4 283 | fi 284 | 285 | ## Get the new version number from disk 286 | updatedVers=$( /usr/bin/defaults read "${appPath}/Contents/Info.plist" CFBundleShortVersionString ) 287 | 288 | 289 | ## Now close the progress bar 290 | exec 20>&- 291 | rm -f /tmp/hpipe 292 | 293 | cleanUpAction_Success 294 | 295 | } 296 | 297 | 298 | function copyAPPUpdate1 () 299 | { 300 | 301 | ## Description: This function is called when the specified app is in an app bundle format. 302 | ## It first mounts the disk image, gets the application size, and then calls function copyAPPUpdate2. 303 | 304 | errNoMount="The installation of ${properName} failed. The error was: 305 | 306 | Disk image mount failed 307 | 308 | Please try running the policy again." 309 | 310 | errNoAppBundle="The installation of ${properName} failed. The error was: 311 | 312 | App bundle not found in disk image 313 | 314 | Please try running the policy again." 315 | 316 | 317 | echo "Silently mounting the ${properName} disk image..." 318 | 319 | echo "0 Accessing downloaded file..." >&20 320 | 321 | ## Mount the disk image and capture the mounted volume's name 322 | updateVolName=$( /usr/bin/hdiutil attach "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" -nobrowse -noverify -noautoopen 2>&1 | awk -F'[\t]' '/\/Volumes/{ print $NF }' ) 323 | 324 | if [ "$?" == "0" ]; then 325 | ## Get the package name in the mounted disk image 326 | updateAPPName=$( ls "$updateVolName" | grep ".app$" | grep -i "${properName}" ) 327 | 328 | if [[ ! -z "$updateAPPName" ]]; then 329 | echo "A matching app bundle was found on the mounted volume - ${updateAPPName}" 330 | echo "0 Checking application..." >&20 331 | origSize=$(du -sk "${updateVolName}/${updateAPPName}" | awk '{print $1}') 332 | 333 | echo "0 Checking active applications..." >&20 334 | copyAPPUpdate2 335 | else 336 | echo "Mounting of the disk image failed. Exit" 337 | exec 20>&- 338 | rm -f /tmp/hpipe 339 | "$cdPath" msgbox --title "${MsgTitle}" --text "Installation failed" \ 340 | --informative-text "$errNoMount" \ 341 | --button1 " OK " --icon caution --width 400 342 | exit 1 343 | fi 344 | else 345 | echo "Couldn't locate an app bundle on the mounted volume. Exiting..." 346 | exec 20>&- 347 | rm -f /tmp/hpipe 348 | "$cdPath" msgbox --title "${MsgTitle}" --text "Installation failed" \ 349 | --informative-text "$errNoAppBundle" \ 350 | --button1 " OK " --icon caution --width 400 351 | exit 1 352 | fi 353 | 354 | } 355 | 356 | 357 | function dlLatest () 358 | { 359 | 360 | 361 | errNoDwnld="The installation of ${properName} failed. The error was: 362 | 363 | Installation could not be downloaded 364 | 365 | Please try running the policy again." 366 | 367 | ## Description: This function is used to download the current, or latest version of the specified product. 368 | ## This function gets the download_url string passed to it and uses curl to pull down the update into 369 | ## the "/Library/Application Support/ITUpdater/Downloads/" directory 370 | 371 | rawSize=$(curl -sI "${download_url}" | awk '/Content-Length/{print $NF}' | tail -1 | tr -cd [:digit:]) 372 | adjSize=$(expr ${rawSize} / 1024) 373 | 374 | ## Set up progress bar elements 375 | exec 20>&- 376 | rm -f /tmp/hpipe 377 | mkfifo /tmp/hpipe 378 | sleep 0.2 379 | 380 | ## Set up the progress bar 381 | "$cdPath" progressbar --title "" --text " Please wait. Downloading the latest ${properName}..." --width 500 \ 382 | --posY top --float --icon-file "$dmg_icon" --icon-height 40 --icon-width 40 < /tmp/hpipe & 383 | 384 | ## Wait just a half sec 385 | sleep 0.5 386 | 387 | ## Send progress through the named pipe 388 | exec 20<> /tmp/hpipe 389 | 390 | ## Start the download and push the process to the background 391 | curl -sf "${download_url}" -o "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" & 392 | 393 | ## Wait a moment before beginning calculations 394 | sleep 0.5 395 | 396 | ## Loop while DMG download is taking place, calculating percentage complete. Update progress bar 397 | pct=0 398 | 399 | while [[ "$pct" -lt 100 ]]; do 400 | sleep 0.2 401 | dlSize=$(du -hk "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" | awk '{print $1}' 2>/dev/null) 402 | if [ "$dlSize" != "" ]; then 403 | pct=$(expr ${dlSize} \* 100 / ${adjSize}) 404 | echo "$pct ${pct}% - Please wait. Downloading the latest ${properName}..." >&20 405 | fi 406 | done 407 | 408 | sleep 0.7 409 | 410 | if [[ -e "/Library/Application Support/ITUpdater/Downloads/${properName}.dmg" ]]; then 411 | echo "Download of ${properName}.dmg was successful" 412 | ## Begin the copy function 413 | copyAPPUpdate1 414 | else 415 | echo "Download of ${properName}.dmg failed. Exiting..." 416 | ## Shutting down the progress bar 417 | exec 20>&- 418 | rm -f /tmp/hpipe 419 | ## Show download failure message 420 | "$cdPath" msgbox --title "${MsgTitle}" --text "Installation failed" \ 421 | --informative-text "$errNoDwnld" \ 422 | --button1 " OK " --icon caution --width 400 423 | exit 1 424 | fi 425 | 426 | } 427 | 428 | 429 | ## Start of script 430 | 431 | ## Create the ITUpdater directory if it doesn't exist 432 | if [[ ! -d "/Library/Application Support/ITUpdater/" ]]; then 433 | mkdir "/Library/Application Support/ITUpdater/" 434 | fi 435 | 436 | ## Create the Downloads directory if it doesn't exist 437 | if [[ ! -d "/Library/Application Support/ITUpdater/Downloads/" ]]; then 438 | mkdir "/Library/Application Support/ITUpdater/Downloads/" 439 | fi 440 | 441 | if [ -d "${appPath}" ]; then 442 | echo "Google Chrome is already installed in the main Applications folder" 443 | 444 | checkUpdt=$( "$cdPath" msgbox --title "${MsgTitle}" --text "Google Chrome is already installed" \ 445 | --informative-text "Chrome is already installed on this Mac, so we'll check for updates instead of re-downloading it." \ 446 | --button1 " OK " --button2 " Cancel " --cancel "button2" --icon info --width 400 --posY top --timeout 15 ) 447 | if [ "$checkUpdt" -lt "2" ]; then 448 | findGSU 449 | else 450 | echo "User cancelled checking for an updated version" 451 | exit 0 452 | fi 453 | else 454 | dlLatest 455 | fi 456 | -------------------------------------------------------------------------------- /depreciated/repair_permissions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: repair_permissions.sh 4 | ## Script author: Mike Morales 5 | ## Last change: 2015-03-04 6 | 7 | ## Path to cocoaDialog and the Disk Utility icon (used in the progress bar) 8 | cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 9 | diskUtilIcon="/Applications/Utilities/Disk Utility.app/Contents/Resources/DFA.icns" 10 | 11 | ## The next 3 variables can be set to: 12 | ## a) show a pre message to the user running the policy (showPreamble) 13 | ## b) allow the user to opt out of seeing the pre message for future runs of the policy (allowOptOut) 14 | ## c) send an email to an address or mailing list of any permissions problems that diskutil could not repair (sendMail) 15 | ## 16 | ## In addition, the emailAddress variable should be set if using option (c) to send email on errors. 17 | ## It will be ignored if the sendMail variable is not set to "yes" 18 | 19 | ## Set the "showPreamble" variable below to "yes" to show a pre message to the user explaining that 20 | ## disk permissions repair is not an exact science. 21 | ## If you would rather the script skip this message and go straight to running the repair, simply set 22 | ## the string for showPreamble to any other value or comment out the line. 23 | 24 | showPreamble="yes" 25 | 26 | ## Set the "allowOptOut" variable below to "yes" to allow the end user to opt out of any future pre message display when they run the 27 | ## policy again the next time. With the opt out option enabled by the user, the policy will go straight to the repair process when run again. 28 | 29 | allowOptOut="yes" 30 | 31 | ## Set the "sendMail" variable below to "yes" to have the script send an email to the email address listed 32 | ## under the "emailAddress" variable. Note that this only sends an email in the event the script has detected 33 | ## an issue with disk permissions that could not be repaired. Otherwise the script runs the repair and reports 34 | ## the results to the end user and exits silently. 35 | ## To prevent any emails from being sent, simply set the "sendMail" string to any other value or comment out the line. 36 | 37 | sendMail="yes" 38 | 39 | ## Set the "emailAddress" string below to a valid email or mailing list address to send emails to. Note that this 40 | ## variable works in conjunction with the above "sendMail" option. The email will be ignored if sendMail is not set to "yes" 41 | 42 | emailAddress="someone@somecompany.com" 43 | 44 | ## Set the text between the quotes below to what you would like to display to the user as a preamble message. 45 | ## The text below can be left in place, or edited to your needs. 46 | 47 | startText="Before you begin, its important to understand the following items: 48 | 49 | • Some system items may report that they needed a permissions repair repeatedly. 50 | (This is normal and NOT a cause for concern for your Mac) 51 | • This will only correct permissions in the OS and some applications. 52 | • It will NOT fix permission problems for your account. 53 | 54 | If you would like to continue with the repair, click \"Continue\" below. Otherwise, click \"Cancel\" to exit." 55 | 56 | ## Set the plist name to use for storing the user level setting related to the allowOptOut option above 57 | ## Note that this will be ignored if allowOptOut is not set to "yes" 58 | 59 | Plist="com.mm2270.dprsetting.plist" 60 | 61 | ########################################## End of initial script variables ########################################### 62 | 63 | ## Get the Startup Volume name 64 | startupVol=$( diskutil info / | awk -F':' '/Volume Name/{print $NF}' | sed 's/^ *//' ) 65 | 66 | ## Get the logged in username 67 | loggedInUser=$( ls -l /dev/console | awk '{print $3}' ) 68 | 69 | function startRepair () 70 | { 71 | 72 | ## Set up progress bar elements 73 | exec 20>&- 74 | rm -f /tmp/hpipe 75 | mkfifo /tmp/hpipe 76 | sleep 0.2 77 | 78 | ## Set up the progress bar 79 | "$cdPath" progressbar --title "" --text " Please wait. Starting repair permissions for \"${startupVol}\"" --width 550 --posY top --float \ 80 | --icon-file "$diskUtilIcon" --icon-height 40 --icon-width 40 < /tmp/hpipe & 81 | 82 | ## Wait just a half sec 83 | sleep 0.5 84 | 85 | ## Send progress through the named pipe 86 | exec 20<> /tmp/hpipe 87 | 88 | /usr/sbin/diskutil repairPermissions -plist "/Volumes/${startupVol}" > /private/tmp/repairdiskprogress.plist & 89 | 90 | pct=0 91 | until [[ "$pct" -eq 100 ]]; do 92 | sleep 1 93 | if [[ $(echo "$dots" | wc -c | sed 's/^ *//') -gt "7" ]]; then 94 | dots="" 95 | else 96 | dots="${dots}. " 97 | fi 98 | pct=$(awk -F'>|<' '/PercentComplete/{getline; print $3}' /private/tmp/repairdiskprogress.plist | tail -1 | cut -d. -f1) 99 | if [ ! -z "$pct" ]; then 100 | echo "$pct Please wait. Now repairing permissions for \"${startupVol}\"${dots}" >&20 101 | fi 102 | done 103 | 104 | 105 | if [[ $(awk -F'>|<' '/PercentComplete/{getline; print $3}' /private/tmp/repairdiskprogress.plist | tail -1 | cut -d. -f1) == "100" ]]; then 106 | ## Close the progress bar 107 | exec 20>&- 108 | rm -f /tmp/hpipe 109 | 110 | ## Output a status file of any permissions that were repaired 111 | awk -F'>|<' '/Status/{getline; print $3}' /private/tmp/repairdiskprogress.plist > "/private/tmp/repairdiskstatusoutput.txt" 112 | 113 | ## Check for keywords in the status output and set problem flag if any repairs could not be done 114 | while read status; do 115 | if [[ $(echo "$status" | egrep -i "could not| not |error|fail") != "" ]]; then 116 | repairStatus="problem" 117 | fi 118 | done < <(cat "/private/tmp/repairdiskstatusoutput.txt") 119 | 120 | if [[ "$repairStatus" == "problem" ]] && [[ "$sendMail" == "yes" ]]; then 121 | textMsg="Disk permissions repair completed for \"${startupVol}\". We detected some problems. A report of this repair has been emailed to your IT administrator. 122 | The information below lists the results:" 123 | 124 | elif [[ "$repairStatus" == "problem" ]] && [[ "$sendMail" != "yes" ]]; then 125 | textMsg="Disk permissions repair completed for \"${startupVol}\". We detected some problems. 126 | The information below lists the results:" 127 | 128 | else 129 | textMsg="Disk permissions repair has completed for \"${startupVol}\" 130 | The information below lists the results of the repair:" 131 | 132 | fi 133 | 134 | ## If any problems were detected and the sendMail option is enabled, send email with details 135 | 136 | if [[ "$repairStatus" == "problem" ]] && [[ "$sendMail" ]]; then 137 | 138 | ## Gather some system details for the email 139 | MacName=$( scutil --get ComputerName ) 140 | serialNo=$( ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}' ) 141 | 142 | mailDetails="Disk permissions repair was run from Self Service on ${MacName}. 143 | Problems were detected. Details are below: 144 | 145 | Mac Name: ${MacName} 146 | Serial Number: ${serialNo} 147 | User: ${loggedInUser} 148 | 149 | The repair operation reported: 150 | 151 | $(cat /private/tmp/repairdiskstatusoutput.txt)" 152 | 153 | ## Send the email with details 154 | echo "$mailDetails" | mail -s "Disk Permissions Repair problem detected" "$emailAddress" 155 | fi 156 | 157 | "$cdPath" textbox \ 158 | --title "" \ 159 | --text-from-file "/private/tmp/repairdiskstatusoutput.txt" \ 160 | --informative-text "$textMsg" \ 161 | --button1 " OK " \ 162 | --icon info \ 163 | --width 600 \ 164 | --quiet 165 | 166 | ## Clean up the files created by the repair process 167 | rm -f "/private/tmp/repairdiskprogress.plist" 168 | rm -f "/private/tmp/repairdiskstatusoutput.txt" 169 | fi 170 | 171 | } 172 | 173 | 174 | function showPreamble () 175 | { 176 | 177 | ## Get user's home directory path 178 | userHome=$( dscl . read /Users/${loggedInUser} NFSHomeDirectory | awk '{print $NF}' ) 179 | optOutSetting=$( /usr/bin/defaults read "${userHome}/Library/Preferences/${Plist}" showPreamble 2>/dev/null ) 180 | 181 | if [[ "$optOutSetting" == "1" ]]; then 182 | startRepair 183 | fi 184 | 185 | if [[ "$allowOptOut" == "yes" ]]; then 186 | 187 | userChoice=$( "$cdPath" checkbox \ 188 | --title "Disk Permissions Repair" \ 189 | --label "$(echo -e "Do you want to start the repair?\n\n$startText")" \ 190 | --items "I understand. Please skip this message next time" \ 191 | --button1 "Continue" \ 192 | --button2 "Cancel" \ 193 | --cancel "button2" \ 194 | --width 550 \ 195 | --posY top \ 196 | --icon info ) 197 | else 198 | userChoice=$( "$cdPath" msgbox \ 199 | --title "Disk Permissions Repair" \ 200 | --text "Do you want to start the repair?" \ 201 | --informative-text "$startText" \ 202 | --button1 "Continue" \ 203 | --button2 "Cancel" \ 204 | --cancel "button2" \ 205 | --width 550 \ 206 | --posY top \ 207 | --icon info ) 208 | fi 209 | 210 | buttonClicked=$( echo "$userChoice" | awk 'NR==1{print}' ) 211 | boxChecked=$( echo "$userChoice" | awk 'NR==2{print}' ) 212 | 213 | if [[ "$buttonClicked" == "1" ]]; then 214 | if [[ "$boxChecked" == "1" ]]; then 215 | /usr/bin/defaults write "${userHome}/Library/Preferences/${Plist}" showPreamble -int 1 216 | startRepair 217 | else 218 | startRepair 219 | fi 220 | else 221 | exit 0 222 | fi 223 | 224 | } 225 | 226 | if [ "$showPreamble" == "yes" ]; then 227 | showPreamble 228 | else 229 | startRepair 230 | fi 231 | -------------------------------------------------------------------------------- /depreciated/template: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /download_jss_scripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: download_jss_scripts.sh 4 | ## Author: Mike Morales (@mm2270 on JAMFNation) 5 | ## https://jamfnation.jamfsoftware.com/viewProfile.html?userID=1927 6 | ## Last change: 2018-Jan-15 7 | ## Last change description: 8 | ## Placed curl header in front of account credentials to prevent errors when downloading script contents 9 | ## Replaced xmllint with xpath string on line 156 to obtain full script contents 10 | 11 | ## Description: Script to download all JSS scripts from a 12 | ## Casper Suite version 9.x or version 10.x Jamf Pro server. For more detailed information, 13 | ## run the script in Terminal with the -h flag 14 | 15 | ## The following section contains the only variables that should be manually edited in 16 | ## the script. They can also be assigned to the script as Casper Suite parameters. 17 | ## Read the descriptions for more info. 18 | 19 | ## If you choose to hardcode API information into the script, set the API Username 20 | ## and API Password here. Note: The API account only needs 'read' privileges to 21 | ## pull JSS scripts 22 | 23 | apiUser="" ## Set the API Username here if you want it hardcoded 24 | apiPass="" ## Set the API Password here if you want it hardcoded 25 | jssURL="" ## Set the JSS URL here if you want it hardcoded 26 | 27 | ## Set the script downloads folder path here. 28 | ## Default path is within the JAMF directory in "JSS_Scripts" 29 | scriptDownloadDir="/Library/Application Support/JAMF/JSS_Scripts" 30 | 31 | ################################ DO NOT EDIT BELOW THIS LINE ################################ 32 | 33 | script=$(basename $0) 34 | directory="$(cd "$(dirname "$0")" && pwd)" 35 | 36 | ## Help / Usage function 37 | usage () 38 | { 39 | cat << EOF 40 | SYNOPSIS 41 | sudo script.sh -a "api_user" -p "api_password" -s "server" 42 | or 43 | sudo jamf runScript -script "script.sh" -path "/path/to/" -p1 "api_user" -p2 "api_password" -p3 "server" 44 | 45 | COMPATIBILITY: 46 | Casper Suite version 9.x and 10.x 47 | 48 | OPTIONS: 49 | -h Show this usage screen 50 | -a API account username 51 | -p API account password 52 | -s JSS Server address [optional] 53 | 54 | DESCRIPTION: 55 | This script can be used to download a copy of all JSS scripts 56 | located on the Casper Suite server specified in the server option. 57 | The Casper Suite server (JSS) URL is optional. If not specified at run time, 58 | the script will attempt to obtain the JSS address from the client's settings. 59 | 60 | The script can be run in two primary ways. 61 | 1. Calling the script directly in the shell 62 | 63 | Example: 64 | sudo "$0" -a "api_username" -p "api_password" -s "https://jss.server.com:8443" 65 | 66 | 2. Using the jamf binary 67 | 68 | Example: 69 | sudo jamf runScript -script "$script" -path "$directory" -p1 "api_username" -p2 "api_password" -p3 "https://jss.server.com:8443" 70 | 71 | You may also use the script directly in a JSS policy, specifying the API username, 72 | API password and (optionally) the JSS URL in parameters 4 through 6, respectively. 73 | 74 | NOTES: 75 | It is recommended to enclose the API username, API password and JSS URL in double quotes 76 | to protect the script against any special characters or spaces in the strings. 77 | 78 | EOF 79 | exit 80 | } 81 | 82 | ## Run loop to check for passed args on the command line 83 | while getopts ha:p:s: option; do 84 | case "${option}" in 85 | a) apiUser=${OPTARG};; 86 | p) apiPass=${OPTARG};; 87 | s) jssURL=${OPTARG};; 88 | h) usage;; 89 | esac 90 | done 91 | 92 | ## Check to see if the script was passed any script parameters from Casper 93 | if [[ "$apiUser" == "" ]] && [[ "$4" != "" ]]; then 94 | apiUser="$4" 95 | fi 96 | 97 | if [[ "$apiPass" == "" ]] && [[ "$5" != "" ]]; then 98 | apiPass="$5" 99 | fi 100 | 101 | if [[ "$jssURL" == "" ]] && [[ "$6" != "" ]]; then 102 | jssURL="$6" 103 | fi 104 | 105 | ## Finally, make sure we got at least an apiUser & apiPass variable, else we exit 106 | if [[ -z "$apiUser" ]] || [[ -z "$apiPass" ]]; then 107 | echo -e "API Username = $apiUser\nAPI Password = $apiPass" 108 | echo "One of the required variables was not passed to the script. Exiting..." 109 | exit 1 110 | fi 111 | 112 | ## If no server address was passed to the script, get it from the Mac's com.jamfsoftware.jamf.plist 113 | if [[ -z "$jssURL" ]]; then 114 | jssURL=$(/usr/bin/defaults read /Library/Preferences/com.jamfsoftware.jamf.plist jss_url 2> /dev/null | sed 's/\/$//') 115 | if [[ -z "$jssURL" ]]; then 116 | echo "JSS URL = $jssURL" 117 | echo "Oops! We couldn't get the JSS URL from this Mac, and none was passed to the script" 118 | exit 1 119 | else 120 | echo "JSS URL = $jssURL" 121 | fi 122 | else 123 | ## Make sure to remove any trailing / in the passed parameter for the JSS URL 124 | jssURL="${jssURL%/}" 125 | fi 126 | 127 | ## Set up the JSS Scripts API URL 128 | jssScriptsURL="${jssURL}/JSSResource/scripts" 129 | 130 | ## Run quick check on access to the JSS API 131 | curl -skfu "${apiUser}:${apiPass}" "${jssScriptsURL}" 2>&1 > /dev/null 132 | 133 | if [[ "$?" != "0" ]]; then 134 | echo "There was an error retrieving information from the JSS. 135 | Please check your API credentials and/or the JSS URL, and ensure the JSS is accessible from your location. Exiting now..." 136 | exit 1 137 | fi 138 | 139 | ## Create the script download directory if not present 140 | if [[ ! -d "$scriptDownloadDir" ]]; then 141 | mkdir "$scriptDownloadDir" 142 | fi 143 | 144 | ## Begin script download process 145 | echo "Step 1: Gathering all Script IDs from the JSS..." 146 | ## Generate a list of all Script IDs we can pull from the JSS using the API 147 | allScriptIDs=$(curl -H "Accept: text/xml" -skfu "${apiUser}:${apiPass}" "${jssScriptsURL}" | xmllint --format - | awk -F'>|<' '//{print $3}' | sort -n) 148 | 149 | ## Now read through each ID gathered and get specific information on each Script from the JSS 150 | echo "Step 2: Pulling down each Script from the JSS..." 151 | 152 | downloadCount=0 153 | while read ID; do 154 | ## Get the Script name from its JSS ID 155 | script_Name=$(curl -H "Accept: application/xml" -sku "${apiUser}:${apiPass}" "${jssScriptsURL}/id/${ID}" | xmllint --format - | awk -F'>|<' '//{print $3}') 156 | ## Get the actual script contents from the API record for the script 157 | script_Content=$(curl -H "Accept: application/xml" -sku "${apiUser}:${apiPass}" "${jssScriptsURL}/id/${ID}" | xpath '/script/script_contents/text()') 158 | script_Ext=$(echo "$script_Name" | awk -F. '{print $NF}') 159 | 160 | echo "Script name is: $script_Name" 161 | 162 | if [ "$script_Ext" == "$script_Name" ]; then 163 | ## Get the first line, which should be a shebang of some kind 164 | firstLine=$(echo "${script_Content}" | head -1) 165 | ## If it looks like the first line begins with a shebang... 166 | if [[ $(echo "$firstLine" | grep "^#\!" ) ]]; then 167 | ## ...grab the script's interpreter 168 | shellEnv=$(echo "$firstLine" | awk -F'/' '{print $NF}' | perl -p -e 'tr/\cM//d;') 169 | ## If the script's interpreter ends in sh (.sh, .bash, .ksh, csh, etc)... 170 | if [[ "$shellEnv" =~ sh$ ]]; then 171 | ## ...set the script extension to .sh 172 | script_Ext="sh" 173 | else 174 | ## Otherwise, use whatever we grabbed as the interpreter (might be .py, .pl, etc) 175 | script_Ext=$(echo "${shellEnv}" | sed 's/^M//') 176 | fi 177 | else 178 | ## We didn't see a shebang as the first line, so assume its a shell script. Set the extension to .sh 179 | script_Ext="sh" 180 | fi 181 | ## Echo the script contents into script file 182 | echo "${script_Content}" > "${scriptDownloadDir}/${script_Name}.${script_Ext}" 183 | echo "Downloaded script \"${script_Name}.${script_Ext}\"..." 184 | let downloadCount+=1 185 | else 186 | ## The script name already has an extension. Echo the script contents into script file 187 | echo "${script_Content}" > "${scriptDownloadDir}/${script_Name}" 188 | echo "Downloaded script \"${script_Name}\"..." 189 | let downloadCount+=1 190 | fi 191 | done < <(echo "${allScriptIDs}") 192 | 193 | echo "Finished downloading all scripts from the JSS" 194 | 195 | echo "Step 3: Cleaning up script file contents... 196 | Cleaning up ^M carriage returns in script contents... 197 | Setting executable flag for all script files..." 198 | 199 | ## Loop through all downloaded scripts, stripping out problem characters and adding the ea_display_name line 200 | while read downloadedScript; do 201 | ## Remove all Windows carriage returns (^M) from the script contents 202 | perl -pi -e 'tr/\cM//d;' "${scriptDownloadDir}/${downloadedScript}" 203 | URIDecodedScript=$(cat "${scriptDownloadDir}/${downloadedScript}" | perl -MHTML::Entities -pe 'decode_entities($_);') 204 | echo "$URIDecodedScript" > "${scriptDownloadDir}/${downloadedScript}" 205 | ## Make sure all the scripts have the executable flag set for them 206 | chmod +x "${scriptDownloadDir}/${downloadedScript}" 207 | done < <(ls -p "${scriptDownloadDir}" | grep -v /) 208 | 209 | 210 | echo -e "\nFinal results: 211 | A total of ${downloadCount} scripts were downloaded. 212 | 213 | You should check the output for each script to verify that the results are what you expect." 214 | 215 | echo -e "\nStep 4: Done!" 216 | 217 | exit 218 | -------------------------------------------------------------------------------- /install_select_SS_plug-ins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script: install_select_SS_plug-ins.sh 4 | ## Author: Mike Morales 5 | ## Last change: 2015-01-13 6 | 7 | ## Path to cocoaDialog. Configure for your environment 8 | cdPath="/library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 9 | 10 | ## Common strings used in dialogs. Customize to your needs 11 | msgTitle="Self Service Sidebar Shortcuts" 12 | SSIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns" 13 | 14 | ## Paths to various directories 15 | tmpPluginsDir="/private/tmp/plug-ins_for_install" 16 | SSRootDir="/Library/Application Support/JAMF/Self Service/" 17 | SSPluginsDir="/Library/Application Support/JAMF/Self Service/Plug-ins/" 18 | 19 | ## Sanity check. Make sure the tmpPluginsDir is there, or else alert and exit 20 | if [ ! -d "$tmpPluginsDir" ]; then 21 | echo "The folder with Plug-ins was not found in the expected location. The installer pkg may have failed" 22 | 23 | "$cdPath" msgbox \ 24 | --title "$msgTitle" \ 25 | --text "There was a problem" \ 26 | --informative-text "$(echo -e "It looks like the installation failed.\nYou can try running the policy again.\n\nIf you continue to see this error, contact the Help Desk for assistance.")" \ 27 | --button1 " OK " \ 28 | --icon caution \ 29 | --width 400 \ 30 | --posY top \ 31 | --quiet 32 | 33 | exit 1 34 | fi 35 | 36 | ## Generate two arrays based on the plist files located in the tmp directory 37 | for plist in $(ls "${tmpPluginsDir}" | grep "plist$"); do 38 | ## Get the file name and title from each plist 39 | ID=$(basename "$plist") 40 | Title=$(defaults read "${tmpPluginsDir}/${plist}" title) 41 | ## Generate the two arrays from the above information 42 | plugInIDs+=("$ID") 43 | plugInTitles+=("$Title") 44 | done 45 | 46 | labelText="Choose the Self Service URLs you would like to install from the items below." 47 | 48 | ## Display the selections to the user 49 | userChoices=$("$cdPath" checkbox \ 50 | --title "$msgTitle" \ 51 | --items "${plugInTitles[@]}" \ 52 | --label "$labelText" \ 53 | --button1 " Choose " \ 54 | --button2 " Cancel " \ 55 | --cancel "button2" \ 56 | --value-required \ 57 | --empty-text "At least one item must be checked before clicking \"Choose\". Or, click \"Cancel\" if you want to exit." \ 58 | --icon-file "$SSIcon" \ 59 | --width 400 \ 60 | --posY top) 61 | 62 | ## Get the button clicked and the boxes checked 63 | buttonClicked=$(echo "$userChoices" | awk 'NR==1 {print}') 64 | boxesChecked=($(echo "$userChoices" | awk 'NR > 1 {print}')) 65 | 66 | ## Loop through resulting array and drop any checked items into final array 67 | index=0 68 | if [ "$buttonClicked" == "1" ]; then 69 | ## Create a new array with only the selected items 70 | for item in ${boxesChecked[@]}; do 71 | if [ "$item" == "1" ]; then 72 | enabledOpts+=(${plugInIDs[$index]}) 73 | enabledOptsTitles+=(" • ${plugInTitles[$index]}") 74 | fi 75 | ((index++)) 76 | done 77 | else 78 | echo "User canceled. Exit" 79 | exit 0 80 | fi 81 | 82 | ## If we got this far, the user has made some choices in the dialog. 83 | ## Check to see if the /Self Service/Plug-ins folder exists. If not, create it 84 | 85 | if [ -d "$SSRootDir" ]; then 86 | if [ -d "$SSPluginsDir" ]; then 87 | echo "The Plug-ins folder exists. Moving on to copying plists into place..." 88 | else 89 | mkdir "$SSPluginsDir" 90 | chown root:admin "$SSPluginsDir" 91 | chmod 755 "$SSPluginsDir" 92 | fi 93 | else 94 | echo "The Self Service and Plug-ins folders must be created..." 95 | mkdir -p "$SSPluginsDir" 96 | chown -R root:admin "$SSRootDir" 97 | chmod -R 755 "$SSRootDir" 98 | fi 99 | 100 | ## Copy checked plist files into /Self Service/Plug-ins/ 101 | for item in "${enabledOpts[@]}"; do 102 | cp "${tmpPluginsDir}/$item" "$SSPluginsDir" 103 | done 104 | 105 | ## Final dialog 106 | text="The following Self Service URLs were installed: 107 | 108 | $(printf '%s\n' "${enabledOptsTitles[@]}") 109 | 110 | You can install additional ones by running this policy again." 111 | 112 | "$cdPath" msgbox \ 113 | --title "$msgTitle" \ 114 | --text "Installation complete" \ 115 | --informative-text "$text" \ 116 | --icon-file "$SSIcon" \ 117 | --button1 " OK " \ 118 | --width 400 \ 119 | --posY top \ 120 | --quiet 121 | 122 | ## Clean up files in tmp 123 | rm -Rfd "$tmpPluginsDir" 124 | -------------------------------------------------------------------------------- /offer2AddIcon-v4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ######################################################################################### 4 | ## 5 | ## Script Name: offer2AddIcon-v4.sh 6 | ## Author: Mike Morales 7 | ## Last Date Modified: 06-13-2014 8 | ## Notes: Edited to include cocoaDialog GUI functions if installed 9 | ## Added error check if app path is not found on disk, to exit 10 | ## 11 | ######################################################################################### 12 | 13 | ## Get information about the logged in user 14 | user=$(stat -f%Su /dev/console) 15 | HomeDirPath=$( /usr/bin/dscl . -read /Users/$user NFSHomeDirectory | awk '{print $2}' ) 16 | 17 | ## Set the location to dockutil and the SS icon 18 | dockutil="/usr/sbin/dockutil" 19 | icon="/Applications/Self Service.app/Contents/Resources/Self Service.icns" 20 | 21 | ## Location of cocoaDialog and jamfHelper 22 | cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 23 | jhPath="/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" 24 | 25 | ## Parameter assignments. $4, $5 and $6 must be assigned within the Casper Suite policy 26 | AppName="" 27 | ID="" 28 | AppPath="" 29 | 30 | 31 | ## Assign REQUIRED parameters 4, 5 and 6 to "AppName", "ID" and "AppPath" respectively 32 | if [ "$4" != "" ] && [ "$AppName" == "" ]; then 33 | AppName=$4 34 | fi 35 | 36 | if [ "$5" != "" ] && [ "$ID" == "" ]; then 37 | ID=$5 38 | fi 39 | 40 | if [ "$6" != "" ] && [ "$AppPath" == "" ]; then 41 | AppPath=$6 42 | fi 43 | 44 | ## Set optional parameter for "after" icon 45 | ## Note, script will continue if this parameter is not assigned in the policy 46 | if [ "$7" != "" ]; then 47 | AfterApp=$7 48 | fi 49 | 50 | ## Check for assigned parameters. If either $4 or $5 fail, we exit with an error 51 | if [ "$AppName" == "" ]; then 52 | echo "Error: The parameter 'AppName' is blank. Please specify an application name." 53 | exit 1 54 | fi 55 | 56 | if [ "$ID" == "" ]; then 57 | echo "Error: The parameter 'ID' is blank. Please specify a Dock icon ID #." 58 | exit 1 59 | fi 60 | 61 | if [ "$AppPath" == "" ]; then 62 | echo "Error: The parameter 'AppPath' is blank. Please specify an application path." 63 | exit 1 64 | fi 65 | 66 | ## Debug process - Echoing parameters 67 | echo "Listing parameters for script: 68 | App Name:\t${AppName} 69 | App Path:\t${AppPath} 70 | Icon ID:\t${ID} 71 | Username:\t${user}" 72 | 73 | ## Creating variables based on assigned parameters 74 | dockIconCheck=$( /usr/bin/defaults read $HomeDirPath/Library/Preferences/com.apple.dock | grep "file-label.*${AppName}" ) 75 | 76 | ## Various message strings that we may use 77 | MSG1="The $AppName installation has completed. 78 | 79 | The $AppName icon is not in your Dock. Would you like to add it now?" 80 | 81 | MSG2="The $AppName installation has completed" 82 | 83 | MSG3="The $AppName icon is not in your Dock. Would you like to add it now?" 84 | 85 | #### BEGIN SCRIPT CONTENTS. DO NOT EDIT BELOW THIS LINE #### 86 | 87 | ## Function to add the Dock icon using either dockutil or the jamf binary 88 | function addDockIcon () 89 | { 90 | 91 | if [[ -f /usr/sbin/dockutil ]]; then 92 | echo "$user clicked \"Yes\", adding $AppName Dock icon. dockutil found on system. Utilizing..." 93 | if [[ $AfterApp != "" ]]; then 94 | if [[ $( /usr/sbin/dockutil --list /Users/$user | awk -F"file" '{print $1}' | grep "$AfterApp" ) != "" ]]; then 95 | ## The After app was found in the user's Dock, so place the new icon after it 96 | /usr/sbin/dockutil --add "/Applications/$AppName.app" --after "$AfterApp" /Users/$user 97 | echo "$AppName icon added after the $AfterApp icon in $user's Dock" 98 | else 99 | ## The After app wasn't found in the user's Dock, so place it at the end 100 | /usr/sbin/dockutil --add "/Applications/$AppName.app" /Users/$user 101 | echo "$AppName icon added to the end of $user's Dock" 102 | fi 103 | else 104 | ## No After app was set in the script parameter, so just add it to the end of the Dock 105 | /usr/sbin/dockutil --add "/Applications/$AppName.app" /Users/$user 106 | echo "$AppName icon added to the end of $user's Dock" 107 | fi 108 | else 109 | ## Dockutil isn't installed, so fall back to using the jamf binary function 110 | echo "$user clicked \"Yes\", adding $AppName Dock icon. dockutil not found on system. Using jamf binary..." 111 | /usr/local/jamf/bin/jamf modifyDock -id "$ID" -end 112 | echo "$AppName icon added to $user's Dock" 113 | fi 114 | 115 | } 116 | 117 | ## First check to make sure the AppPath executable exists on the Mac or we exit silently, 118 | ## since it means perhaps the installation from Self Service failed 119 | 120 | if [[ ! -d "${AppPath}" ]]; then 121 | echo "The application at $AppPath isn't on this Mac, so exit" 122 | exit 1 123 | fi 124 | 125 | ## If the Dock icon is not found, offer to add it 126 | if [[ "$dockIconCheck" = "" ]]; then 127 | echo "The icon for $AppName was not found in $user's Dock" 128 | if [[ -e "$cdPath" ]]; then 129 | ## cocoaDialog is installed. Display cocoaDialog message 130 | offerRes=$( "$cdPath" msgbox --title "Self Service" --text "$MSG2" --informative-text "$MSG3" --button1 " Yes " --button2 " No " \ 131 | --cancel "button2" --timeout 30 --timeout-format " " --icon-file "$icon" --posY top --width 400 --string-output ) 132 | else 133 | ## cocoaDialog isn't installed yet, so fall back to using jamfHelper. Display jamfHelper message 134 | offerRes=$( "$jamfHelper" -windowType utility -title "Self Service" -description "$MSG1" -button1 "Yes" -button2 "No" \ 135 | -defaultButton 1 -cancelButton 2 -timeout 30 -icon "$icon" ) 136 | fi 137 | 138 | ## Check the result of the offer dialog 139 | if [[ "$offerRes" == "0" ]] || [[ "$offerRes" =~ "Yes" ]]; then 140 | ## Looks like the Yes was button was clicked, so move on to the add Dock icon function 141 | addDockIcon 142 | else 143 | echo "$user clicked \"No\". Leaving Dock as is and exiting" 144 | exit 0 145 | fi 146 | fi 147 | 148 | ## If the Dock icon is already present, let the user know installation is complete 149 | if [[ "$dockIconCheck" != "" ]]; then 150 | echo "$AppName icon was already found in $user's Dock" 151 | if [[ -e "$cdPath" ]]; then 152 | ## cocoaDialog is installed. Use it 153 | "$cdPath" msgbox --title "Self Service" --text "Complete" --informative-text "$MSG2" --button1 " OK " --timeout 10 --timeout-format " " --icon-file "$icon" --posY top 154 | else 155 | ## cocoaDialog isn't installed, so use jamfHelper 156 | "$jamfHelper" -windowType utility -title "Self Service" -description "$MSG2" -button1 "OK" -defaultButton 1 -timeout 10 -icon "$icon" 157 | fi 158 | fi 159 | -------------------------------------------------------------------------------- /post_restart_recon_control.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script Name: post_restart_recon_control.sh 4 | ## Version: 1.1 5 | ## Authored by: Mike Morales (mm2270 - JamfNation) 6 | ## Change Log: 7 | ## 2019-Sept-03: Initial script creation 8 | ## 2019-Nov-05: Added maxAttempts variable to allow for a more/less number of reconnection attempts to be set 9 | ## Added additional comments to explain some of the script better 10 | 11 | 12 | ## Purpose: to deploy (create) a LaunchDaemon and companion local script to a Mac that can be called into action later thru the use of a control plist file 13 | ## 14 | ## How this script works: 15 | ## • A local script and LaunchDaemon are both created using the included information in this script. 16 | ## • The scripts name is set to "postrestart.recon.sh" and is created in /private/var/ 17 | ## • It is partially customized at the time of creation by using the 2 variables below, 'yourOrg' and 'maxAttempts' 18 | ## • The LaunchDaemon's identifier is partially customized on creation using the 'yourOrg' variable below 19 | ## • The LaunchDaemon is not loaded after creation (it will load automatically on the next reboot) 20 | 21 | 22 | ## How the post reboot recon process works once in place: 23 | ## 1. The LaunchDaemon calls the script on initial run (only runs once), which typically means after a restart. 24 | ## 2. The script looks for a local plist file (/Library/Preferences/com.$yourOrg.postrestart.reconcontrol.plist) The plist has a simple boolean value for a post reboot recon. 25 | ## 3a. If the value is set to TRUE or 1, the script will attempt to connect to the machine's Jamf Pro server and perform a recon. (See point 4 for alternate response) 26 | ## 3b. It will loop up to 30 times, pausing 1 second between each attempt while trying to connect to the Jamf Pro server. If it cannot connect in 30 attempts, it exits. 27 | ## 3c. If connection is successful, the recon is performed and the plist value is changed to FALSE or 0. 28 | ## 4. If the plist does not exist, the script performs the same steps as above, a recon but then creating a new plist and setting the value to FALSE or 0. 29 | ## 30 | ## After initial deployment, and first use, the LaunchDaemon remains active, and the LaunchDaemon and script remain on the computer. 31 | ## They only spring into action if the control plist is modified through some other means, such as a command run at the completion of a Jamf Pro policy. 32 | 33 | 34 | ## Usage: 35 | ## The plist value can be changed with a simple shell command added to a policy to set the plist value to TRUE, which means a recon will be attempted after the next restart. 36 | ## 37 | ## Example of shell command to enable a post reboot recon (entered into the EXECUTE COMMAND field in a Jamf policy): 38 | ## /usr/bin/defaults write /Library/Preferences/com.acme.postrestart.reconcontrol.plist PerformRecon -bool TRUE 39 | ## 40 | ## Note: the 'acme' portion of the plist name must be changed to the shortname of your organization entered below in the 'yourOrg' value 41 | 42 | 43 | ## Set a value for your organization name. Keep this short, like an acronym if possible. 44 | ## No special characters as they might cause a problem with the final xml file. 45 | ## Lowercase text works best but is not a requirement. 46 | yourOrg="acme" 47 | 48 | 49 | ## The value below determines how many connection attempts to the Mac's Jamf Pro server the script should make before finally giving up. 50 | ## The default is 30, which comes to approximately 30 seconds of attempts. If you feel this is too short, enter a higher integer value here. Enter a whole number only, such as "60" 51 | ## Considerations: 52 | ## Please keep in mind to put in a reasonable value, so the LaunchDaemon is not retrying to connect endlessly, eating up resources. 53 | ## Likewise, be cautious not to set this too low. Since the script is called by a LaunchDaemon, it will fire up right after the Mac starts up. If the Mac doesn't connect to an 54 | ## internet connection until after it gets to the Desktop (ex: Wi-Fi), setting this too low may cause the recon to never occur. 55 | maxAttempts="30" 56 | 57 | 58 | #################################################################################################################################################################################### 59 | ## Items below this line should not be altered, unless you are sure of what changes you need to make 60 | #################################################################################################################################################################################### 61 | 62 | 63 | ## Data for the LaunchDaemon 64 | LAUNCHD_PLIST=' 65 | 66 | 67 | 68 | Label 69 | com.'${yourOrg}'.postrestart.recon 70 | ProgramArguments 71 | 72 | /private/var/postrestart.recon.sh 73 | 74 | RunAtLoad 75 | 76 | 77 | ' 78 | 79 | ## Path to the local control plist file 80 | CONTROL_PLIST="/Library/Preferences/com.${yourOrg}.postrestart.reconcontrol.plist" 81 | 82 | ## Path to the local script that is run by the LaunchDaemon 83 | LOCAL_SCRIPT_LOCATION="/private/var/postrestart.recon.sh" 84 | 85 | ## Data for the local script to be created 86 | LOCAL_SCRIPT='#!/bin/bash 87 | 88 | CONTROL_PLIST="/Library/Preferences/com.'${yourOrg}'.postrestart.reconcontrol.plist" 89 | 90 | function reconLoop () 91 | { 92 | 93 | SERVER_TEST=$(/usr/local/bin/jamf checkJSSConnection 2>&1 > /dev/null; echo $?) 94 | 95 | ## The script will make up to '${maxAttempts}' attempts to contact the Jamf server before exiting, or will perform the recon once it establishes a connection 96 | until [[ $x -eq '${maxAttempts}' ]]; do 97 | ## If the jamf checkJSSConnection exited with a 0 status, perform a recon... 98 | if [[ "$SERVER_TEST" == 0 ]]; then 99 | /usr/local/bin/jamf recon 100 | sleep 1 101 | ## Update the plist value to false to prevent an additional unintended run 102 | /usr/bin/defaults write "$CONTROL_PLIST" PerformRecon -bool FALSE 103 | ## Make sure the plist can be written to later with a policy 104 | chmod 755 "$CONTROL_PLIST" 105 | exit 0 106 | else 107 | ## If the jamf checkJSSConnection did not exit 0, wait 1 second before looping 108 | echo "Pausing 1 second to wait for Jamf server to be available" 109 | sleep 1 110 | fi 111 | ((x++)) 112 | done 113 | 114 | } 115 | 116 | ## Check if the plist file exists, and if a value can be pulled from it 117 | if [ -e "$CONTROL_PLIST" ]; then 118 | VALUE=$(/usr/bin/defaults read "$CONTROL_PLIST" PerformRecon 2>/dev/null) 119 | ## If the value is set to true, or there was no value set... 120 | if [[ "$VALUE" == "1" ]] || [[ -z "$VALUE" ]]; then 121 | ## ...set an initial loop integer value, and move on to the recon loop function 122 | x=1 123 | reconLoop 124 | else 125 | ## If the value is set to anything other than true or not null, exit the process without performing a recon 126 | echo "No post restart recon required. Exiting..." 127 | exit 0 128 | fi 129 | else 130 | echo "No plist file found. Performing a recon loop just in case. The plist file will be created after completion" 131 | reconLoop 132 | fi' 133 | 134 | ## Create the local script 135 | cat << EOS > "$LOCAL_SCRIPT_LOCATION" 136 | ${LOCAL_SCRIPT} 137 | EOS 138 | 139 | ## Ensuree the local script is executable 140 | /bin/chmod +x "$LOCAL_SCRIPT_LOCATION" 141 | 142 | ## Create the LaunchDaemon 143 | cat << EOD > /Library/LaunchDaemons/com.${yourOrg}.postrestart.recon.plist 144 | ${LAUNCHD_PLIST} 145 | EOD 146 | 147 | ## Set the proper owner/group and POSIX permissions on the LaunchDaemon plist 148 | /usr/sbin/chown root:wheel /Library/LaunchDaemons/com.${yourOrg}.postrestart.recon.plist 149 | /bin/chmod 644 /Library/LaunchDaemons/com.${yourOrg}.postrestart.recon.plist 150 | 151 | echo "LaunchDaemon and script creation completed." 152 | exit 0 153 | -------------------------------------------------------------------------------- /reboot_scheduler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script name: reboot_scheduler.sh 4 | ## Script author: Mike Morales 5 | ## Last change: 2015-06-04 6 | 7 | ## Synopsis: 8 | 9 | ## reboot_scheduler is intended to be used after software updates have been installed on a Mac 10 | ## that require a reboot. 11 | ## Rather than initiating an immediate reboot of the Mac, or only allowing a short grace period 12 | ## before a reboot occurs, the script can be used to prompt the logged in user to select a reboot time 13 | ## from a few options in a dialog. (uses cocoaDialog) 14 | ## 15 | ## Alternately, when the script is being run from a Casper Suite policy, a value in minutes can also be 16 | ## passed to the script in Parameter 4 ($4), which will set up a future reboot schedule. In this mode, 17 | ## the user is notified of when the 5 minute countdown will begin before their Mac reboots. 18 | ## 19 | ## In either situation, a LaunchDaemon with a specific StartCalendarInterval and a script will then 20 | ## be created. The LaunchDaemon is set up to run the script at the appointed StartCalendarInterval time, 21 | ## which will then start a 5 minute countdown, but only if the Mac has not already been rebooted in 22 | ## the interim by the user. 23 | 24 | ## Script exit codes 25 | ## 26 | ## 0 Script exited successfully 27 | ## 1 Some date parameters necessary for the LaunchDaemon could not be determined 28 | ## 2 The LaunchDaemon and/or the script could not be created 29 | ## 3 The LaunchDaemon could not be loaded (launchctl error) 30 | ## 4 cocoaDialog could not be found in the specified location 31 | 32 | 33 | ## Path to cocoaDialog (Edit to match your environment) 34 | cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 35 | 36 | ## The value for mins to reboot. If left blank, we will get the selection from user input or passed in Parameter 4. 37 | mins="" 38 | 39 | ## If Parameter 4 was assigned a value in the policy, check to see if its a whole integer. 40 | ## If true, override the above blank value for hrs with the value assigned to $4. 41 | ## If false, retain a blank assignment for the hrs value (user will be prompted to select) 42 | 43 | ## ***** VERY IMPORTANT!! PLEASE READ ***** 44 | ## While not a strict requirement, it is recommended that you use values in minutes that are divisible by 60 45 | ## to equal whole hours, (ex: For 2 hours, enter "120" for 120 minutes) 46 | ## 47 | ## If a value is used that is not divisible by 60, the script will use a decimal value to display 48 | ## in the dialog presented to the user. 49 | ## (ex: 150 minutes will be converted to 1.5 hours as shown to the user) 50 | 51 | ## 1. ONLY PASS A VALUE THAT IS CONVERTED TO MINUTES (ex: To set a value for 2 hours, enter "120" for 120 minutes) 52 | ## 2. Values passed above 10 will result in a scheduled reboot using the LaunchDaemon. 53 | ## 3. Values passed at or below 10 will result in a reboot timer appearing immediately to the user with a countdown 54 | 55 | if [ ! -z "$4" ]; then 56 | ## Check to make sure a whole integer was passed 57 | if [[ $(echo "$4 / $4" | bc) == "1" ]]; then 58 | mins="$4" 59 | echo "Pre-assigned minutes value was assigned: $mins" 60 | else 61 | mins="" 62 | fi 63 | fi 64 | 65 | ## Set the minutes deferral optons here. Only used if a 'mins' value is not assigned in Parameter 4. 66 | ## These are used both in the dialogs and in the resulting scheduled time to reboot. 67 | 68 | ## ***** VERY IMPORTANT!! PLEASE READ ***** 69 | ## 1. ONLY USE VALUES IN MINUTES FOR ALL THREE 'deferOpt' ITEMS BELOW 70 | ## 2. ONLY change the values between the quote marks. Do NOT change the values between the brackets [] 71 | ## (Failure to recalculate into a minutes value will result in odd or very short deferrals for reboots occurring) 72 | ## 3. Start with the longest deferral value and work down. The final value can be set to 10 or lower to initiate an immediate reboot countdown 73 | 74 | ## Example: To set your longest deferral option to 8 hours, enter a value of "480" for 'deferOpt[0]' 75 | 76 | deferOpt[0]="120" ## Longest deferral option (usually results in 'hours') 77 | deferOpt[1]="30" ## Shorter deferral option (still usually results in 'hours') 78 | deferOpt[2]="5" ## Reboot soon option. Recommended not to exceed 10 (minutes). If value is above "10" it will revert to a normal delayed reboot 79 | 80 | ## Create an array with the assigned values 81 | deferOpts=(${deferOpt[0]} ${deferOpt[1]} ${deferOpt[2]}) 82 | 83 | ## Path to the log file 84 | rdlog="/private/var/log/rdlog.log" 85 | 86 | ## Start new log file entry with date stamp 87 | echo -e "Start timestamp: $(date)" | tee -a "$rdlog" 88 | 89 | ## Sanity check for cocoaDialog 90 | if [ ! -e "$cdPath" ]; then 91 | echo -e "Error 4: cocoaDialog could not be found at: $cdPath. Exiting...\n" | tee -a "$rdlog" 92 | exit 4 93 | fi 94 | 95 | ## Beginning of setDeferral function 96 | function setDeferral () 97 | { 98 | 99 | ## Time conversions are done below, based on the value passed in parameter 4 100 | ## 1. Calculate specified mins in seconds 101 | hrsSecs=$((mins*60)) 102 | #hrsSecs=$((hrs*60)) ## Uncomment this variable for testing purposes. Sets scheduled reboot time to minutes instead of hrs 103 | ## 2. Get the current time in seconds 104 | currSecs=$(date +"%s") 105 | ## 3. Generate scheduled reboot date (current time in seconds + hrs in seconds) in seconds 106 | futureSecs=$((currSecs+hrsSecs)) 107 | ## 4. Convert scheduled reboot date into 'day_hour_minute_month' format 108 | rDate=$(date -jf "%s" "$futureSecs" +"%d_%H_%M_%m") 109 | ## 5. Convert scheduled reboot date into readable format for dialog 110 | rDateFormat=$(date -jf "%s" "$futureSecs" +"%B %d, %Y at %I:%M %p") 111 | 112 | ## Now use the rDate variable and extract the individual strings to use for the LaunchDaemon 113 | Day=$(echo "$rDate" | awk -F_ '{print $1}' | sed 's/^0//') 114 | Hour=$(echo "$rDate" | awk -F_ '{print $2}' | sed 's/^0//') 115 | Minute=$(echo "$rDate" | awk -F_ '{print $3}' | sed 's/^0//') 116 | MinuteR=$(echo "$rDate" | awk -F_ '{print $3}') 117 | Month=$(echo "$rDate" | awk -F_ '{print $4}' | sed 's/^0//') 118 | 119 | ## Check all StartCalendarInterval strings to make sure they were generated 120 | if [[ ! -z "$Day" ]] && [[ ! -z "$Hour" ]] && [[ ! -z "$Minute" ]] && [[ ! -z "$Month" ]]; then 121 | echo -e "Values needed for the LaunchDaemon have been determined" | tee -a "$rdlog" 122 | echo -e "Values to be used for the LaunchDaemon's StartCalendarInterval: 123 | Day: $Day 124 | Hour: $Hour 125 | Minute: $Minute 126 | Month: $Month" | tee -a "$rdlog" 127 | 128 | echo -e "This Mac will be set to reboot on ${Month}/${Day} at ${Hour}:${MinuteR}:00" | tee -a "$rdlog" 129 | else 130 | echo -e "Error 1: Some paramaters for the LaunchDaemon StartCalendarInterval could not be obtained. Exiting...\n" | tee -a "$rdlog" 131 | exit 1 132 | fi 133 | 134 | echo -e "Creating the LaunchDaemon..." | tee -a "$rdlog" 135 | ## Create the restart LaunchDaemon using the above values 136 | 137 | echo ' 138 | 139 | 140 | 141 | Label 142 | com.org.rd 143 | Program 144 | /private/var/rtimer.sh 145 | RunAtLoad 146 | 147 | StartCalendarInterval 148 | 149 | Day 150 | '$Day' 151 | Hour 152 | '$Hour' 153 | Minute 154 | '$Minute' 155 | Month 156 | '$Month' 157 | 158 | 159 | ' > "/Library/LaunchDaemons/com.org.rd.plist" 160 | 161 | 162 | ######################################### Begin script creation stage ######################################### 163 | 164 | ## Create a local script for the LaunchDaemon to use 165 | echo "#!/bin/sh 166 | 167 | ## Set creation date and scheduled reboot timestamp values 168 | cTime=$(date +\"%s\") 169 | rTime=\"$((futureSecs-60))\" 170 | 171 | rdlog=\"/private/var/log/rdlog.log\" 172 | 173 | ## Path to cocoaDialog. Customize path to your needs 174 | cdPath=\"/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog\" 175 | 176 | mins=\"$mins\" 177 | 178 | ## Calculate specified time in seconds 179 | hrsSecs=\$((mins*60)) 180 | #hrsSecs=\$((hrs*60)) ## Testing purposes. Comment out 181 | lastReboot=\$(sysctl kern.boottime | awk '{print \$5}' | sed 's/,$//') 182 | currTime=\$(date +\"%s\") 183 | timeDiff=\$((currTime-lastReboot)) 184 | 185 | function rebootAlert () 186 | { 187 | 188 | echo \"Executing a 5 minute reboot delay...\" | tee -a \"\$rdlog\" 189 | ## Begin a 5 minute delayed restart and push to the background 190 | /sbin/shutdown -r +5 & 191 | 192 | echo \"Delayed restart exit status: \$(echo \$?)\" | tee -a \"\$rdlog\" 193 | 194 | msgText=\"Important software updates were installed on your Mac $rMin $rInc ago that require a reboot to complete. 195 | 196 | The grace period for a reboot chosen has now expired and your Mac must reboot to finish these updates. 197 | Please save any open work within the next 5 minutes to avoid any data loss. Your Mac will automatically reboot once 5 minutes has passed.\" 198 | 199 | buttonLabel=\" OK \" 200 | 201 | icon=\"/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff\" 202 | 203 | ## Show the reboot required message with a 5 minute countdown 204 | \"\$cdPath\" msgbox --title \"\" --text \"Your Mac's reboot grace period has expired\" --informative-text \"\$msgText\" --width 450 --icon-file \"\$icon\" --button1 \"\$buttonLabel\" --posY top --timeout 297 --quiet 205 | 206 | echo \"Cleaning up. Deleting LaunchDaemon...\" | tee -a \"\$rdlog\" 207 | rm -f \"/Library/LaunchDaemons/com.org.rd.plist\" 208 | rm -f \"\$0\" 209 | 210 | exit 0 211 | 212 | } 213 | 214 | function cleanUp () 215 | { 216 | 217 | echo \"Cleaning up...\\n\" | tee -a \"\$rdlog\" 218 | 219 | rm -f \"/Library/LaunchDaemons/com.org.rd.plist\" 220 | /bin/launchctl remove com.org.rd.plist 221 | rm -f \"\$0\" 222 | 223 | exit 0 224 | 225 | } 226 | 227 | ## Find out if we actually need to reboot the Mac by checking how long the Mac's been up. 228 | ## 1. Compare the last boot time in seconds with the creation timestamp of the script (in Unix seconds). 229 | ## a) If the creation timestamp value is lower than the last boot time, it means the Mac has been 230 | ## rebooted since the script was created. There is no need for a reboot. Move to cleanup stage. 231 | ## b) If the creation timestamp value is higher than the last boot time, the Mac has not been 232 | ## rebooted since the script creation date. Move to step 2... 233 | ## 2. Check the current time in seconds to see if it is lower or higher than the scheduled reboot time. 234 | ## a) If the current time in seconds is less than the scheduled reboot time, exit. 235 | ## b) If the current time in seconds is equal or higher than the scheduled reboot time, we need to 236 | ## reboot the Mac. Begin the rebootAlert function. 237 | 238 | if [[ \"\$cTime\" -lt \"\$lastReboot\" ]]; then 239 | echo \"This Mac has been rebooted within the last $rMin $rInc. No action required. Cleaning up silently...\" | tee -a \"\$rdlog\" 240 | cleanUp 241 | elif [[ \"\$(date +\"%s\")\" -lt \"\$rTime\" ]]; then 242 | echo \"We haven't reached the time to reboot. Exiting...\\n\" | tee -a \"\$rdlog\" 243 | exit 0 244 | else 245 | echo \"This Mac has not been rebooted within the last $rMin $rInc. Action required. Starting reboot process...\" | tee -a \"\$rdlog\" 246 | rebootAlert 247 | fi" > /private/var/rtimer.sh 248 | 249 | ######################################### End of script creation stage ######################################### 250 | 251 | ## Finish up by checking status of LaunchDaemon and local script and load the LaunchDaemon 252 | if [[ -e "/Library/LaunchDaemons/com.org.rd.plist" ]] && [[ -e "/private/var/rtimer.sh" ]]; then 253 | ## Set permissions on the plist 254 | echo -e "LaunchDaemon and script were successfully created. Correcting permissions on LaunchDaemon..." | tee -a "$rdlog" 255 | chown root:wheel "/Library/LaunchDaemons/com.org.rd.plist" 256 | chmod 644 "/Library/LaunchDaemons/com.org.rd.plist" 257 | chflags hidden "/Library/LaunchDaemons/com.org.rd.plist" 258 | 259 | ## Make the script executable 260 | echo -e "Making the script executable..." | tee -a "$rdlog" 261 | chmod +x "/private/var/rtimer.sh" 262 | 263 | ## Now unload and load the LaunchDaemon 264 | ## (We do an unload in the event the new LaunchDaemon is overwriting an older one. This ensures the new job settings are properly loaded into launchd) 265 | /bin/launchctl unload "/Library/LaunchDaemons/com.org.rd.plist" 2>/dev/null 266 | /bin/launchctl load "/Library/LaunchDaemons/com.org.rd.plist" 267 | 268 | if [ "$?" == "0" ]; then 269 | echo -e "LaunchDaemon loaded successfully" | tee -a "$rdlog" 270 | else 271 | echo -e "Error 3: LaunchDaemon could not be loaded. launchctl error code: $?" | tee -a "$rdlog" 272 | exit 3 273 | fi 274 | else 275 | echo -e "Error 2: LaunchDaemon or script could not be created. Exiting...\n" | tee -a "$rdlog" 276 | exit 2 277 | fi 278 | 279 | if [ "$preassigned" ]; then 280 | 281 | header="A required reboot has been scheduled" 282 | 283 | rebootSetText="Your administrator has installed important updates on your Mac that require a reboot. 284 | 285 | However, to give you a grace period on this reboot, your Mac has been set up to reboot $mins minutes ($rMin $rInc) from now, on $rDateFormat. You will see a reminder 5 minutes before the reboot to give you time to close any open work. 286 | 287 | If you reboot your Mac prior to this date, you will not be asked to reboot again at the appointed time." 288 | 289 | 290 | else 291 | 292 | header="A reboot deferral has been set" 293 | 294 | rebootSetText="Thank you! 295 | 296 | Your Mac has been set up to reboot ${deferOpt[$userSelection]} minutes ($rMin $rInc) from now, on $rDateFormat. You will see a reminder 5 minutes before the reboot to give you time to close any open work. 297 | 298 | If you reboot your Mac prior to this date, you will not be asked to reboot again at the appointed time." 299 | 300 | fi 301 | 302 | "$cdPath" msgbox \ 303 | --title "" \ 304 | --text "$header" \ 305 | --informative-text "$rebootSetText" \ 306 | --button1 " OK " \ 307 | --width 450 \ 308 | --posY top \ 309 | --icon info \ 310 | --quiet 311 | 312 | exit 0 313 | 314 | } 315 | ## End of setDeferral function 316 | 317 | ## Beginning of rebootSoon function 318 | function rebootSoon () 319 | { 320 | 321 | ## Convert the mins var into seconds for the timeout of the dialog 322 | timeoutSecs=$(($mins*60)) 323 | 324 | icon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff" 325 | 326 | if [ "$preassigned" ]; then 327 | rebootSoonHead="Your Mac has been scheduled to reboot soon" 328 | 329 | rebootSoonText="Important updates have been installed on your Mac that require a reboot, which your administrator has scheduled to occur in the next $mins minutes. 330 | 331 | Please save any open work within the next $mins minutes to avoid any data loss. You can choose to leave this window open, or close it. The $mins minute countdown will continue in the background." 332 | 333 | rDelayTime="${deferOpt[$userSelection]}" 334 | echo "reboot delay is ${rDelayTime}" 335 | else 336 | rebootSoonHead="You chose to reboot in $mins minutes" | tee -a "$rdlog" 337 | 338 | rebootSoonText="Please save any open work within the next $mins minutes to avoid any data loss. You can choose to leave this window open, or close it. The $mins minute countdown will continue in the background." 339 | 340 | rDelayTime="$mins" 341 | echo "reboot delay is ${rDelayTime}" | tee -a "$rdlog" 342 | fi 343 | 344 | echo -e "Beginning ${rDelayTime} minute reboot delay" | tee -a "$rdlog" 345 | 346 | ## Begin delayed restart and push to the background 347 | /sbin/shutdown -r +${rDelayTime} & 348 | 349 | "$cdPath" msgbox \ 350 | --title "" \ 351 | --text "$rebootSoonHead" \ 352 | --informative-text "$rebootSoonText" \ 353 | --width 450 \ 354 | --icon-file "$icon" \ 355 | --button1 " OK " \ 356 | --posY top \ 357 | --timeout "$timeoutSecs" \ 358 | --quiet 359 | 360 | exit 361 | 362 | } 363 | 364 | ## A couple of sanity checks are performed to see if we need to actually schedule anything 365 | 366 | ## Get the logged in user name and UID 367 | loggedInUser=$(ls -l /dev/console | awk '{print $3}') 368 | loggedInID=$(id "$loggedInUser" | tr ' ' '\n' | awk -F'[=|(]' '/uid/{print $2}') 369 | 370 | ## Check to see if someone is actually logged in 371 | if [[ "$loggedInUser" == "root" ]] && [[ "$loggedInID" == "0" ]]; then 372 | echo -e "No user is currently logged in on this Mac. We can reboot immediately" | tee -a "$rdlog" 373 | 374 | ## If the Mac is sitting at a login screen, just reboot right away 375 | shutdown -r now 376 | else 377 | echo -e "A user \"$loggedInUser\" is logged in on this Mac. Proceeding..." | tee -a "$rdlog" 378 | fi 379 | 380 | ## Check to see if a previous scheduled reboot has been set up 381 | if [ -e "/Library/LaunchDaemons/com.org.rd.plist" ]; then 382 | if [[ $(/bin/launchctl list | grep "com.org.rd") != "" ]]; then 383 | echo -e "This Mac has already been set up to reboot at a future date. We will not create an additional schedule." | tee -a "$rdlog" 384 | exit 0 385 | fi 386 | fi 387 | 388 | 389 | ## Set up text for dialog 390 | askRebootText="Important Software Updates were just installed on your Mac. Some of these updates require a reboot to complete. 391 | 392 | However, you have the option of deferring the reboot to one of the options below. Please make a choice and click Continue." 393 | 394 | if [ -z "$mins" ]; then 395 | 396 | ## Loop over array and create strings and variables to use for dialog 397 | NO=0 398 | for OPT in "${deferOpts[@]}"; do 399 | if [[ "$OPT" -gt "60" ]]; then 400 | incW[$NO]="hours" 401 | deferRaw=$(echo "scale=1; $OPT/60" | bc) 402 | if [[ "${deferRaw##*.}" == "0" ]]; then 403 | defer[$NO]="${deferRaw%.*}" 404 | else 405 | defer[$NO]="${deferRaw}" 406 | fi 407 | elif [[ "$OPT" == "60" ]]; then 408 | incW[$NO]="hour" 409 | defer[$NO]=$((OPT/60)) 410 | else 411 | incW[$NO]="minutes" 412 | defer[$NO]="$OPT" 413 | fi 414 | NO=$((NO+1)) 415 | done 416 | 417 | echo -e "No pre-assigned deferral was set for the script. Prompting user for input..." | tee -a "$rdlog" 418 | userChoice=$( "$cdPath" radio \ 419 | --title "" \ 420 | --label "$askRebootText" \ 421 | --button1 "Continue" \ 422 | --items "${defer[0]} ${incW[0]} from now" "${defer[1]} ${incW[1]} from now" "${defer[2]} ${incW[2]} from now" \ 423 | --width 450 \ 424 | --posY top \ 425 | --icon caution \ 426 | --value-required \ 427 | --empty-text "Choose one of the deferral options before clicking \"Continue\"" \ 428 | --timeout 300 \ 429 | --timeout-format " " ) 430 | 431 | userSelection=$( echo "$userChoice" | awk 'NR==2{print}' ) 432 | 433 | if [ ! -z "$userSelection" ]; then 434 | mins="${deferOpt[$userSelection]}" 435 | rMin="${defer[$userSelection]}" 436 | rInc="${incW[$userSelection]}" 437 | echo -e "User chose a $mins minute deferral for reboot" | tee -a "$rdlog" 438 | 439 | if [[ "$mins" -gt 10 ]]; then 440 | setDeferral 441 | else 442 | rebootSoon 443 | fi 444 | else 445 | 446 | ## If the cocoaDialog message was quit by the user without making a selection 447 | ## set a default value equal to the longest allowed deferral and create the LaunchDaemon/script 448 | mins="${deferOpt[0]}" 449 | rMin="${defer[0]}" 450 | rInc="${incW[0]}" 451 | echo -e "The dialog exited (timed out or user quit), so we're setting a default $mins minute deferral" | tee -a "$rdlog" 452 | setDeferral 453 | fi 454 | 455 | else 456 | 457 | ## If we got a pre-assigned mins value, skip user input, create some variables, and display the restart set dialog 458 | preassigned="yes" 459 | echo -e "A pre-assigned value was defined by \$4" | tee -a "$rdlog" 460 | 461 | if [[ "$mins" -gt "60" ]]; then 462 | rMinRaw=$(echo "scale=1; $mins/60" | bc) 463 | if [[ "${rMinRaw##*.}" == "0" ]]; then 464 | rMin="${rMinRaw%.*}" 465 | else 466 | rMin="${rMinRaw}" 467 | fi 468 | rInc="hours" 469 | elif [[ "$mins" == "60" ]]; then 470 | rMin=$((mins/60)) 471 | rInc="hour" 472 | elif [[ "$mins" -lt "60" ]]; then 473 | rMin="$mins" 474 | rInc="minutes" 475 | fi 476 | 477 | if [[ "$mins" -gt 10 ]]; then 478 | setDeferral 479 | else 480 | echo "A pre-assigned value was passed to the script that was at or below 10 minutes." | tee -a "$rdlog" 481 | rebootSoon 482 | fi 483 | fi 484 | -------------------------------------------------------------------------------- /selectable_SoftwareUpdate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Script Name: Selectable_SoftwareUpdate.sh 4 | ## Script Author: Mike Morales, @mm2270 on JAMFNation 5 | ## Last Update: 2016-01-20 6 | 7 | ## Last Update: 8 | ## Included 'Reboot Now' button, when a reboot is required after installed updates, 9 | ## which waits 4 seconds, then does an immediate reboot when clicked. 10 | 11 | ## Set Internal Field Separator to % to fix updates since 10.13.2 12 | IFS='%' 13 | 14 | ## Path to cocoaDialog (customize to your own location) 15 | cdPath="/Library/Application Support/JAMF/bin/cocoaDialog.app/Contents/MacOS/cocoaDialog" 16 | 17 | ## Quick sanity check to make sure cocoaDialog is installed in the path specified 18 | if [ ! -e "$cdPath" ]; then 19 | echo "cocoaDialog was not found in the path specified. It may not be installed, or the path is wrong. Exiting..." 20 | exit 1 21 | else 22 | ## If cocoaDialog was found, check to make sure its the 3.0 beta 7 version 23 | exePath=$(echo ${cdPath#$(dirname "$(dirname "$cdPath")")/}) 24 | cDInfoPath="$(echo "$cdPath" | sed "s|$exePath||")Info.plist" 25 | if [[ $(defaults read "$cDInfoPath" CFBundleShortVersionString) != "3.0-beta7" ]]; then 26 | echo "The version of cocoaDialog installed is not 3.0-beta 7. The 3.0-beta7 version is required for proper functioning of this script." 27 | exit 1 28 | fi 29 | fi 30 | 31 | ## Set the installAllAtLogin flag here to 'yes' or leave it blank (equivalent to 'no') 32 | ## Function: When the script is run on a Mac that is at the login window, if the flag is set to 'yes', 33 | ## it will lock the login window to prevent unintended logins and proceed to install all available updates. 34 | ## Once completed, the login window will either be unlocked in the case of no restarts needed, 35 | ## or a restart will be done immediately to complete the installations. 36 | 37 | installAllAtLogin="yes" 38 | 39 | ## Get minor version of OS X 40 | osVers=$( sw_vers -productVersion | cut -d. -f2 ) 41 | 42 | ## Set appropriate Software Update icon depending on OS version 43 | if [[ "$osVers" -lt 8 ]]; then 44 | swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/Software Update.icns" 45 | else 46 | swuIcon="/System/Library/CoreServices/Software Update.app/Contents/Resources/SoftwareUpdate.icns" 47 | fi 48 | 49 | ## Set appropriate Restart icon depending on OS version 50 | if [[ "$osVers" == "9" ]]; then 51 | restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.tiff" 52 | else 53 | restartIcon="/System/Library/CoreServices/loginwindow.app/Contents/Resources/Restart.png" 54 | fi 55 | 56 | ## Start - Check Casper Suite script parameters and assign any that were passed to the script 57 | 58 | ## PARAMETER 4: Set the Organization/Department/Division name. Used in dialog titles 59 | ## Default string of "Managed" is used if no script parameter is passed 60 | if [[ "$4" != "" ]]; then 61 | orgName="$4" 62 | else 63 | orgName="Managed" 64 | fi 65 | 66 | ## PARAMETER 5: Set to "no" (case insensitive) to show a single progress bar update for all installations. 67 | ## Default value of "yes" will be used if no script parameter is passed 68 | if [[ "$5" != "" ]]; then 69 | shopt -s nocasematch 70 | if [[ "$5" == "no" ]]; then 71 | showProgEachUpdate="no" 72 | else 73 | showProgEachUpdate="yes" 74 | fi 75 | shopt -u nocasematch 76 | else 77 | showProgEachUpdate="yes" 78 | fi 79 | 80 | ## PARAMETER 6: Set the number of minutes until reboot (only used if installations require it) 81 | ## Default value of 5 minutes is assigned if no script parameter is passed 82 | ## Special note: Only full integers can be used. No decimals. 83 | ## If the script detects a non whole integer, it will fall back on the default 5 minute setting. 84 | if [[ "$6" != "" ]]; then 85 | ## Run test to make sure we have a non floating point integer 86 | if [[ $(expr "$6" / "$6") == "1" ]]; then 87 | minToRestart="$6" 88 | else 89 | echo "Non integer, or a decimal value was passed. Setting reboot time to default (5 minutes)" 90 | minToRestart="5" 91 | fi 92 | else 93 | minToRestart="5" 94 | fi 95 | 96 | ## Parameter 7: Set to the full path of an icon or image file for any dialogs that are not using the 97 | ## Apple Software Update icon. This could be a company logo icon for example 98 | ## Default icon is set in the following manner: 99 | ## If no script parameter is passed, or the icon/image can not be found and JAMF Self Service is present on the Mac, its icon will be used 100 | ## If Self Service is not found, the Software Update icon will be used 101 | if [[ "$7" != "" ]]; then 102 | if [[ -e "$7" ]]; then 103 | echo "A custom dialog icon was set: $7" 104 | msgIcon="$7" 105 | else 106 | if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then 107 | ## Self Service present. Use a default Self Service icon if the file specified could not be found 108 | msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns" 109 | else 110 | ## Icon file not found, and Self Service not present. Set icon to Software Update 111 | msgIcon="$swuIcon" 112 | fi 113 | fi 114 | else 115 | if [[ -e "/Applications/Self Service.app/Contents/Resources/Self Service.icns" ]]; then 116 | ## Self Service present. Use a default Self Service icon if no parameter was passed 117 | msgIcon="/Applications/Self Service.app/Contents/Resources/Self Service.icns" 118 | else 119 | ## No parameter passed, and Self Service not present. Set icon to Software Update 120 | msgIcon="$swuIcon" 121 | fi 122 | fi 123 | 124 | ## End - Check Casper Suite script parameters 125 | 126 | 127 | ## Text displayed in dialog prompting for selections. Customize if desired. 128 | ## Two versions: 129 | ## One,for when reboot *required* updates are found. 130 | ## Two,for when only non-reboot updates are found. 131 | swuTextReboots="Select the Apple Software Update items you would like to install now from the list below. 132 | 133 | ◀ = Indicates updates that will REQUIRE a reboot of your Mac to complete. 134 | 135 | To install all updates that will not require a reboot, click \"Install No Reboot Updates\" 136 | 137 | " 138 | 139 | swuTextNoReboots="Select the Apple Software Update items you would like to install now from the list below. 140 | 141 | " 142 | 143 | ################################################## ENV VARIABLES ##################################################### 144 | ## ## 145 | ## These variables are gathered to set up the visual environment of the messaging to match the logged in user's ## 146 | ## settings. We gather the settings, then change the root account's settings to match. ## 147 | ## ## 148 | ###################################################################################################################### 149 | 150 | ## Get current logged in user name 151 | loggedInUser=$( ls -l /dev/console | /usr/bin/awk '{ print $3 }' ) 152 | echo "Current user is: $loggedInUser" 153 | 154 | ## Determine logged in user's home directory path 155 | HomeDir=$( dscl . read /Users/$loggedInUser NFSHomeDirectory | awk '{ print $NF }' ) 156 | 157 | ## Get logged in user's Appearance color settings 158 | AquaColor=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleAquaColorVariant 2> /dev/null ) 159 | 160 | ## If user has not changed their settings, value will be null. Set to default 'Aqua' color 161 | if [[ -z "$AquaColor" ]]; then 162 | AquaColor="1" 163 | else 164 | AquaColor="$AquaColor" 165 | fi 166 | 167 | ## Get logged in user's Keyboard access settings 168 | KeybdMode=$( defaults read "$HomeDir/Library/Preferences/.GlobalPreferences" AppleKeyboardUIMode 2> /dev/null ) 169 | 170 | ## If user has not changed their settings, value will be null. Set to default 'Text boxes and lists only' 171 | if [[ -z "$KeybdMode" ]]; then 172 | KeybdMode="0" 173 | else 174 | KeybdMode="$KeybdMode" 175 | fi 176 | 177 | ## Set the root account environment settings to match current logged in user's 178 | defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleAquaColorVariant -int "${AquaColor}" 179 | defaults write /private/var/root/Library/Preferences/.GlobalPreferences AppleKeyboardUIMode -int "${KeybdMode}" 180 | 181 | ## Restart cfprefsd so new settings will be recognized 182 | killall cfprefsd 183 | 184 | ################################# Do not modify below this line ######################################## 185 | 186 | ## Function to run when installations are complete 187 | doneRestart () 188 | { 189 | 190 | doneMSG="The installations have completed, but your Mac needs to reboot to finalize the updates. 191 | 192 | Your Mac will automatically reboot in $minToRestart minutes. Begin to save any open work and close applications now." 193 | 194 | ## Display initial message for 30 seconds before starting the progress bar countdown 195 | userSelection=$("$cdPath" msgbox \ 196 | --title "$orgName Software Update > Updates Complete" \ 197 | --text "Updates installed successfully" \ 198 | --informative-text "$doneMSG" \ 199 | --button1 " OK " \ 200 | --button2 "Reboot Now" \ 201 | --icon-file "$msgIcon" \ 202 | --posY top \ 203 | --width 450 \ 204 | --timeout 30 \ 205 | --timeout-format " ") 206 | 207 | if [[ "$userSelection" == "1" ]]; then 208 | echo "User clicked OK button. Continuing with reboot countdown..." 209 | elif [[ "$userSelection" == "2" ]]; then 210 | echo "User clicked Reboot Now. Initiating reboot in 4 seconds..." 211 | sleep 4 212 | /sbin/shutdown -r now 213 | else 214 | echo "Dialog timed out. Continuing with reboot countdown..." 215 | fi 216 | 217 | ## Sub-function to (re)display the progressbar window. Developed to work around the fact that 218 | ## CD responds to Cmd+Q and will quit. The script continues the countdown. The sub-function 219 | ## causes the progress bar to reappear. When the countdown is done we quit all CD windows 220 | showProgress () 221 | { 222 | 223 | ## Display progress bar 224 | "$cdPath" progressbar --title "" --text " Preparing to restart this Mac..." \ 225 | --width 500 --height 90 --icon-file "$restartIcon" --icon-height 48 --icon-width 48 < /tmp/hpipe & 226 | 227 | ## Send progress through the named pipe 228 | exec 20<> /tmp/hpipe 229 | 230 | } 231 | 232 | ## Close file descriptor 20 if in use, and remove any instance of /tmp/hpipe 233 | exec 20>&- 234 | rm -f /tmp/hpipe 235 | 236 | ## Create the name pipe input for the progressbar 237 | mkfifo /tmp/hpipe 238 | sleep 0.2 239 | 240 | ## Run progress bar sub-function 241 | showProgress 242 | 243 | echo "100" >&20 244 | 245 | timerSeconds=$((minToRestart*60)) 246 | startTime=$( date +"%s" ) 247 | stopTime=$((startTime+timerSeconds)) 248 | secsLeft=$timerSeconds 249 | progLeft="100" 250 | 251 | while [[ "$secsLeft" -gt 0 ]]; do 252 | sleep 1 253 | currTime=$( date +"%s" ) 254 | progLeft=$((secsLeft*100/timerSeconds)) 255 | secsLeft=$((stopTime-currTime)) 256 | minRem=$((secsLeft/60)) 257 | secRem=$((secsLeft%60)) 258 | if [[ $(ps axc | grep "cocoaDialog") == "" ]]; then 259 | showProgress 260 | fi 261 | echo "$progLeft $minRem minutes, $secRem seconds until reboot. Please save any work now." >&20 262 | done 263 | 264 | echo "Closing progress bar." 265 | exec 20>&- 266 | rm -f /tmp/hpipe 267 | 268 | ## Close cocoaDialog. This block is necessary for when multiple runs of the sub-function were called in the script 269 | for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do 270 | /usr/bin/osascript -e 'tell application "cocoaDialog" to quit' 271 | done 272 | 273 | ## Clean up by deleting the SWUList file in /tmp/ 274 | rm /tmp/SWULIST 275 | 276 | ## Delay 1/2 second, then force reboot 277 | sleep 0.5 278 | shutdown -r now 279 | 280 | } 281 | 282 | ## Function to install selected updates, updating progress bar with information 283 | installUpdates () 284 | { 285 | 286 | if [[ "${restartReq}" == "yes" ]]; then 287 | installMSG="Installations are now running. Please do not shut down your Mac or put it to sleep until the installs finish. 288 | 289 | IMPORTANT: 290 | Because you chose some updates that require a restart, we recommend saving any important documents now. Your Mac will reboot soon after the installations are complete." 291 | 292 | elif [[ "${restartReq}" == "no" ]] || [[ "${restartReq}" == "" ]]; then 293 | installMSG="Updates are now installing. Please do not shut down your Mac or put it to sleep until the installs finish." 294 | fi 295 | 296 | ## Sub-function to display both a button-less CD window and a progress bar 297 | ## This sub routine gets called by the enclosing function. It can also be called by 298 | ## the install process if it does not see 2 instances of CD running 299 | showInstallProgress () 300 | { 301 | 302 | ## Display button-less window above progress bar, push to background 303 | "$cdPath" msgbox --title "$orgName Software Update > Installation" --text "Installations in progress" \ 304 | --informative-text "${installMSG}" --icon-file "${msgIcon}" --width 450 --height 184 --posY top & 305 | 306 | ## Display progress bar 307 | echo "Displaying progress bar window." 308 | "$cdPath" progressbar --title "" --text " Preparing to install selected updates..." \ 309 | --posX "center" --posY 198 --width 450 --float --icon installer < /tmp/hpipe & 310 | 311 | ## Send progress through the named pipe 312 | exec 10<> /tmp/hpipe 313 | 314 | } 315 | 316 | ## Close file descriptor 10 if in use, and remove any instance of /tmp/hpipe 317 | exec 10>&- 318 | rm -f /tmp/hpipe 319 | 320 | ## Create the name pipe input for the progressbar 321 | mkfifo /tmp/hpipe 322 | sleep 0.2 323 | 324 | ## Run the install progress sub-function (shows button-less CD window and progressbar 325 | showInstallProgress 326 | 327 | if [[ "$showProgEachUpdate" == "yes" ]]; then 328 | echo "Showing individual update progress." 329 | ## Run softwareupdate in verbose mode for each selected update, parsing output to feed the progressbar 330 | ## Set initial index loop value to 0; set initial update count value to 1; set variable for total updates count 331 | i=0; 332 | pkgCnt=1 333 | pkgTotal="${#selectedItems[@]}" 334 | for index in "${selectedItems[@]}"; do 335 | UpdateName="${progSelectedItems[$i]}" 336 | echo "Now installing ${UpdateName}..." 337 | /usr/sbin/softwareupdate --verbose -i "${index}" 2>&1 | while read line; do 338 | ## Re-run the sub-function to display the cocoaDialog window and progress 339 | ## if we are not seeing 2 items for CD in the process list 340 | if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then 341 | killall cocoaDialog 342 | showInstallProgress 343 | fi 344 | pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 ) 345 | echo "$pct Installing ${pkgCnt} of ${pkgTotal}: ${UpdateName}..." >&10 346 | done 347 | let i+=1 348 | let pkgCnt+=1 349 | done 350 | else 351 | ## Show a generic progress bar that progresses through all installs at once from 0-100 % 352 | echo "Parameter 5 was set to \"no\". Showing single progress bar for all updates" 353 | softwareupdate --verbose -i "${SWUItems[@]}" 2>&1 | while read line; do 354 | ## if we are not seeing 2 items for CD in the process list 355 | if [[ $(ps axc | grep "cocoaDialog" | wc -l | sed 's/^ *//') != "2" ]]; then 356 | killall cocoaDialog 357 | showInstallProgress 358 | fi 359 | pct=$( echo "$line" | awk '/Progress:/{print $NF}' | cut -d% -f1 ) 360 | echo "$pct Installing ${#SWUItems[@]} updates..." >&10 361 | done 362 | fi 363 | 364 | echo "Closing progress bar." 365 | exec 10>&- 366 | rm -f /tmp/hpipe 367 | 368 | ## Close all instances of cocoaDialog 369 | echo "Closing all cocoaDialog windows." 370 | for process in $(ps axc | awk '/cocoaDialog/{print $1}'); do 371 | /usr/bin/osascript -e 'tell application "cocoaDialog" to quit' 372 | done 373 | 374 | ## If any installed updates required a reboot... 375 | if [[ "${restartReq}" == "yes" ]]; then 376 | ## ...then move to the restart phase 377 | doneRestart 378 | ## If no installed updates required a reboot, display updates complete message instead 379 | elif [[ "${restartReq}" == "no" ]]; then 380 | echo "Showing updates complete message." 381 | doneMSG="The installations have completed successfully. You can resume working on your Mac." 382 | "$cdPath" msgbox --title "$orgName Software Update > Updates Complete" \ 383 | --text "Updates installed successfully" --informative-text "$doneMSG" \ 384 | --button1 " OK " --posY top --width 450 --icon-file "$msgIcon" 385 | 386 | ## Clean up by deleting the SWUList file in /tmp/ before exiting the script 387 | echo "Cleaning up SWU list file." 388 | rm /tmp/SWULIST 389 | exit 0 390 | fi 391 | 392 | } 393 | 394 | ## Function to assess which items were checked, and create new arrays 395 | ## used for installations and other functions 396 | assessChecks () 397 | { 398 | 399 | ## Check to see if the installNoReboots flag was set by the user 400 | if [[ "$installNoReboots" == "yes" ]]; then 401 | echo "User chose to install all non reboot updates. Creating update(s) array and moving to install phase" 402 | ## If flag was set, build update arrays from the noReboots array 403 | for index in "${noReboots[@]}"; do 404 | selectedItems+=( "${SWUItems[$index]}" ) 405 | hrSelectedItems+=( "${SWUList[$index]}" ) 406 | progSelectedItems+=( "${SWUProg[$index]}" ) 407 | done 408 | 409 | ## Automatically set the restart required flag to "no" 410 | restartReq="no" 411 | 412 | ## Then move on to install updates function 413 | installUpdates 414 | fi 415 | 416 | ## If installNoReboots flag was not set, generate array of formatted 417 | ## checkbox indexes for parsing based on the selections from the user 418 | i=0; 419 | for state in ${Checks[*]}; do 420 | checkboxstates=$( echo "${i}-${state}" ) 421 | let i+=1 422 | ## Set up an array we can read through later with the state of each checkbox 423 | checkboxfinal+=( "${checkboxstates[@]}" ) 424 | done 425 | 426 | for check in "${checkboxfinal[@]}"; do 427 | if [[ "$check" =~ "-1" ]]; then 428 | ## First, get the index of the checked item 429 | index=$( echo "$check" | cut -d- -f1 ) 430 | ## Second, generate 3 new arrays: 431 | ## 1) Short names of the updates for the installation 432 | ## 2) Names of updates as presented in the dialog (for checking restart status) 433 | ## 3) Names of the updates for updating the progress bar 434 | selectedItems+=( "${SWUItems[$index]}" ) 435 | hrSelectedItems+=( "${SWUList[$index]}" ) 436 | progSelectedItems+=( "${SWUProg[$index]}" ) 437 | fi 438 | done 439 | 440 | echo "The following updates will be installed: ${progSelectedItems[@]}" 441 | 442 | ## Determine if any of the checked items require a reboot 443 | restartReq="no" 444 | for item in "${hrSelectedItems[@]}"; do 445 | if [[ $(echo "${item}" | grep "^◀") != "" ]]; then 446 | echo "At least one selected update will require reboot. Setting the restartReq flag to \"yes\"" 447 | restartReq="yes" 448 | break 449 | fi 450 | done 451 | 452 | echo "Restart required?: ${restartReq}" 453 | 454 | ## If we have some selected items, move to install phase 455 | if [[ ! -z "${selectedItems[@]}" ]]; then 456 | echo "Updates were selected" 457 | installUpdates 458 | fi 459 | 460 | } 461 | 462 | ## The initial message function 463 | startDialog () 464 | { 465 | 466 | ## Generate array of SWUs for dialog 467 | while read SWU; do 468 | SWUList+=( "$SWU" ) 469 | done < <(echo "${readSWUs}") 470 | 471 | ## Generate array of SWUs for progress bar 472 | while read item; do 473 | SWUProg+=( "${item}" ) 474 | done < <(echo "${progSWUs}") 475 | 476 | ## Generate array of SWUs for installation 477 | while read swuitem; do 478 | SWUItems+=( "$swuitem" ) 479 | done < <(echo "${installSWUs}") 480 | 481 | ## Generate an array of indexes for any non-reboot updates 482 | for index in "${!SWUList[@]}"; do 483 | if [[ $(echo "${SWUList[$index]}" | grep "^◀") == "" ]]; then 484 | noReboots+=( "$index" ) 485 | fi 486 | done 487 | 488 | ## Show dialog with selectable options 489 | if [[ ! -z "${noReboots[@]}" ]]; then 490 | echo "There are some non reboot updates available. Showing selection screen to user" 491 | SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" \ 492 | --label "$swuTextReboots" --button1 " Install " --button2 " Cancel " --button3 "Install No Reboot Updates" --cancel "button2" \ 493 | --icon-file "$swuIcon" --icon-height 80 --icon-width 80 --width 500 --posY top ) 494 | 495 | ## Get the button pressed and the options checked 496 | Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' ) 497 | Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' )) 498 | ## Set up a non array string from the checkboxes returned 499 | ChecksNonArray=$( echo "$SWUDiag" | awk 'NR==2{print $0}' ) 500 | 501 | ## If the "Install" button was clicked 502 | if [[ "$Button" == "1" ]]; then 503 | echo "User clicked the \"Install\" button." 504 | ## Check to see if at least one box was checked 505 | if [[ $( echo "${ChecksNonArray}" | grep "1" ) == "" ]]; then 506 | echo "No selections made. Alerting user and returning to selection screen." 507 | "$cdPath" msgbox --title "$orgName Software Update" --text "No selections were made" \ 508 | --informative-text "$(echo -e "You didn't select any updates to install.\n\nIf you want to cancel out of this application, click the \"Cancel\" button in the window instead, or press the Esc key.\n\nThe Software Update window will appear again momentarily.")" \ 509 | --button1 " OK " --timeout 10 --timeout-format " " --width 500 --posY top --icon caution 510 | ## Because we are restarting the function, first empty all previously built arrays 511 | ## Credit to Cem Baykara (@Cem - JAMFNation) for discovering this issue during testing 512 | SWUList=() 513 | SWUProg=() 514 | SWUItems=() 515 | ## Now restart this function after the alert message times out 516 | startDialog 517 | else 518 | ## "Install" button was clicked and items checked. Run the assess checkbox function 519 | echo "Selections were made. Moving to assessment function..." 520 | assessChecks 521 | fi 522 | elif [[ "$Button" == "3" ]]; then 523 | ## "Install No Reboot Updates" button was clicked. Set the installNoReboots flag to "yes" and skip to check assessment 524 | echo "User clicked the \"Install No Reboot Updates\" button." 525 | installNoReboots="yes" 526 | assessChecks 527 | else 528 | echo "User chose to Cancel. Exiting..." 529 | exit 0 530 | fi 531 | 532 | else 533 | ## No non-reboot updates were available. Display a different dialog to the user 534 | echo "No non-reboot updates found, but other updates available. Showing selection dialog to user" 535 | SWUDiag=$( "$cdPath" checkbox --title "$orgName Software Update" --items "${SWUList[@]}" \ 536 | --label "$swuTextNoReboots" --button1 " Install " --button2 " Cancel " --cancel "button2" \ 537 | --icon-file "$swuIcon" --icon-height 80 --icon-width 80 --width 500 --posY top --value-required \ 538 | --empty-text "$(echo -e "You must check at least one item before clicking \"Install\".\n\nIf you want to exit, click \"Cancel\" or press the esc key.")" ) 539 | 540 | ## Get the button pressed and the options checked 541 | Button=$( echo "$SWUDiag" | awk 'NR==1{print $0}' ) 542 | Checks=($( echo "$SWUDiag" | awk 'NR==2{print $0}' )) 543 | 544 | if [[ "$Button" == "1" ]]; then 545 | ## "Install" button was clicked. Run the assess checkbox function 546 | echo "User clicked the \"Install\" button" 547 | assessChecks 548 | else 549 | echo "User chose to Cancel from the selection dialog." 550 | echo "Cleaning up SWU list file. Exiting..." 551 | rm /tmp/SWULIST 552 | exit 0 553 | fi 554 | fi 555 | 556 | } 557 | 558 | ## Function to lock the login window and install all available updates 559 | startLockScreenAgent () 560 | { 561 | 562 | ## Note on this function: To make the script usable outside of a Casper Suite environment, 563 | ## we are using the Apple Remote Management LockScreen.app, located inside the AppleVNCServer bundle. 564 | ## This bundle and corresponding app is installed by default in all recent versions of OS X 565 | 566 | ## Set a flag to yes if any updates in the list will require a reboot 567 | while read line; do 568 | if [[ $(echo "$line" | grep "^◀") != "" ]]; then 569 | rebootsPresent="yes" 570 | break 571 | fi 572 | done < <(echo "$readSWUs") 573 | 574 | ## Define the name and path to the LaunchAgent plist 575 | PLIST="/Library/LaunchAgents/com.LockLoginScreen.plist" 576 | 577 | ## Define the text for the xml plist file 578 | LAgentCore=" 579 | 580 | 581 | 582 | Label 583 | com.LockLoginScreen 584 | RunAtLoad 585 | 586 | LimitLoadToSessionType 587 | LoginWindow 588 | ProgramArguments 589 | 590 | /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/MacOS/LockScreen 591 | -session 592 | 256 593 | -msg 594 | Updates are currently being installed on this Mac. It will automatically be restarted or returned to the login window when installations are complete. 595 | 596 | 597 | " 598 | 599 | ## Create the LaunchAgent file 600 | echo "Creating the LockLoginScreen LaunchAgent..." 601 | echo "$LAgentCore" > "$PLIST" 602 | 603 | ## Set the owner, group and permissions on the LaunchAgent plist 604 | echo "Setting proper ownership and permissions on the LaunchAgent..." 605 | chown root:wheel "$PLIST" 606 | chmod 644 "$PLIST" 607 | 608 | ## Use SIPS to copy and convert the SWU icon to use as the LockScreen icon 609 | 610 | ## First, back up the original Lock.jpg image 611 | echo "Backing up Lock.jpg image..." 612 | mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg \ 613 | /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak 614 | 615 | ## Now, copy and convert the SWU icns file into a new Lock.jpg file 616 | ## Note: We are converting it to a png to preserve transparency, but saving it with the .jpg extension so LockScreen.app will recognize it. 617 | ## Also resize the image to 400 x 400 pixels so its not so honkin' huge! 618 | echo "Creating SoftwareUpdate icon as png and converting to Lock.jpg..." 619 | sips -s format png "$swuIcon" --out /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg \ 620 | --resampleWidth 400 --resampleHeight 400 621 | 622 | ## Now, kill/restart the loginwindow process to load the LaunchAgent 623 | echo "Ready to lock screen. Restarting loginwindow process..." 624 | kill -9 $(ps axc | awk '/loginwindow/{print $1}') 625 | 626 | ## Install all available Software Updates 627 | echo "Screen locked. Installing all available Software Updates..." 628 | /usr/sbin/softwareupdate --install --all 629 | 630 | if [ "$?" == "0" ]; then 631 | ## Delete LaunchAgent and reload the Login Window 632 | echo "Deleting the LaunchAgent..." 633 | rm "$PLIST" 634 | sleep 1 635 | 636 | if [[ "$rebootsPresent" == "yes" ]]; then 637 | ## Put the original Lock.jpg image back where it was, overwriting the SWU Icon image 638 | echo "The rebootsPresent flag was set to 'yes' Replacing Lock.jpg image and immediately rebooting the Mac..." 639 | mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak \ 640 | /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 641 | 642 | ## Kill the LockScreen app and restart immediately 643 | killall LockScreen 644 | /sbin/shutdown -r now 645 | else 646 | ## Put the original Lock.jpg image back where it was, overwriting the SWU Icon image 647 | echo "The rebootsPresent flag was not set. Replacing Lock.jpg image and restoring the loginwindow..." 648 | mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak \ 649 | /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 650 | 651 | ## Kill/restart the login window process to return to the login window 652 | kill -9 $(ps axc | awk '/loginwindow/{print $1}') 653 | fi 654 | 655 | else 656 | 657 | echo "There was an error with the installations. Removing the Agent and unlocking the login window..." 658 | 659 | rm "$PLIST" 660 | sleep 1 661 | 662 | mv /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg.bak \ 663 | /System/Library/CoreServices/RemoteManagement/AppleVNCServer.bundle/Contents/Support/LockScreen.app/Contents/Resources/Lock.jpg 664 | 665 | ## Kill/restart the login window process to return to the login window 666 | kill -9 $(ps axc | awk '/loginwindow/{print $1}') 667 | exit 0 668 | fi 669 | 670 | } 671 | 672 | ## The script starts here 673 | 674 | ## Gather available Software Updates and export to a file 675 | echo "Pulling available Software Updates..." 676 | /usr/sbin/softwareupdate -l > /tmp/SWULIST 677 | echo "Finished pulling available Software Updates into local file" 678 | 679 | echo "Checking to see what updates are available..." 680 | ## Generate list of readable items and installable items from file 681 | readSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K \[recommended\][ *]//g;s/\[restart\] */◀ /g' | sed 's/[ ]//g' ) 682 | progSWUs=$( cat /tmp/SWULIST | awk -F"," '/recommended/{print $2,$1}' | sed -e 's/[0-9]*K \[recommended\][ *]//g;s/\[restart\] *//g' | sed 's/[ ]//g' ) 683 | installSWUs=$( cat /tmp/SWULIST | grep -v 'recommended' | awk -F'\\* ' '/\*/{print $NF}' ) 684 | 685 | ## First, make sure there's at least one update from Software Update 686 | if [[ -z "$readSWUs" ]]; then 687 | echo "No pending Software Updates found for this Mac. Exiting..." 688 | exit 0 689 | elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" != "root" ]]; then 690 | echo "Software Updates are available, and a user is logged in. Moving to initial dialog..." 691 | startDialog 692 | elif [[ ! -z "$readSWUs" ]] && [[ "$loggedInUser" == "root" ]]; then 693 | if [ "$installAllAtLogin" == "yes" ]; then 694 | echo "SWUs are available, no-one logged in and the installAllAtLogin flag was set. Locking screen and installing all updates..." 695 | startLockScreenAgent 696 | else 697 | echo "SWUs are available, no-one logged in but the installAllAtLogin flag was not set. Exiting..." 698 | exit 0 699 | fi 700 | fi 701 | --------------------------------------------------------------------------------