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 }