Update to script that runs Windows Update

Articles in the "Programmatically Run Windows Update" series

  1. Programmatically run Windows Update (as part of a broader patch and reboot process)
  2. Update to script that runs Windows Update [This article]

As part of the patch and reboot script I use in my Exchange environment, there is a separate script that runs Windows Update, first covered here.  I have updated the script to allow skipping the installation of any patches by specifying the KB numbers in a companion file.

Starting on the second line of the file (so that the first line can contain instructions), list one KB number per line.  To use this list, there is a new function in the script:

The function gets the current path of the script, checks for the existence of the file, and reads each KB into an array. If the file is not present, or is present but no KB numbers are listed, the array will just be empty. This allows the script to still run successfully when you don’t have the file or specify any patches to skip.

When searching for available updates via the Microsoft.Update.Session interface, you can’t filter on a KB number, but you can filter on an Update ID (UID). Unfortunately, to cross reference KB number and UID, you have to first perform a search anyway. So rather than searching twice, I just work with the collection created from the search. The object that contains the list of available hotfixes cannot be manipulated the same way as a standard collection object since it is a COM object, so you can’t use a method like Remove() to remove an item from the collection.

Therefore, in order to have a collection of updates that don’t include certain KBs, you can build a new collection, skipping items in the first collection that match a filter. To do this, I create another Microsoft.Update.UpdateColl object that will be used to specify which updates to download (so that you don’t download all of the updates and then skip certain ones):

To add to this collection, each item in the first collection is compared to the array that contains the list of KBs:

(The WriteEvent function is defined in the script and is used to write a custom event to the Application log so that you will know that an update was intentionally skipped.) The rest of the script is basically unchanged; just some minor tweaks to accommodate the new code. The download has been updated and includes the companion text file. (If you are wondering why a companion file is used, it is because, as part of the Exchange patch and reboot script, the Windows Update script is remotely called via a scheduled task, and there isn’t a way to dynamically specify command line parameters for a scheduled task (without modifying the task itself)).

  Run-WindowsUpdate.zip (1.8 KiB)

Patch and reboot script updated to v2.4

These are the new features since the last posted version of the reboot script:

  • PowerShell remoting is now used to connect to Exchange instead of adding the snap-in locally.  Adding the snap-in isn’t a supported method by Microsoft since it bypasses RBAC, which resulted in an error balancing databases because the session couldn’t write to the admin audit log.  This also means the script no longer needs to be run on a system that has the management tools installed, so that check has been removed.
  • An optional parameter (RemotingServer) has been added to allow you to specify the Exchange server to use for implicit remoting.  The default action is that the script will look in AD for an Exchange server to connect to and will loop through the list until it is successful.  If you want to control the server used for remoting, then use this parameter.
  • Two optional parameters (ReportRecipients and StatusRecipients) have been added to allow you to override the recipients used for their respective purposes.  This is allows somebody running the script one-off to specify who should receive the messages without having to edit the script.  You can use either (or both) parameters.
  • An optional switch parameter (PauseBeforePatching) has been added to allow you to have the script pause before the patch script is called (if not using RebootOnly) to allow you to do some manual action on the server outside of the script and without it losing its place.  Manually installing a rollup is an example of what you may want to do, or some other application update.  Because this parameter requires interaction in the shell, it will be ignored if it is used in a non-interactive session (such as a scheduled task).
  • When processing a mailbox server, the cluster node will be paused and resumed at the appropriate times.  The script will remotely invoke the commands on the mailbox server being processed, so you don’t need to have the cluster management tools installed locally.
  • Comment-based help has been updated to reflect the new parameters.

The updated version can be downloaded below:

  Reboot-ExchangeServers.zip (7.5 KiB)

 

Patch and reboot script for an entire Exchange environment, Part 4

After enabling transcript logging, loading the Exchange snap-in, and connecting to SCOM, the server processing begins.  This is grouped by server role and production vs. DR.

