|
Back when I began doing OS X administration, I asked myself how I could automatically keep the OS X systems updated without having to login to them remotely each day to run Software Update and without giving end users administrator access to the machines (which is a no-no in our corporate security policy). I found out that Apple makes it pretty easy to do this with the command line, which means it can be setup to run in a cron job. For instance, to automatically get all the latest required updates from Apple installed on a machine without having to do it manually, you can create a cron task to issue the following command as root: softwareupdate -q -i -r
This works fine for most users, most of the time. Unfortunately, what if you're a QuickTime Pro user and Apple puts out a QuickTime update marked as "required"? That's right, Software Update will dutifully download the new version and install it over top of the version you're registered for. Unless you have the time and money to call them and buy a new registration code for the new version of QuickTime, your "Pro" features are all locked down again. This is precisely the situation Apple placed me in as an administrator a while ago. One of our artists' machines was automatically updated to Quicktime 7.0 from 6.5.2 Pro, rendering the "Pro" functionality inactive. Because of the way things work in our corporate environment, it's not a quick or simple process for him to just buy the new registration code. It means a lengthy purchase process that can take weeks, sometimes even months. An artist can't do without QuickTime for that long. But as the Mac Administrator, I couldn't do without the systems getting their security updates for that long. Apple had once again put me in a bind.
How I Scripted Around Apple's Limitation What I had to do to get out of this bind was develop the script you see at the end of this article. This script is designed to do the following: - Query Software Update to get a list of the available updates from Apple. That list looks something like this:
Software Update Tool Copyright 2002-2003 Apple Computer, Inc.
Software Update found the following new or updated software: ! iPod2005-06-26-0 iPod Updater 2005-06-26, (null), 30052K [required] ! iTunes4-4.9 iTunes, 4.9, 10140K [required]
- Strip out the lines of that list that include the names of the individual updates. This gets us a list that looks like this:
! iPod2005-06-26-0 ! iTunes4-4.9 - Remove unnecessary characters from those lines. The resulting list looks like this:
iPod2005-06-26-0 iTunes4-4.9 - Compare the names of the updates to a list of names or partial names I DON'T want installed, ever. For example, if I create the following list:
iPod QuickTime Xserve
and I run the above list of available updates through it, the resulting "filtered" list of updates should look something like this:
iTunes4-4.9 - One by one, install all the other available updates. In other words, it issues the following command for each named update in the list:
softwareupdate -q -i <updatename>
(where "<updatename>" is the name of one of the updates from the list)
For our example, we'd issue only one command, which would be:
softwareupdate -q -i iTunes4-4.9
And that's all it takes to automatically and selectively update OS X, installing the updates we care about and ignoring those we don't. Caveats About This Script As implemented here, there may be a few minor issues that you'll want to investigate and/or correct. Depending on whether they cause me trouble, I may go back and correct them later. If I do, this list will disappear from this article: - Right now, I collect a list of ALL the available updates and I apply any update that doesn't match my "exclude" list. That means I'll be getting "non-required" updates along with my "required" updates. I want that to happen for the moment. I may not later on.
- There is a possibility that an update that requires a reboot could terminate the script and force that reboot before the script has the chance to apply the other updates. That means if Apple puts out several updates today and the script runs tonight, it may only do "some" of those updates because when it gets to one of the updates that requires a reboot (and it reboots) the script is terminated automatically. Adding "restart" functionality seemed overkill, since I think eventually all the updates will get applied. For example, imagine that we had the following list of updates, some of which require restarts:
a (no restart) b (restart needed) c (no restart) d (no restart) e (restart needed) f (restart needed)
The first evening, we'd get update "a" and "b", then we'd restart. The next night, "a" and "b" would be done and therefore no longer appear in the list, so we'd do "c", "d", and "e" (then restart). The following evening, we'd do "f" and restart. So it would take 3 evenings to get all those updates, but we'd eventually get them all. So I'm not too concerned that I don't run all the updates before the reboot happens. If you are, you could probably adapt the script to prioritize the "no restart" updates to the top of the list so that you get as many as possible, and/or set the cron to run more than once every day. Personally, that felt like overkill so I didn't do it.
- This script has been tested with MacOS X 10.3.x and 10.4. It may work with earlier or later releases, but that's not guaranteed.
There may be other bugs in the script that I'm not aware of, especially since I just developed it today. Use at your own risk! Exactly "How" the Script Works For those of you who are more interested in a step-by-step understanding of the script and how it works, I would encourage you to read the comments I've sprinkled liberally through the code. How To Install and Use the Script on Your System(s) Here's how to install and configure the script for your own use: - Copy the code below into a "Plain Text Format" document in TextEdit on your Mac. Adjust the "workdir" path to the path where you plan to save and run the script. Save the script to that path, adjusting the name to something like "selupdate.command" as you do, and tell TextEdit not to append the ".txt" to the file name.
- Go to a Terminal window (login as root or administrator). Use the "cd" command to change to the directory where you saved the above script file. Enter the UNIX command "chmod a+x selupdate.command" (replacing the "selupdate.command" bit with the name you used above if you changed it).
- Copy the lines in the "updexclude.txt" file below into a new "Plain Text Format" document in TextEdit on your Mac. If you're happy with the updates I'm excluding, save the file in the same directory you specified for "workdir" above and call the file "updexclude.txt".
- To test the script, while logged in as "root", launch a Terminal window and issue the command "softwareupdate -l" to list the available updates. If there are no updates available, you'll need to wait until there are since the script won't do anything much if there are no updates to apply. Assuming there are updates available, compare those to the updates on your "exclude list" to see if any of them are applicable. For example, if the exclude list contains "quicktime" and Apple lists only one update called "QuickTime701" then the script should exclude that one available update and more or less do nothing (I've written the script to not be case-sensitive because I figure Apple might decide to name an update "QuickTime" this time, "quickTime" another time, and "quicktime" on some other occasion - I don't want any of those updates if I can help it). But if there is at least one available update NOT on the exclude list, you should be in a good position to test.
- Bring up a terminal window as "root" and "cd" to the directory where you saved the script. To start the test, issue the command line "csh selupdate.command" (replacing "selupdate.command" with the name you saved the script under in the first step above). If there are pending updates from Apple that aren't in the "exclude list" file, they should be installed automatically. A "**DONE**" message will be displayed when the script is finished.
- Once you're satisfied that the script is working to your satisfaction, you might want to schedule it to run every day. To do that (assuming you have an existing cron file), add the following line to your cron file (or if you have no cron file, create a text file containing the following line):
00 23 * * * /bin/csh <workdir>/selupdate.command
(Replace <workdir> above with the path to the place where you saved the script file.)
This means that the cron daemon should run the script for you every day of the week at 23:00, or 11pm local time. To change the time of day, adjust the "23" to an hour of your choosing. Replace "23" with an asterisk ("*") if you want it to run every hour, on the hour (which I don't recommend since it could force a reboot when you're using the machine!).
For more information on configuring the script to run at specific days, times, etc., do a Google search for information about "cron" and "crontab". Covering cron in detail is beyond the scope of this article.
The Selective OS X Update script: #! /bin/csh # # This script is intended to get a list of the updates currently # available from Apple via SoftwareUpdate, filter out the updates # matching a list we DON'T want, and apply the rest. # # Initial draft: July 27, 2005 # Last Updated: July 29, 2005 # Updated By: Michael Salsbury # # # Set variables we will use later # # workdir is the working directory where the script and its # temporary files should be located. # set workdir="/Library/CASAdmin" # # excludefile is the file which contains names or partial # names of updates we want to exclude from processing. # set excludefile="$workdir/casupdateexclude.txt" # # Capture the current update list to a temp file. # softwareupdate -l > "$workdir/updtemp.txt" # # If we're up to date, stop. # set iscurrent = `grep -c "Your software is up to date" "$workdir/updtemp.txt"` # if ($iscurrent == 1) then echo "This machine is current. No updates available." exit endif # # Find the optional updates in the list, their lines in # the command output start with "* " # grep '* ' "$workdir/updtemp.txt" > "$workdir/updlist.txt" # # Find the required updates in the list, their lines in # the command output start with "! " # grep '! ' "$workdir/updtemp.txt" >> "$workdir/updlist.txt" # # Remove the leading spaces and "*" or "!" # perl -pi -e 's/ * //' "$workdir/updlist.txt" perl -pi -e 's/\* //' "$workdir/updlist.txt" perl -pi -e 's/! //' "$workdir/updlist.txt" # # At this point, updlist.txt should contain a plain vanilla # text list with the names of the available updates, one per # line, in the format expected by the softwareupdate command. # # To avoid updating the things we don't want to update, we # now need to filter this list down, eliminating the lines # that match pre-defined update names that we know we don't # want to include, for example "QuickTime" (which we might # not want to update until we've purchased an upgraded Pro # license). # # The exclude file's format is pretty simple. For example, # if we want to filter out all Xserve and QuickTime updates # from the process, we'd have a file that contains only the # following lines, created in TextEdit and saved as a .txt # file: # # QuickTime # Xserve # # The "-i" option in the grep command line below implies # that I want to ignore case in the update name. This will # hopefully prevent me from missing some other "quicktime" # update if Apple changes its capitalization standards. If # you'd rather be more specific and eliminate only those # updates which exactly match something, delete the "-i" from # the lines below. # # The "-v" option below is essential to the script. It tells # grep that we want all the lines that DON'T match what it # finds in the exclude file. # # The "-f" option tells grep that what we want to do is supply # a list of things to look for (or rather, avoid) in the file # specified by the $excludefile variable. # # grep will pipe its filtered list to "filtered.txt" in our # work directory # grep -v -f "$excludefile" "$workdir/updlist.txt" > "$workdir/filtered.txt" # # At this point, we should have a "filtered.txt" file that contains # the exact names of all the updates we want the softwareupdate # command to apply to the computer. Now, we just need to feed those # updates to the command. # foreach line (`cat "$workdir/filtered.txt"`) echo "Attempting update: $line" echo "Attempting update: $line" >> "$workdir/output.txt" softwareupdate -i $line >> "$workdir/updoutput.txt" end # # Remove the temp files. # if (-e "$workdir/filtered.txt") then rm "$workdir/filtered.txt" endit if (-e "$workdir/updlist.txt") then rm "$workdir/updlist.txt" endif if (-e "$workdir/updtemp.txt") then rm "$workdir/updtemp.txt" endif # # Display output if we have any. # if (-e "$workdir/updoutput.txt") then echo " " echo "Following is the captured output from the update commands:" echo " " cat "$workdir/updoutput.txt" rm -f "$workdir/updoutput.txt" endif echo "**END OF OUTPUT - Updating completed. **" echo " "
Sample content for the casupdateexclude.txt file: QuickTime iPod Xserve AirPort iSight
Related Blogs:
Related Links:
|