检查是否在多台服务器上安装了 windows 个安全补丁

Checking if windows security patches are installed on multiple servers

我正在尝试检查我在变量列表中设置的指定 KB # 是否与服务器上已安装补丁的完整 KB 列表匹配。如果匹配则显示补丁已安装,否则提示未安装。

下面的代码似乎不起作用,因为它显示为未安装,但实际上它已经安装了。

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

我想说的是,对于从 Win32_QuickFixEngineering WMI class 获取有关所有已安装补丁的信息的能力,显然存在误解。 甚至 official documentation 状态:

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

似乎 Win32_QuickFixEngineering 有点像老式的方法,应该通过使用 Windows 更新代理 API 来重新替换,以枚举使用 WUA 安装的所有更新 - https://docs.microsoft.com/en-us/windows/win32/wua_sdk/using-the-windows-update-agent-api

另外,这篇好文请收藏~ https://support.infrasightlabs.com/article/what-does-the-different-windows-update-patch-dates-stand-for/

通过 "Microsoft.Update.Session" 术语搜索,您会找到很多代码示例

正如 Kostia 已经解释的那样,Win32_QuickFixEngineering 不会检索所有更新和补丁。为了得到这些,我会使用一个辅助函数,它也获取 Windows 更新和 returns 它们全部作为字符串数组,如下所示:

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
}

此外,我会重写您的 uptime 函数,使其成为:

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    
    }
}

有了这两个功能,你就可以做

$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' }
        }
    }
}

输出将是这样的:

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

注意:我在荷兰机器上,所以这里显示的默认日期格式是'dd-M-yyyy H:mm:ss'


更新


为了能够在日期范围内 select,需要更改代码以便函数 Get-UpdateId returns 一个对象数组,而不是一个数组像上面的字符串。

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

Get-LastBootTime 函数不需要更改,所以我让您从答案的第一部分复制它。

通过 UpdateId 属性

检查已安装的更新
$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
        }
    }
}

输出(类似)

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    :

在开始日期和结束日期内安装修补程序和更新

$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}}, *
}

输出(类似)

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