简体   繁体   中英

Checking if windows security patches are installed on multiple servers

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..."

I would like to say that there is apparently a misconception about the ability to obtain information about all installed patches from Win32_QuickFixEngineering WMI class. Even the official documentation states:

Updates supplied by Microsoft Windows Installer (MSI) or the Windows update site ( https://update.microsoft.com ) are not returned by Win32_QuickFixEngineering.

It seems that Win32_QuickFixEngineering is something like old fashioned approach which should be re replaced by using Windows Update Agent API to enumerate all updates installed using WUA - https://docs.microsoft.com/en-us/windows/win32/wua_sdk/using-the-windows-update-agent-api

Also, please take a loot at this good article - https://support.infrasightlabs.com/article/what-does-the-different-windows-update-patch-dates-stand-for/

You will find a lot of code examples by searching by "Microsoft.Update.Session" term

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 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM