Windows PowerShell 4.0 – Get-HotFixIDStatus

Recently a client had a need for a way to check if a list of computers had a certain Microsoft HotFix without having to manually logon to each one and check. PowerShell to the rescue

For this script I have implemented several new things I have learned since my last post and have refined other processes


###############################################################################
####                                                                       ####
####                                                                       ####
####                          Get-HotFixIDStatus                           ####
####                            Version 3.1.2                              ####
####                       Created by: Liam Matthews                       ####
####                                                                       ####
####                                                                       ####
###############################################################################

################################Release notes##################################
####                                                                       ####
################################### 1.0.0 #####################################
#### - Initial release                                                     ####
####                                                                       ####
################################### 2.0.0 #####################################
#### - Added help context for PowerShell                                   ####
#### - Added input parameters                                              ####
#### - Modified to allow for single computer or list from txt file         ####
#### - Modified output function to be optional                             ####
#### - Created custom WMI function with timeout to fix hanging             ####
#### - Added error handling and output                                     ####
####                                                                       ####
################################### 2.1.0 #####################################
#### - Moved error handling to function                                    ####
#### - Fixed all updates showing as false                                  ####
#### - Additional queries no longer run if first one failed                ####
####                                                                       ####
################################### 2.2.0 #####################################
#### - Made global error variable to fix errors not outputting             ####
#### - Added shortened error messages                                      ####
#### - Added function to test connection to device                         ####
#### - Increased WMI query timeout to 60 seconds                           ####
####                                                                       ####
################################### 2.2.1 #####################################
#### - Added additional error messages for detection                       ####
####                                                                       ####
################################### 3.0.0 #####################################
#### - Added Support for logging                                           ####
#### - Added error handling for file import                                ####
#### - Change screen output to only occur if Output argument was not used  ####
####                                                                       ####
################################### 3.1.0 #####################################
#### - Added debug text                                                    ####
####                                                                       ####
################################### 3.1.1 #####################################
#### - Changed formatting                                                  ####
#### - fixed a bug causing multiple logs being written for the same error  ####
################################### 3.1.1 #####################################
#### - Removed positional requirement for output and log arguments         ####
####                                                                       ####
###############################################################################

<#
.SYNOPSIS
    Get-HotFixIDStatus Status retrieves all collections where the specified device has a membership

.DESCRIPTION
    Get-HotFixIDStatus Status retrieves all collections where the specified device has a membership

.PARAMETER HotFixID
    The HotFix humber as provided by Microsoft

    Example: KB1234567
    Example: 1234567

.PARAMETER Computer
    The name of the computer to fetch the information from

    Example: Client01

.PARAMETER ComputerList
    TXT file to fetch computer names from if more than one

    Example: List.txt

.PARAMETER Output
    Outputs the data to a html file in the script launch folder

.PARAMETER Log
    Outputs script running data to log file

.EXAMPLE
    Get-HotFixIDStatus -HotFixID KB1234567 -ComputerName Client01

    ComputerName                                           KB1234567 Installed                                    OperatingSystem
    ----                                                   -------------------                                    ---------------
    Client01                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition           

    The above command checks if the specified HotFixID is installed on the computer 

.EXAMPLE
    Get-HotFixIDStatus -HotFixID KB1234567 -ComputerList Server.txt -Output

    ComputerName                                           KB1234567 Installed                                    OperatingSystem
    ----                                                   -------------------                                    ---------------
    Client01                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client02                                               True                                                   Microsoft Windows Server 2003 R2 Enterprise Edition
    Client03                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition
    Client04                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client05                                               False                                                  Microsoft Windows Server 2003 R2 Enterprise Edition
    Client06                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client07                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition
    Client08                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition           

    The above command checks if the specified HotFixID is installed on the list of computers contained in a text file and outputs the results to a html file in the script launch folder

.NOTES
    Version 3.1.2
    Created by Liam Matthews
#>

#
[CmdletBinding(DefaultParameterSetName="ComputerName")]
Param
(
    # Argument: HotFixID
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
    [String]$HotFixID,

    # Argument: ComputerList
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="ComputerList",Position=1)]
    [String]$ComputerList,

    # Argument: ComputerName
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="ComputerName",Position=1)]
    [String]$ComputerName = "localhost",

    # Argument: Output
    [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
    [Switch]$Output,

    # Argument: Log
    [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
    [Switch]$Log
)

#Declaring initial variables
$ScriptInfo = Get-PSCallStack
$StartTime = Get-Date -Format "HH:mm:ss.fff"
$StartDate = Get-Date -Format "MM-dd-yyyy"
$StartDateTime = Get-Date -Format "HHmmss"
$TZbias = (Get-WmiObject -Query "Select Bias from Win32_TimeZone").bias

#Formatting HotFixID variable
$HotFixID = $HotFixID.Replace("KB","")

#Funtion for outputting data to log file
Function Output-Log([string]$LogText,[int]$Type = "1",[string]$Component = $ScriptInfo.Command,[string]$Context = "KB$HotFixID",[string]$OutputFile = "KB$HotFixID.log")
{
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($Component)`">"

    #Write to log
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$OutputFile"
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "+++Starting new thread+++"
}
IF ($Log)
{
    Output-log -LogText "Script loaded from: $($ScriptInfo.ScriptName)"
}
IF ($Log)
{
    Output-log -LogText "Script loaded with arguments: $($ScriptInfo.Arguments)"
}
IF ($Log)
{
    Output-log -LogText "Script loaded by: $env:USERNAME" -Type "1"
}
IF ($Log)
{
    Output-log -LogText "Preloading functions" -Type "1"
}

#Function for
Function Get-WMICustom([string]$ComputerName,[string]$Namespace = "root\cimv2",[string]$Class,[string]$Filter = "",[int]$Timeout = 60)
{
    #Clear error queue
    $Error.Clear()
    #Set default error action
    $ErrorActionPreference = "Stop"

    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Running function: Get-WMICustom with arguments: -ComputerName $ComputerName -Namespace $Namespace -class $Class -Filder $Filter -Timeout $Timeout"
    }

    #Attempting scriptblock
    Try
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Building query block"
        }

        #Building query block
        $ConnectionOptions = New-Object System.Management.ConnectionOptions
        $EnumerationOptions = New-Object System.Management.EnumerationOptions
        $TimeoutSeconds = New-Timespan -seconds $Timeout
        $EnumerationOptions.set_timeout($timeoutseconds)
        $AssembledPath = "\\" + $ComputerName + "\" + $Namespace
        $Scope = New-Object System.Management.ManagementScope $AssembledPath, $ConnectionOptions
        $Scope.Connect()
        $QueryString = "SELECT * FROM " + $Class

        IF($Filter.Length -gt 0)
        {
            $QueryString += " WHERE " + $Filter
        }

        $Query = New-Object System.Management.ObjectQuery $QueryString
        $Searcher = New-Object System.Management.ManagementObjectSearcher
        $Searcher.set_options($EnumerationOptions)
        $Searcher.Query = $QueryString
        $Searcher.Scope = $Scope

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Running query"
        }

        #Run query
        Trap { $_ } $Result = $Searcher.get()

        #Return results
        Return $Result
    }
    #Action to take on error
    Catch
    {
        #Action if access denied error
        IF ($_.Exception.Message -match "Access is denied")
        {
            $Global:ErrorMessage = "Access denied"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Access Denied" -Type 2
            }
        }

        #Action if time out error
        IF ($_.Exception.Message -match "Timed Out")
        {
            $Global:ErrorMessage = "Timed out while connecting to target"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Timed out while connecting to target" -Type 2
            }
        }

        #Action if insufficient memory error
        IF ($_.Exception.Message -match "Insufficient memory to continue the execution")
        {
            $Global:ErrorMessage = "Insufficient memory on target"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Insufficient memory on target" -Type 2
            }
        }

        #Action if RPC server busy error
        IF ($_.Exception.Message -match "The RPC server is too busy to complete this operation")
        {
            $Global:ErrorMessage = "RPC server busy"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: RPC server busy" -Type 2
            }
        }

        #Action if error is unknown
        IF
        (
            $_.Exception.Message -notmatch "Access is denied" -and,
            $_.Exception.Message -notmatch "Timed Out" -and,
            $_.Exception.Message -notmatch "Insufficient memory to continue the execution" -and,
            $_.Exception.Message -notmatch "The RPC server is too busy to complete this operation"
        )
        {
            $Global:ErrorMessage = $_.Exception.Message
        }
    }
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "Get-WMICustom function loaded"
}

Function Get-DeviceOnline([string]$ComputerName,[int]$Count = 1)
{
    #Clear error queue
    $Error.Clear()
    #Set default error action
    $ErrorActionPreference = "Stop"

    #Attempting scriptblock
    Try
    {

        #Pinging target
        $Ping = Test-Connection -ComputerName $ComputerName -Count $Count

        #Returning results
        Return $Ping

    }
    #Action to take on error
    Catch
    {
        #Setting error output
        $Global:ErrorMessage = "Device offline"
    }
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "Get-DeviceOnline function loaded"
}

#Clearing results variable from any previous use
$Results = @()

#Action if ComputerList argument defined
IF ($ComputerList)
{

    #Attempting scriptblock
    Try
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Loading computers from defined list: $ComputerList"
        }

        #Reading file
        $ComputerNames = Get-Content $ComputerList -ErrorAction Stop
    }

    #Action of error
    Catch
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Failed to load file" -Type 3
        }
        IF ($Log)
        {
            Output-log -LogText $_.Exception.Message -Type 3
        }
        IF ($Log)
        {
            Output-log -LogText "Script Terminating" -Type 3
        }
        Exit
    }

    #Loop to run for each computer in list
    ForEach ($ComputerName in $ComputerNames)
    {
        #Displaying progress bar
        $PercentComplete = ($Results.Count)/($ComputerNames.Count)*100
        $PercentComplete = [math]::Round($PercentComplete)
        Write-Progress -Activity "Gathering information" -Status "$PercentComplete%: $ComputerName" -PercentComplete $PercentComplete

        #Clearing variables
        $DeviceName = ""
        $HotFixIDExists = ""
        $FullOSName = ""
        $ShortOSName = ""
        $Global:ErrorMessage = ""

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Testing if $ComputerName is online"
        }

        $DeviceOnline = Get-DeviceOnline -ComputerName $ComputerName

        #Output current status to log
        IF ($Log -and $DeviceOnline)
        {
            Output-log -LogText "$ComputerName is responding to ping"}ELSE{Output-log -LogText "$ComputerName is not responding to ping. Skipping" -Type "2" | Continue
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and fetching computer name"
            }
        }

        #Connecting to WMI and fetching computer name
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $DeviceName = (Get-WMICustom -ComputerName $ComputerName -Class "Win32_ComputerSystem").Name
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Successfully fetched $DeviceName from WMI"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and fetching Operating System"
            }
        }

        #Connecting to WMI and fetching Operating System
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $FullOSName = Get-WMICustom -ComputerName $ComputerName -Class "Win32_OperatingSystem"
        }
        IF ($Global:ErrorMessage)
        {
            $ShortOSName = "N/A"
        }
        ELSE
        {
            $ShortOSName = ($FullOSName.Name.Split("|"))[0]
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($WMIErrorOutput)
            {

            }
            ELSE
            {
                IF ($Log)
                {
                    Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                    $WMIErrorOutput = $true
                }
            }
        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Successfully fetched $ShortOSName from WMI"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and searching for KB$HotFixID"
            }
        }

        #Connecting to WMI and searching for hotfix
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $HotFixIDSearch = Get-WMICustom -ComputerName $ComputerName -Class "Win32_QuickFixEngineering" -Filter "HotFixID = 'KB$HotFixID'"
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($WMIErrorOutput)
            {

            }
            ELSE
            {
                IF ($Log)
                {
                    Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                    $WMIErrorOutput = $true
                }
            }
        }
        ELSE
        {
            IF ($Log -and $HotFixIDSearch.HotFixID -eq "KB$HotFixID")
            {
                Output-log -LogText "Successfully connected to WMI and located KB$KBHotFixID"
            }
            ELSE
            {
                Output-log -LogText "Successfully connected to WMI but was unable to locate KB$KBHotFixID"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Finished connecting to WMI with no errors"
            }
        }

        #Defining variables on error
        IF ($Global:ErrorMessage)
        {
            $DeviceName = $ComputerName + ": " + $ErrorMessage
        }
        IF ($HotFixIDSearch.HotFixID -eq "KB$HotFixID")
        {
            $HotFixIDExists = "True"
        }
        ELSE
        {
            $HotFixIDExists = "False"
        }
        IF ($Global:ErrorMessage)
        {
            $HotFixIDExists = "N/A"
        }

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Returning results"
        }

        #Building table
        $Results += New-Object PSObject -Property @{
            "ComputerName" = $DeviceName
            "OperatingSystem" = $ShortOSName
            "KB$HotFixID Installed" = $HotFixIDExists
        }
    }
}
ELSE
{
    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Testing if $ComputerName is online"
    }

    $DeviceOnline = Get-DeviceOnline -ComputerName $ComputerName

    #Output current status to log
    IF ($Log -and $DeviceOnline)
    {
        Output-log -LogText "$ComputerName is responding to ping"}ELSE{Output-log -LogText "$ComputerName is not responding to ping. Skipping" -Type "2" | Continue
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and fetching computer name"
        }
    }

    #Connecting to WMI and fetching computer name
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $DeviceName = (Get-WMICustom -ComputerName $ComputerName -Class "Win32_ComputerSystem").Name
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($Log)
        {
            Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
            $WMIErrorOutput = $true
        }
    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Successfully fetched $DeviceName from WMI"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and fetching Operating System"
        }
    }

    #Connecting to WMI and fetching Operating System
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $FullOSName = Get-WMICustom -ComputerName $ComputerName -Class "Win32_OperatingSystem"
    }
    IF ($Global:ErrorMessage)
    {
        $ShortOSName = "N/A"
    }
    ELSE
    {
        $ShortOSName = ($FullOSName.Name.Split("|"))[0]
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($WMIErrorOutput)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Successfully fetched $ShortOSName from WMI"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and searching for KB$HotFixID"
        }
    }

    #Connecting to WMI and searching for hotfix
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $HotFixIDSearch = Get-WMICustom -ComputerName $ComputerName -Class "Win32_QuickFixEngineering" -Filter "HotFixID = 'KB$HotFixID'"
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($WMIErrorOutput)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
    }
    ELSE
    {
        IF ($Log -and $HotFixIDSearch.HotFixID -eq "KB$HotFixID")
        {
            Output-log -LogText "Successfully connected to WMI and located KB$KBHotFixID"
        }
        ELSE
        {
            Output-log -LogText "Successfully connected to WMI but was unable to locate KB$KBHotFixID"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Finished connecting to WMI with no errors"
        }
    }

    #Defining variables on error
    IF ($Global:ErrorMessage)
    {
        $DeviceName = $ComputerName + ": " + $ErrorMessage
    }
    IF ($HotFixIDSearch.HotFixID -eq "KB$HotFixID")
    {
        $HotFixIDExists = "True"
    }
    ELSE
    {
        $HotFixIDExists = "False"
    }
    IF ($Global:ErrorMessage)
    {
        $HotFixIDExists = "N/A"
    }

    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Returning results"
    }

    #Building table
    $Results += New-Object PSObject -Property @{
        "ComputerName" = $DeviceName
        "OperatingSystem" = $ShortOSName
        "KB$HotFixID Installed" = $HotFixIDExists
    }
}

#Output current status to log
IF ($Log -and $Output)
{
    Output-log -LogText "Saving results to KB$HotFixID.html"
}

#Output to file or screen
IF ($Output)
{
    $Results | Sort-Object ComputerName,
    @{
        expression="ComputerName";Descending=$false
    } | ConvertTo-Html -Head $Header | Out-File KB$HotFixID.html
}
ELSE
{
    $Results
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s