├── 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' | sed "s/'/'/g")
22 | title=$(echo "$title" | sed -e 's/"/"/g;s/&/&/g;s/>/>/g;s/</g' | sed "s/'/'/g")
23 | subtitle=$(echo "$subtitle" | sed -e 's/"/"/g;s/&/&/g;s/>/>/g;s/</g' | sed "s/'/'/g")
24 |
25 |
26 | if [ -d "/Library/Application Support/JAMF/" ]; then
27 | ## Check for an existing Self Service Plug-ins directory
28 | if [ -d "/Library/Application Support/JAMF/Self Service/Plug-ins" ]; then
29 | pluginFolderExists="yes"
30 | destDir="/Library/Application Support/JAMF/Self Service/Plug-ins"
31 |
32 | ## Capture a list of any installed plug-ins
33 | installedPlugIns=$(ls "/Library/Application Support/JAMF/Self Service/Plug-ins" | sed 's/.plist//g' | sort -g | awk '$1 > 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 |
--------------------------------------------------------------------------------