Based on my environment’s configuration where hub transport and client access server are colocated, the production HT/CAS servers are processed, then the UM servers, and then mailbox servers.  When the production mailbox servers are complete, the databases are re-balanced according to activation preference.  Then the DR servers are processed.

Let’s go through the ProcessServerGroup function.

The function is called by passing the array that contains the server names, along with the role they serve. Looping through each server in the array, the display reflects the status. If status updates are enabled, line 9 calls a function with the status and the server name; here is the SendStatus function:

The only noteworthy thing about the previous function is the use of the SmtpClient .NET class instead of the Send-MailMessage cmdlet. This is because of the different encoding used between them. When sending to a mobile device via SMS, the cmdlet’s encoding causes non-printable characters to be displayed in the SMS body. Using the .NET class avoids this.

The next section of the ProcessServerGroup function is for SCOM. If SCOM updates are enabled and the remote PowerShell session to the RMS is established, a function to start maintenance mode is called:

And here is that function:

Lines 7-10 send commands to the remote session. It took some time to figure out how to do this because it isn’t well documented. The gist is that the SCOM object for a server has to be found, then a new maintenance window created. You can’t have an open-ended window, so the duration is set to the variable defined earlier. If the server is successfully put into maintenance mode, Line 12 sets a variable with this state. If any of the previous steps fail, a warning is output to the screen. I don’t treat this as a fatal error, so the patching will continue.

The next post in the series will continue with the ProcessServerGroup function and moving active database copies if a server with the mailbox role is being processed.

Download the complete script:

  Reboot-ExchangeServers.zip (7.5 KiB)

Patch and reboot script for an entire Exchange environment, Part 3

The first part of the script body gets the current window title, then checks if transcript is running, starting it if not.

Since the script can’t use remote PowerShell to connect to an Exchange server that will be rebooted, the snap-in is loaded locally.

If the SCOM variable is set to true, then the remote PowerShell connection to the RMS is loaded.

Throughout the script you will see that whenever something is output to the console it is prefixed with the time. This is accomplished with a function to format it the way I want to see it.

This is the function to connect to the SCOM RMS:

The function creates a new session to the RMS, scoping the variable as $global so that if the script aborts and you restart it in the same shell, the session can still be used instead of creating a new one. Once the session is established, several script blocks are executed in it to load the SCOM snap-in and configure it so that a server can be put into maintenance mode. It took some time to figure out how to do all this because the PowerShell documentation for SCOM is very poor and code samples on other sites were for working with clusters or doing other tasks. If no error occurs during any of this, a variable is set to indicate that the session is working. If an error does occur, the script does not abort. I only treat this as a warning since an inability to put a server into maintenance shouldn’t keep the server from being patched. The status variable is set so that the script won’t try and put servers into maintenance mode if doing so will only result in an error anyway.

The next post in the series will get into the actual processing of the servers.
Download the complete script:

  Reboot-ExchangeServers.zip (7.5 KiB)

Patch and reboot script for an entire Exchange environment, Part 2

The first section, or region as used in PowerGUI, is where all the variables are defined.  The script will process servers based on their Exchange roles and whether they are in the production or standby (DR) data center.  For my environment, this means hub transport and client access are on the same server, while the mailbox and unified messaging roles are dedicated.

Each server grouping is an array that the code body will loop through.  To accommodate a grouping that might only have one server, the variables are cast as arrays.

The next part of the variables section is for putting the servers into maintenance mode in SCOM.

All of the variable declarations have trailing comments so you know what they are for, but they are also explained here.  Line 2 is for indicating whether you have SCOM in your environment or, even if you do, that you don’t want to execute that portion in the code body.  Line 3 is the name of the root management server to connect to with remote PowerShell.  Line 4 is for how long the server should be put into maintenance.  (When a server has finished rebooting and all is well, the server will be taken out of maintenance mode, so this is in case it can’t be done for some reason.)

Next is where non-mail variables are defined.

Line 1 specifies how long the script should wait for Windows Update to complete before aborting.  Earlier versions had this set to 20 minutes, then 30, then 45, increasing mostly to accommodate roll-up installation since compiling .NET images (aka the Ngen process) can take some time to complete.  You can set this to whatever you feel comfortable with, knowing that if you set it too short the script will abort when there may be nothing wrong with the server.  Line 2 is how long to wait for a server to reboot before assuming something is wrong and aborting.  Line 3 is how long to wait after a server has successfully rebooted before checking for service status.  In other words, how much time should be given before checking that all Exchange services have started.  This is used along with the value for Line 4 to determine the total time to wait for services to start.  If any service isn’t started by the time the value for Line 3 has initially passed, the script will sleep for those number of minutes and then check again, repeating until all services are started or the number of checks in Line 4 have been reached.  Line 5 is the path and name of the log file to record the screen output to for later review.

The last part of the variables section is for notification.

Lines 2 and 3 are self-explanatory.  Line 4 is a comma-separated list of addresses to send the script completion notice and transcript log.  It is cast as an array in case you specify only one recipient.  Line 5 indicates whether you want status updates to be sent, typically to a mobile device.  These updates are formatted with SMS recipients in mind, so they are to the point.  An status update is sent when a server is starting to be processed, when it has successfully rebooted and passes health checks, and when the script is complete or has aborted (including why it aborted).  This is so the person executing the script doesn’t have to keep an eye on the script to know its progress.  Line 6 is a comma-separated list of address to receive the status updates.  Again, it is cast as an array if you only have one recipient.  While it is geared for consumption by a mobile device via SMS, you can use any address you want.

This completes the variables section.  The next post will skip the functions section and delve into the script body, covering the functions as they are called.  Download the complete script:

  Reboot-ExchangeServers.zip (7.5 KiB)

Programmatically run Windows Update (as part of a broader patch and reboot process)

Articles in the "Programmatically Run Windows Update" series

  1. Programmatically run Windows Update (as part of a broader patch and reboot process) [This article]
  2. Update to script that runs Windows Update

Edit: This script has been updated and is explained here.

It’s been awhile since I have posted a script, so this is the first post of my process for patching and rebooting the Exchange servers at work. I needed a way to patch the environment while maintaining high availability and not resorting to just staggering reboots at, say, 30-minute intervals. The staggered approach doesn’t account for any issues that can occur if a server doesn’t come back up or services fail to start, etc. What I have done to maintain HA starts with this script.

I originally intended to have the Windows Update process as part of my bigger patch-and-reboot script, but I learned that you can’t make calls to the Windows Update API from remote systems. This means that I need to execute the script locally on each server, but my reboot script runs remotely from a management server. Therefore, this is just a standalone script for installing software updates from Windows Update (or, in my case, Microsoft Update, since that is installed). It is locally installed as an on-demand Scheduled Task that gets remotely called from my reboot script, but I will go into those details when I post about that script.

The first function in the script is for writing to the event log. This is how my reboot script monitors the progress. I create a custom event source so that there is no chance of conflicting with anything else. Any time something is to be written I pass the message, type (Information, Warning, etc.), and event ID.

The search criteria is for software updates (no drivers) that are not installed and are checked by default. If you want to control which updates to install, you can manipulate the search criteria a little bit (as documented) so only certain updates are returned, but to not install an update associated with a specific KB article you have to loop through the results and then not install the update that matches that property.

I use the WriteEvent function to write status updates to the event log so that my remote script can watch for events that indicate when it is done or that something has gone wrong. After the search results are downloaded, you still have to actually download them. Since I am not filtering any specific updates, I specify the entire $updates object to download.

When that is complete you loop through the collection and add them to a new installer object, which is finally used to install the updates.

Lastly, I loop through the results to log the name of every update that installed (or tried to install).

While you can have the script issue a reboot locally, I just log when the updates are complete so that my reboot script can trigger that and know precisely when that occurs. The entire script is below (since the code snippets above don’t include every line), but you can also download it.

  Run-WindowsUpdate.zip (1.8 KiB)