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
}