Updated PowerShell Script to Show Windows Update Settings

Last year in this article, I posted a PowerShell script to display Windows Update settings. I’ve enhanced that script to show two additional values and to optionally list all pending updates.

By default, the script will now list pending updates. Updates considered Optional (e.g. Silverlight, hardware drivers, etc.) are excluded. If you want to list pending updates, including updates considered Optional, add a $true parameter.

Update 12 November 2015

The script now lists hidden updates as well as updates that are waiting to be installed.

Update 4 August 2016

  • The script now defaults to $false, i.e. it will not list pending updates unless you run it with $true. This allows it to complete very quickly by default.
  • If the machine is configured to gets updates from and report status to WSUS, the relevant servers are listed.
  • Windows 8/8.1/2012/2012R2 machines show task trigger information for the Regular Maintenance task (during which updates are installed).
  • If you choose to list pending updates (run with $true), the full Title will now be shown, and UpdateID and RevisionNumber are included. This means each row can be up to 200 characters long. For best results, stretch your PowerShell window to make it wide enough.

Update 26 February 2018

Retrieve and display all WindowsUpdate registry values to cover Windows 10 as well.

The Results

You can run this script directly in PowerShell, but if you deploy it as a Daily Script Check in MaxRM, you’ll be able to quickly see the Windows Update settings and pending updates on any of your machines. (Tip:  use the Re-run Checks option to re-run the daily checks at any time.)

Note Listing updates can take 30 seconds or longer, so if you are running this from MaxRM, set the script timeout to 60 or even 120 seconds. On slow machines, you may have to run it as a scheduled task rather than as a DSC check.

Once deployed through MaxRM, you’ll see output like this in the dashboard:

Show Windows Updates 1

If you ran the script with a $true parameter, scroll down to see the pending updates:

Show Windows Updates 2

Note that the pending updates are actually listed in a multi-column table (Severity, Title, etc.). MaxRM apparently removes extra white space when copying the output to the dashboard, but you can still read the output pretty easily.

The Script

And here is the script.

