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'
Update
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