I am trying to check if the specified KB # that I have set in my variables list matches the full list of KB installed patches on the server. If it matches, it will display that the patch is installed, otherwise it will state that it is not installed.
The code below does not seem to work, as it is showing as not installed, but in fact it's already been installed.
[CmdletBinding()]
param ( [Parameter(Mandatory=$true)][string] $EnvRegion )
if ($EnvRegion -eq "kofax"){
[array]$Computers = "wprdkofx105",
"wprdkofx106",
"wprdkofx107",
$KBList = "KB4507448",
"KB4507457",
"KB4504418"
}
elseif ($EnvRegion -eq "citrix"){
[array]$Computers = "wprdctxw124",
"wprdctxw125",
$KBList = "KB4503276",
"KB4503290",
"KB4503259",
"KB4503308"
}
### Checks LastBootUpTime for each server
function uptime {
gwmi win32_operatingsystem | Select
@{LABEL='LastBootUpTime';EXPRESSION=
{$_.ConverttoDateTime($_.lastbootuptime)}} | ft -AutoSize
}
### Main script starts here. Loops through all servers to check if
### hotfixes have been installed and server last reboot time
foreach ($c in $Computers) {
Write-Host "Server $c" -ForegroundColor Cyan
### Checks KB Installed Patches for CSIRT to see if patches have been
### installed on each server
foreach ($elem in $KBList) {
$InstalledKBList = Get-Wmiobject -class Win32_QuickFixEngineering -
namespace "root\cimv2" | where-object{$_.HotFixID -eq $elem} |
select-object -Property HotFixID | Out-String
if ($InstalledKBList -match $elem) {
Write-Host "$elem is installed" -ForegroundColor Green
}
else {
Write-Host "$elem is not installed" -ForegroundColor Red
}
}
Write-Host "-------------------------------------------"
Invoke-Command -ComputerName $c -ScriptBlock ${Function:uptime}
}
Read-Host -Prompt "Press any key to exit..."
As Kostia already explained, the Win32_QuickFixEngineering
does NOT retrieve all updates and patches. To get these, I would use a helper function that also gets the Windows Updates and returns them all as string array like below:
function Get-UpdateId {
[CmdletBinding()]
Param (
[string]$ComputerName = $env:COMPUTERNAME
)
# First get the Windows HotFix history as array of 'KB' id's
Write-Verbose "Retrieving Windows HotFix history on '$ComputerName'.."
$result = Get-HotFix -ComputerName $ComputerName | Select-Object -ExpandProperty HotFixID
# or use:
# $hotfix = Get-WmiobjectGet-WmiObject -Namespace 'root\cimv2' -Class Win32_QuickFixEngineering -ComputerName $ComputerName | Select-Object -ExpandProperty HotFixID
# Next get the Windows Update history
Write-Verbose "Retrieving Windows Update history on '$ComputerName'.."
if ($ComputerName -eq $env:COMPUTERNAME) {
# Local computer
$updateSession = New-Object -ComObject Microsoft.Update.Session
}
else {
# Remote computer (the last parameter $true enables exception being thrown if an error occurs while loading the type)
$updateSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session", $ComputerName, $true))
}
$updateSearcher = $updateSession.CreateUpdateSearcher()
$historyCount = $updateSearcher.GetTotalHistoryCount()
if ($historyCount -gt 0) {
$result += ($updateSearcher.QueryHistory(0, $historyCount) | ForEach-Object { [regex]::match($_.Title,'(KB\d+)').Value })
}
# release the Microsoft.Update.Session COM object
try {
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateSession) | Out-Null
Remove-Variable updateSession
}
catch {}
# remove empty items from the combined $result array, uniquify and return the results
$result | Where-Object { $_ -match '\S' } | Sort-Object -Unique
}
Also, I would rewrite your uptime
function to become:
function Get-LastBootTime {
[CmdletBinding()]
Param (
[string]$ComputerName = $env:COMPUTERNAME
)
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $ComputerName
$os.ConvertToDateTime($os.LastBootupTime)
}
catch {
Write-Error $_.Exception.Message
}
}
Having both functions in place, you can do
$Computers | ForEach-Object {
$updates = Get-UpdateId -ComputerName $_ -Verbose
# Now check each KBid in your list to see if it is installed or not
foreach ($item in $KBList) {
[PSCustomObject] @{
'Computer' = $_
'LastBootupTime' = Get-LastBootTime -ComputerName $_
'UpdateID' = $item
'Installed' = if ($updates -contains $item) { 'Yes' } else { 'No' }
}
}
}
The output will be something like this:
Computer LastBootupTime UpdateID Installed -------- -------------- -------- --------- wprdkofx105 10-8-2019 6:40:54 KB4507448 Yes wprdkofx105 10-8-2019 6:40:54 KB4507457 No wprdkofx105 10-8-2019 6:40:54 KB4504418 No wprdkofx106 23-1-2019 6:40:54 KB4507448 No wprdkofx106 23-1-2019 6:40:54 KB4507457 Yes wprdkofx106 23-1-2019 6:40:54 KB4504418 Yes wprdkofx107 12-4-2019 6:40:54 KB4507448 No wprdkofx107 12-4-2019 6:40:54 KB4507457 No wprdkofx107 12-4-2019 6:40:54 KB4504418 Yes
Note: I'm on a Dutch machine, so the default date format shown here is 'dd-M-yyyy H:mm:ss'
In order to alse be able to select on a date range, the code needs to be altered so the function Get-UpdateId
returns an array of objects, rather than an array of strings like above.
function Get-UpdateId {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true , Position = 0)]
[string]$ComputerName = $env:COMPUTERNAME
)
# First get the Windows HotFix history as array objects with 3 properties: 'Type', 'UpdateId' and 'InstalledOn'
Write-Verbose "Retrieving Windows HotFix history on '$ComputerName'.."
$result = Get-HotFix -ComputerName $ComputerName | Select-Object @{Name = 'Type'; Expression = {'HotFix'}},
@{Name = 'UpdateId'; Expression = { $_.HotFixID }},
InstalledOn
# or use:
# $result = Get-WmiobjectGet-WmiObject -Namespace 'root\cimv2' -Class Win32_QuickFixEngineering -ComputerName $ComputerName |
# Select-Object @{Name = 'Type'; Expression = {'HotFix'}},
# @{Name = 'UpdateId'; Expression = { $_.HotFixID }},
# InstalledOn
# Next get the Windows Update history
Write-Verbose "Retrieving Windows Update history on '$ComputerName'.."
if ($ComputerName -eq $env:COMPUTERNAME) {
# Local computer
$updateSession = New-Object -ComObject Microsoft.Update.Session
}
else {
# Remote computer (the last parameter $true enables exception being thrown if an error occurs while loading the type)
$updateSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session", $ComputerName, $true))
}
$updateSearcher = $updateSession.CreateUpdateSearcher()
$historyCount = $updateSearcher.GetTotalHistoryCount()
if ($historyCount -gt 0) {
$result += ($updateSearcher.QueryHistory(0, $historyCount) | ForEach-Object {
[PsCustomObject]@{
'Type' = 'Windows Update'
'UpdateId' = [regex]::match($_.Title,'(KB\d+)').Value
'InstalledOn' = ([DateTime]($_.Date)).ToLocalTime()
}
})
}
# release the Microsoft.Update.Session COM object
try {
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($updateSession) | Out-Null
Remove-Variable updateSession
}
catch {}
# remove empty items from the combined $result array and return the results
$result | Where-Object { $_.UpdateId -match '\S' }
}
The Get-LastBootTime
function does not need changing, so I leave you to copy that from the first part of the answer.
To check for installed updates by their UpdateId
property
$Computers | ForEach-Object {
$updates = Get-UpdateId -ComputerName $_ -Verbose
$updateIds = $updates | Select-Object -ExpandProperty UpdateId
# Now check each KBid in your list to see if it is installed or not
foreach ($item in $KBList) {
$update = $updates | Where-Object { $_.UpdateID -eq $item }
[PSCustomObject] @{
'Computer' = $_
'LastBootupTime' = Get-LastBootTime -ComputerName $_
'Type' = $update.Type
'UpdateID' = $item
'IsInstalled' = if ($updateIds -contains $item) { 'Yes' } else { 'No' }
'InstalledOn' = $update.InstalledOn
}
}
}
Output (something like)
Computer : wprdkofx105 LastBootupTime : 10-8-2019 20:01:47 Type : Windows Update UpdateID : KB4507448 IsInstalled : Yes InstalledOn : 12-6-2019 6:10:11 Computer : wprdkofx105 LastBootupTime : 10-8-2019 20:01:47 Type : UpdateID : KB4507457 IsInstalled : No InstalledOn :
To get hotfixes and updates installed within a start and end date
$StartDate = (Get-Date).AddDays(-14)
$EndDate = Get-Date
foreach ($computer in $Computers) {
Get-UpdateId -ComputerName $computer |
Where-Object { $_.InstalledOn -ge $StartDate -and $_.InstalledOn -le $EndDate } |
Select-Object @{Name = 'Computer'; Expression = {$computer}},
@{Name = 'LastBootupTime'; Expression = {Get-LastBootTime -ComputerName $computer}}, *
}
Output (something like)
Computer : wprdkofx105 LastBootupTime : 20-8-2019 20:01:47 Type : HotFix UpdateId : KB4474419 InstalledOn : 14-8-2019 0:00:00 Computer : wprdkofx107 LastBootupTime : 20-8-2019 20:01:47 Type : Windows Update UpdateId : KB2310138 InstalledOn : 8-8-2019 15:39:00