11 thoughts on “Updated PowerShell Script to Show Windows Update Settings

  1. Pingback: PowerShell Script to Change Windows Update Settings | MCB Systems

  2. Pingback: Print Detailed Windows Update Information | MCB Systems

  3. Brachus

    This does not work correctly for any domain joined machines. It does not show any of the GPO based settings.

  4. Mark Berry Post author

    Brachus, thanks for the feedback. What OS are you using? The results should be accurate if not complete for domain machines. For example, if your GPO sets Windows Update to “2 – Notify…”, you should see that in this script, but you won’t see that the user is blocked from changing that setting. What exactly where you hoping to see?

  5. david

    how to you run this for a list of computers,
    I keep getting pipeline failures.

  6. Mark Berry Post author

    David, the script only shows information about the machine where it is run. I use a monitoring tool to deploy it on multiple machines.

  7. Pingback: Windows 10 Does Not Show or Install Optional Updates | MCB Systems

  8. Andres Correa

    Hi Mark!

    Your script reference a $LogFile parameter, but that is not really used.
    Do you have a version that includes its usage?
    It would be very usefull.

    Thanks in advance for your attention.

  9. Mark Berry Post author

    Andres – you’re right, I don’t use the $LogFile parameter.. That parameter is only used when the script is deployed and called from MaxRM. Per https://dashboard.systemmonitor.us/dashboard/helpcontents/script_guide_parse.htm, “When the Agent runs the script two arguments are always appended to the command line (for debug) and the -logfile logname can be ignored.” Put another way, if you DON’T have the $LogFile param and you call it from MaxRM, it will pass the unexpected param and the script will fail.

  10. Bruce E. Kriebel

    Mark,

    Nice piece of code. I did a little work on it to fit it to my needs to output a single string to return to another program. I thought you might find some of the changes useful.

    -------- Code  --------
    Function WinUpdSettings {
    
    
    
      
      param(
        [Parameter(Mandatory = $false,
                          Position = 0,
                          ValueFromPipelineByPropertyName = $true)]
        [Boolean]$ShowPendingUpdates=$True
      )
      
      function ListRegKeyValues {
        param(
          [Parameter(Mandatory=$true)]
          [string]$path)
      
        $RegStr = "Hive: $path`n" 
      		
        $RegKey = (Get-ItemProperty $path)
        $RegKey.PSObject.Properties | ForEach-Object {
      	  $Name = $_.Name
      		$Value = $_.Value
      	  switch ($Name) {
      		  # Don't print the PowerShell properties that are 
                # apparently present in every PSObject
      		    "PSPath"       { break }
      			"PSParentPath" { break }
      			"PSChildName"  { break }
      			"PSDrive"      { break }
      			"PSProvider"   { break }
      			default {$RegStr +=  "$Name :  $Value"}
          }
        }	
      
        "$RegStr"
      
      }  #End Function ListRegKeyValues
      
      #---------------   Main Program -------------------
      
      Clear-Host	
      
      [Boolean]$ErrFound = $false
      
      $TStr = @"
    Computer Name: $env:COMPUTERNAME
      
    Microsoft AutoUpdate settings (from the Microsoft.Update COM object)
    --------------------------------------------------------------------
    "@ | Out-String
      
      Try {
        $objAutoUpdateSettings = 
        (New-Object -ComObject "Microsoft.Update.AutoUpdate").Settings
        $objSysInfo = 
          New-Object -ComObject "Microsoft.Update.SystemInfo"
      
      	# See https://stackoverflow.com/a/32253197/550712 for the 
          # Out-String.Trim() trick to trim blank lines
         $TStr += ($objAutoUpdateSettings | Out-String).Trim()
         $TStr += "`nReboot required           : " +
                  "$($objSysInfo.RebootRequired)" | Out-String
      
          $path1 = "HKLM:\Software\Policies\Microsoft\Windows" +
                   "\WindowsUpdate"
          $Key1  =  ListRegKeyValues "$path1"
          $Key2  =  ListRegKeyValues "$path1\AU"
      
      $TStr += @"
      
    Registry values (set by Group Policy or manually)
    -------------------------------------------------
    $key1
      
    $Key2
      
    "@ | Out-String
      	
      
      
        $Title = "WSUS Server       : "
      
        Try { 
          
      
          $GIPArgs = @{Path        = "HKLM:\Software\Policies\" + 
                                     "Microsoft\Windows\WindowsUpdate"
                       Name        = "WUServer" 
                       ErrorAction = "Stop"}
          $output = Get-ItemProperty @GIPArgs
      
          $WsusStr = "$Title" + $output.WUServer
        }
        Catch { 
          $WsusStr ="$Title" + 
          "WSUS not configured.`n`t`t`t`t`tThe machine" +
          " must be contacting `n`t`t`t`t`tWindows Update directly." 
        }
      
        $Title ="`n`nWSUS Status Server: "
      
        Try { 
          $GIPArgs = @{Path        = "HKLM:\Software\Policies\" +
                                     "Microsoft\Windows\WindowsUpdate"
                       Name        = "WUStatusServer" 
                       ErrorAction = "Stop"}
          $output = Get-ItemProperty @GIPArgs
          $WsusStr += "$Title" + $output.WUStatusServer
        }
        Catch { 
          $WsusStr += "$Title" + 
          "WSUS not configured.`n`t`t`t`t`tThe machine " +
          "must be contacting `n`t`t`t`t`tWindows Update directly." 
        }
      
        $TStr += $WsusStr | Out-String
      
        # Static info on the meaning of various Settings.
      $TStr += @"
        
        Key to NotIfication Values
        --------------------------
        NotIficationLevel:
        1 - Never check for updates
        2 - Check for updates but let me choose whether to download
            and install them
        3 - Download updates but let me choose whether to install them
        4 - Install updates automatically
        
        ScheduledInstallationDay:
          0 - Every day
        1-7 - Sunday through Saturday
        
        For Windows 10, you should also see: 
           ScheduledInstallEveryWeek = 1 and/or 
           ScheduledInstallFirstWeek = 1, etc.
        
    "@ | Out-String
      
      
      
      [Version]$OSVersion = $null
      
      Try {
        $OSVersion = (Get-CimInstance Win32_OperatingSystem).Version
      }
      Catch {
        # Get-CimInstance requires PowerShell 3. 
        # Fall back to .NET If Get-CimInstance doesn't work.
      
        $OSVersion = [System.Environment]::OSVersion.Version
      }
      
      # List of Windows versions:  http://www.robvanderwoude.com/ver.php
      
      If ( ($OSVersion -ge [Version]"6.2.9200") ) {
      
      $TStr += @"  
    Windows 8 and Later Scheduled Maintenance, If Any
    -------------------------------------------------
    \Microsoft\Windows\TaskScheduler\Regular Maintenance task trigger:
    "@ | Out-String
      
        Try {
          $GSTArgs - @{TaskName = "Regular Maintenance" 
                       TaskPath = "\Microsoft\Windows\TaskScheduler\" 
                       ErrorAction =  "Stop"}
          $task = Get-ScheduledTask 
          $TStr =+ $task.Triggers  | Out-String 
        }
      
        Catch { 
          $TStr += "Error: Could not retrieve`n`t`t\Microsoft\" +
                   "\Windows\TaskSchedulerRegular Maintenance task`n"
        }
      
      # To change, use 
      # $TaskTime = New-ScheduledTaskTrigger -At 12:00 -Daily  
      # Set-ScheduledTask -TaskName "Regular Maintenance" -TaskPath "\Microsoft\Windows\TaskScheduler\" -Trigger $TaskTime -ErrorAction Stop
      
      } #End If( $OSVersion
      
      $TStr += @"
      
    Pending Software Updates including Hidden Updates
    -------------------------------------------------
    "@ | Out-String
      
        If ($ShowPendingUpdates) {
          #Get All Assigned updates in $SearchResult.
          $UpdateSession  = 
                New-Object  -ComObject Microsoft.Update.Session
          $UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
          $SearchResult   = $UpdateSearcher.Search("IsInstalled=0")
      
      
          $Sev = "Critical","Important","Moderate","Low",
                 "Unspecified",$null
          $a   = $null,$null,$null,$null,$null,$null
      
          For ($cnt = 0; $cnt -lt $Sev.Count; $cnt++) {
      
             [Object[]] $a[$cnt] = $SearchResult.updates | 
                     Where { $_.MsrcSeverity -eq $($Sev[$cnt]) }
             
          } #End For ($cnt...
      
          #Write Results
          $updTotal = 0
          #Fix $Sev $Null Severity to Other for output!
          $Sev[$Sev.Count -1] = "Other"
      
          For ($cnt = 0; $cnt -lt $Sev.Count; $cnt++) {
             If ($Null -ne $a[$cnt]) {
               $TStr += "{0,-11} : {1,2}" -f 
                        $($Sev[$cnt]),$($a[$cnt].count) | Out-String
               $UpdTotal += $a[$cnt].count
             }
             Else {$TStr += "{0,-11} : {1,2}" -f $($Sev[$cnt]),0 |
                   Out-String}
          } 
                         
          $TStr += "{0,-11} : {1,2}" -f "Total",$updTotal  | Out-String
                               
      $TStr += @"
      
      Notes:  BrowseOnly updates are considered optional.
              Microsoft anti-virus updates include sub-updates
              that are already installed, so size is inaccurate.
      
    Ready to Install
    ---------------- 
    "@ | Out-String
      
          # "If" statement in Expression: 
          #   http://blogs.technet.com/b/josebda/archive/2014/04/19/powershell-tips-for-building-objects-with-custom-properties-and-special-formatting.aspx
          # Formatting number as MB:  https://technet.microsoft.com/en-us/library/ff730948.aspx
          # Available update properties (IUpdate interface):  https://msdn.microsoft.com/en-us/library/windows/desktop/aa386099(v=vs.85).aspx
          # Use Out-String to keep AutoSize from truncating columns based on screen size:
          #   https://poshoholic.com/2010/11/11/powershell-quick-tip-creating-wide-tables-with-powershell/
      
          $NotHiddenUpdates = $SearchResult.updates | 
              Where-Object {$_.IsHidden -eq $false}
      
          If ($null -eq $NotHiddenUpdates) { 
            $TStr += "None" | Out-String
          } 
          Else {
            $FmtUpdts = 
              @{Expression={If ($_.MsrcSeverity -eq $null) {"Other"} 
                            Else {$_.MsrcSeverity} };
                            Label="`nSeverity";Width=8}, 
              @{Expression={$_.BrowseOnly};Label="Browse`nOnly";
                            Width=6;Align="Left"},
              @{Expression={$_.IsDownloaded};Label="IsDown`nloaded";
                            Width=6;Align="Left"}, 
              @{Expression={"{0:N1} MB" -f ($_.MaxDownloadSize / 1MB) };
                            Label="Max D/L`nSize";
                            Width=8;Align="Right"},
              @{Expression={$_.Title};Label="`nTitle";Width=45}
       
            $TStr += $NotHiddenUpdates | 
                     Sort-Object MsrcSeverity, Title | 
                     Format-Table -Property $FmtUpdts -Wrap | 
                     Out-String -Width 80
      
          } #End Else
      
      $TStr += @"
      
    Hidden Updates
    --------------
    "@ | Out-String
      
          $HiddenUpdates = $SearchResult.updates | 
               Where-Object {$_.IsHidden -eq $true}
      
          If ($HiddenUpdates -eq $null) { 
            $TStr += "None"  | Out-String
          } 
          Else {
            $TStr += $HiddenUpdates | 
                    Sort-Object MsrcSeverity, Title | 
                    Format-Table -Property $FmtUpdts -Wrap | 
                    Out-String -Width 80
          }
        }  #End If ($ShowPendingUpdates) 
      
        Else {
          $TStr += "The ShowPendingUpdates parameter is `$false," +
                   " so pending updates not listed.`n" | Out-String
         
        } # End Else $ShowPendingUpdates
      	
        $TStr += "`nScript execution succeeded" | Out-String
        $ExitCode = 0
      
      } # Try
      
      Catch {
        
        $TStr += "$error[0]" | Out-String
        $TStr += "`nScript execution failed" | Out-String
        $ExitCode = 1001 # Cause script to report failure in MaxFocus RM dashboard
      }
      
      
      $TStr += "`nLocal Machine Time:  " + (Get-Date -Format G) +
               "`nExit Code: " + $ExitCode | Out-String
      
      "$TStr"
    
    } #End WinUpdSettings
    -------End Code------
  11. Mark Berry Post author

    Thanks Bruce. I wrapped the code in a “pre” tag. Not pretty in the comment but I think if anyone wants to cut and paste, it will retain spacing.

Leave a Reply

Your email address will not be published. Required fields are marked *

Notify me of followup comments via e-mail. You can also subscribe without commenting.