Updated PowerShell Script to Show Windows Update Settings

Mark Berry March 12, 2015

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.

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.

<#
.Synopsis
  Print the Microsoft auto-update settings for the local computer
  and optionally list pending updates.

  Copyright (c) 2016 by MCB Systems. All rights reserved.
  Free for personal or commercial use.  May not be sold.
  No warranties.  Use at your own risk.

.Notes 
    Name:       MCB.WindowsUpdate.ShowSettings.ps1
    Author:     Mark Berry, MCB Systems
    Created:    10/03/2014
    Last Edit:  08/04/2016

    Adapted from http://fixitscripts.com/problems/windows-update-settings.
    Also see https://technet.microsoft.com/en-us/library/ee692834.aspx
             http://powershellautomation.blogspot.com/2013/05/list-installed-pending-updates.html

    Changes:
    10/10/2014 - Reformat fixed output to be narrower.
    
    03/12/2015 - Rename script.  
                 Add RebootRequired and NoAutoRebootWithLoggedOnUsers info.
                 Add optional list of pending updates.  This takes 30+ seconds, so allow
                 suppressing by setting new $ShowPendingUpdates param to $false or 0.
                 
    04/08/2015 - When listing updates, include optional updates (remove "BrowseOnly=0" criterion).
                 Add BrowseOnly and IsDownloaded columns to update list. 
                 
                 Note that Windows Defender updates include bundled updates for the base engine etc.
                 so total size will be 400MB+.  Most of the "sub-updates" show IsInstalled=True but 
                 are still included in total size.  Getting true size would require looping through 
                 bundled updates, which I won't try to do now.
                 
    04/17/2015 - Edit note about optional updates.  Add note about Microsoft anti-virus update size.           

    11/12/2015 - Also list hidden updates if ShowPendingUpdates = $true.    
    
    02/17/2016 - Change default for $ShowPendingUpdates to $false. This lets script execute quickly
                 when run with no params.
               - For Windows 8/8.1/2012/2012R2, show start trigger of the Regular Maintenance task,
                 which controls when updates are installed.  No longer used in Windows 10.
                 
    08/04/2016 - When displaying pending updates, use Format-Table -AutoSize to show entire Title.
                 Add a column for UpdateID with RevisionNumber.
                 Output may now be up to 200 characters wide--you may want to copy to a text 
                 editor without line wrapping.
                 
    08/04/2016 - If the machine is configured to get updates from WSUS, identify the WUServer and 
                 WUStatusServer.  Otherwise indicated that WSUS is not configured.
#>

param(
  [Parameter(Mandatory = $false,
                    Position = 0,
                    ValueFromPipelineByPropertyName = $true)]
  [Boolean]$ShowPendingUpdates=$false,

  [Parameter(Mandatory = $false,
                    Position = 1,
                    ValueFromPipelineByPropertyName = $true)]
  [String]$LogFile=""
)

[Boolean]$ErrFound = $false

"Computer Name: " + $env:COMPUTERNAME
""
"Microsoft AutoUpdate settings"
# The output below starts with two line breaks, so omit the newline here
Write-Host -NoNewLine ("-----------------------------")

try {
  $objAutoUpdateSettings = (New-Object -ComObject "Microsoft.Update.AutoUpdate").Settings
  $objSysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo"
  $objAutoUpdateSettings
  "Reboot required               : " + $objSysInfo.RebootRequired
  
  # NoAutoReboot can apparently only be set by policy, so report that here.
  # Reference: https://technet.microsoft.com/en-us/library/cc720464%28v=ws.10%29.aspx.
  Write-Host -NoNewLine ("NoAutoRebootWithLoggedOnUsers : ")
  try { 
    # If Get-ItemProperty fails, value is not in registry. Do not fail entire script. 
    # "-ErrorAction Stop" forces it to catch even a non-terminating error.
    $output = Get-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoRebootWithLoggedOnUsers -ErrorAction Stop
    switch ($output.NoAutoRebootWithLoggedOnUsers)
    {
      0 {"False (set in registry)"}
      1 {"True (set in registry)"}
    }
  }
  catch { 
    "Unknown (local policy registry value not found)" 
  }

  # WSUS server info, if available
  # Reference: https://technet.microsoft.com/en-us/library/dd939844(v=ws.10).aspx
  ""
  Write-Host -NoNewLine ("WSUS Server               : ")
  try { 
    # If Get-ItemProperty fails, value is not in registry. Do not fail entire script. 
    # "-ErrorAction Stop" forces it to catch even a non-terminating error.
    $output = Get-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate -Name WUServer -ErrorAction Stop
    $output.WUServer
  }
  catch { 
    "WSUS not configured. The machine must be contacting Windows Update directly." 
  }
  Write-Host -NoNewLine ("WSUS Status Server        : ")
  try { 
    $output = Get-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WindowsUpdate -Name WUStatusServer -ErrorAction Stop
    $output.WUStatusServer
  }
  catch { 
    "WSUS not configured. The machine must be contacting Windows Update directly." 
  }

  # Static info on the meaning of various Settings.
  ""
  "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"
  "Note:  On Windows 8/8.1/2012/2012R2, ScheduledInstallationDay and"
  "       ScheduledInstallationTime are only reliable if the values" 
  "       are set through Group Policy."
  ""

# For Windows 8/8.1/2012/2012R2, show start trigger of the Regular Maintenance task
  
# Per http://stackoverflow.com/a/26003354/550712 .NET OS Version is inaccurate on an 
# upgraded Windows 8.1 machine, so use Get-CimInstance if available, else fall back to .NET.
# Cast as [Version] to allow accurate comparisons even when Version.Major is two digits (Windows 10).
[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") -and ($OSVersion -le [Version]"6.3.9600") ) {
  "Windows 8 / 8.1 / 2012 / 2012R2 scheduled maintenance"
  "-----------------------------------------------------"
  try {
    """\Microsoft\Windows\TaskScheduler\Regular Maintenance"" task trigger:"
    $task = Get-ScheduledTask –TaskName "Regular Maintenance" -TaskPath "\Microsoft\Windows\TaskScheduler\" -ErrorAction Stop
    $task.Triggers    
  }
  catch { 
    "Error:  Could not retrieve \Microsoft\Windows\TaskScheduler\Regular Maintenance task"
    ""
  }

# To change, use 
# $TaskTime = New-ScheduledTaskTrigger -At 12:00 -Daily  
# Set-ScheduledTask –TaskName "Regular Maintenance" -TaskPath "\Microsoft\Windows\TaskScheduler\" –Trigger $TaskTime -ErrorAction Stop
}

"Pending Software Updates including Hidden Updates"
"-------------------------------------------------"
  if ($ShowPendingUpdates) {
    #Get All Assigned updates in $SearchResult.
    $UpdateSession = New-Object -ComObject Microsoft.Update.Session
    $UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
    # Available search criteria:  https://msdn.microsoft.com/en-us/library/windows/desktop/aa386526%28v=vs.85%29.aspx
    # "BrowseOnly=0" omits updates that are considered "Optional" (e.g. Silverlight, hardware drivers).
    # As of 4/8/2015, omit "BrowseOnly=0" so we'll see all available updates.
    # Include "IsAssigned=1" to only see updates intended for deployment by Automatic Updates:
    #   $SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")
    # Omit "IsAssigned=1" to also see Recommended updates:
    $SearchResult = $UpdateSearcher.Search("IsInstalled=0")

    #Extract Results for type of updates that are needed.  For to be arrays so we can .count them.
    [Object[]] $Critical = $SearchResult.updates | where { $_.MsrcSeverity -eq "Critical" }
    [Object[]] $Important = $SearchResult.updates | where { $_.MsrcSeverity -eq "Important" }
    [Object[]] $Moderate = $SearchResult.updates | where { $_.MsrcSeverity -eq "Moderate" }
    [Object[]] $Low = $SearchResult.updates | where { $_.MsrcSeverity -eq "Low" }
    [Object[]] $Unspecified = $SearchResult.updates | where { $_.MsrcSeverity -eq "Unspecified" }
    [Object[]] $Other = $SearchResult.updates | where { $_.MsrcSeverity -eq $null }

    #Write Results
    "Critical    : $($Critical.count)"
    "Important   : $($Important.count)"
    "Moderate    : $($Moderate.count)"
    "Low         : $($Low.count)"
    "Unspecified : $($Unspecified.count)"
    "Other       : $($Other.count)"  
    "Total       : $($SearchResult.updates.count)"
    ""
    "Notes:  ""BrowseOnly"" updates are considered optional."
    "         Microsoft anti-virus updates include sub-updates"
    "         that are already installed, so size is inaccurate."
    # "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/
    
    ""
    "Ready to Install"
    "----------------"
    $NotHiddenUpdates = $SearchResult.updates | Where-Object {$_.IsHidden -eq $false}
    If ($NotHiddenUpdates -eq $null) { 
      "None" 
    } else {
      $NotHiddenUpdates | Sort-Object MsrcSeverity, Title | `
        Format-Table -AutoSize @{Expression={if ($_.MsrcSeverity -eq $null) {"Other"} else {$_.MsrcSeverity} };Label="Severity"}, `
        @{Expression={$_.Title};Label="Title"}, `
        @{Expression={"{" + $_.Identity.UpdateID + "}." + $_.Identity.RevisionNumber};Label="UpdateID and RevisionNumber"}, `
        @{Expression={$_.BrowseOnly};Label="BrowseOnly"}, `
        @{Expression={$_.IsDownloaded};Label="IsDownloaded"}, `
        @{Expression={"{0:N1} MB" -f ($_.MaxDownloadSize / 1MB) };Label="MaxDownload";align="right"} | `
        Out-String -Width 200 
    }
    ""
    "Hidden Updates"
    "--------------"
    $HiddenUpdates = $SearchResult.updates | Where-Object {$_.IsHidden -eq $true}
    If ($HiddenUpdates -eq $null) { 
      "None" 
    } else {
      $HiddenUpdates | Sort-Object MsrcSeverity, Title | `
        Format-Table -AutoSize @{Expression={if ($_.MsrcSeverity -eq $null) {"Other"} else {$_.MsrcSeverity} };Label="Severity"}, `
        @{Expression={$_.Title};Label="Title"}, `
        @{Expression={"{" + $_.Identity.UpdateID + "}." + $_.Identity.RevisionNumber};Label="UpdateID and RevisionNumber"}, `
        @{Expression={$_.BrowseOnly};Label="BrowseOnly"}, `
        @{Expression={$_.IsDownloaded};Label="IsDownloaded"}, `
        @{Expression={"{0:N1} MB" -f ($_.MaxDownloadSize / 1MB) };Label="MaxDownload";align="right"} | `
        Out-String -Width 200
    }
  } else {
    "The ShowPendingUpdates parameter is False, so pending updates not listed."
    ""
  } # if $ShowPendingUpdates
  
  "Script execution succeeded"
  $ExitCode = 0
} # try
catch {
  ""
  $error[0]
  ""
  "Script execution failed"
  $ExitCode = 1001 # Cause script to report failure in MaxFocus RM dashboard
}

""
"Local Machine Time:  " + (Get-Date -Format G)
"Exit Code: " + $ExitCode
Exit $ExitCode


7 Comments

  1. PowerShell Script to Change Windows Update Settings | MCB Systems   |  November 12, 2015 at 4:54 pm

    […] already posted a script to show Windows Update settings. What if you want to change the settings? In particular, I […]

  2. Print Detailed Windows Update Information | MCB Systems   |  December 18, 2015 at 7:33 pm

    […] this year, I wrote about how to show and change Windows Update settings on a machine using PowerShell. But sometimes you need to know […]

  3. Brachus   |  February 29, 2016 at 6:25 am

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

  4. Mark Berry   |  February 29, 2016 at 9:11 am

    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   |  May 31, 2016 at 9:55 am

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

  6. Mark Berry   |  May 31, 2016 at 10:37 am

    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. Windows 10 Does Not Show or Install Optional Updates | MCB Systems   |  August 04, 2016 at 1:57 pm

    […] see if your machine is affected, save the PowerShell script from this blog post and run it as […]

Leave a Reply





*