在多个服务器上查询Windows Server更新

beq87vna  于 2023-04-22  发布在  Windows
关注(0)|答案(1)|浏览(111)

我正试着写一个powershell脚本来查询几个服务器并检查它们是否有任何待处理的更新。
基本上,我使用“MSCatalog”模块来查询更新如下:

$Search = (Get-Date -Format "yyyy-MM")
$Updates = (Get-MSCatalogUpdate -Search $Search -AllPages ) | Where-Object { ($_.Title -like "*Server*") -OR ($_.Products -like "*Server*") }

我最感兴趣的是新的每月更新,所以我只是使用获取日期查询这个月的更新。当然,我过滤了服务器的标题或产品列表。
现在我的服务器都是全面的。我们有一些遗留服务器2008,2012,2012 R2,2016和2019。
我试图找出一个好的方法来检查,看看是否有任何更新丢失。要获得当前的服务器更新,我运行下面的命令:

$ServerKBs = (((Get-HotFix -ComputerName $($FilteredServerResult.ServerName)) | Select-Object HotFixID,InstalledOn) | Where-Object {($_.InstalledOn -like "$ServerUpdateSearch" )}).HotFixID

我坚持的部分是我试图只比较与服务器相关的更新。例如,我不关心2016或2012 r2服务器的2019更新。我只希望特定于该服务器的更新显示。现在我明白了,我可以通过列名过滤它,并将其与服务器上的操作系统进行比较,以确保它匹配。例如,要获取操作系统的数字版本,我可以使用以下命令:

$ServerOSString = (($FilteredServerResult.OS) -replace '\D+(\d+)\D+','$1')

例如,输出将是2016而不是“Windows Server 2016 Standard”,然后我可以这样做:

if(($Update.Products -match $ServerOSString) -eq $True )
{
    #Do Stuff
}

这是我卡住的部分,因为假设这一切都对齐,现在我将服务器上安装的KB与我从Microsoft Windows Catalog中查询的内容进行比较......可能有一些更新匹配,其他更新可能不匹配。我想只关注那些具有与服务器不匹配的挂起更新的更新。我如何才能进行这样的操作?
我使用poshrs-job在多个服务器上运行它,所以它更快,所以我想尽可能高效地完成这一任务,并希望输出一个简单的服务器列表,这些服务器仍然需要使用KB &\或标题进行修补。
下面是我正在尝试做的一个例子:

CLS

#Main Variables

#The Search will always be the year-month when you run the script
$Search = (Get-Date -Format "yyyy-MM")

#Formatting is different one servers than on Windows update catalog
$ServerUpdateSearch = (Get-Date -Format "*MM*yyyy*")

#------------- MSCatalog (For Querying Windows Catalog)

if((Get-Module -ListAvailable -Name "MSCatalog") -or (Get-Module -Name "MSCatalog"))
{
        Import-Module MSCatalog
}
else
{   
    Install-Module -Name MSCatalog -Scope CurrentUser -Force -Confirm:$False
    Import-Module MSCatalog
}

#------------- PoshRSJob (multitasking)

if((Get-Module -ListAvailable -Name "PoshRSJob") -or (Get-Module -Name "PoshRSJob"))
{
        Import-Module PoshRSJob
}
else
{   
    Install-Module -Name PoshRSJob -Scope CurrentUser -Force -Confirm:$False
    Import-Module PoshRSJob
}

#------------- ImportExcel Module 

if((Get-Module -ListAvailable -Name "ImportExcel") -or (Get-Module -Name "ImportExcel"))
{
        Import-Module ImportExcel
}
else
{
    #Install NuGet (Prerequisite) first
    Install-PackageProvider -Name NuGet -Scope CurrentUser -Force -Confirm:$False
    
    Install-Module -Name ImportExcel -Scope CurrentUser -Force -Confirm:$False
    Import-Module ImportExcel
}

#Clear screen again
CLS

#----------------------------------------------------------------------------------------------------------------

#Start Timestamp
$Start = Get-Date

#Global Variables
$Path = (Split-Path $script:MyInvocation.MyCommand.Path)
$ErrorFile = (Split-Path $script:MyInvocation.MyCommand.Path) + "\ERROR.csv"

#------------------------------------  Setup Excel Variables

#The file we will be reading from
$ExcelFile = (Get-ChildItem -Path "$Path\*.xlsx").FullName

#Worksheet we are working on (by default this is the 1st tab)
$worksheet = (((New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList (New-Object -TypeName System.IO.FileStream -ArgumentList $ExcelFile,'Open','Read','ReadWrite')).Workbook).Worksheets[1]).Name

$ExcelServers = Import-Excel -Path $ExcelFile -WorkSheetname $worksheet -StartRow 1

#------------------------------------ Populate our variable with data from spreadsheet

$ExcelServersList = foreach($ExcelServer in $ExcelServers) {
    $ExcelServer | Select-Object @{Name="ServerName";Expression={$_.Child}}, "Primary", @{Name="PatchWindow";Expression={$_."Patch Window"}}, @{Name="TestServer";Expression={$_."Test Server"}}, "DMZ", @{Name="OS";Expression={$_."Operating System"}} 
}

#------------------------------------ Remove Duplicate entries

$SortedExcelServersList = ($ExcelServersList | Sort-Object -Property ServerName -Unique)

#------------------------------------ Seperate Servers from DMZ Servers

$FilteredServers = ForEach($SortedExcelServerList in $SortedExcelServersList) {

    if($($SortedExcelServerList.DMZ) -eq $true)
    {
        $SortedExcelServerList.ServerName = [System.String]::Concat("$($SortedExcelServerList.ServerName)",".DMZ.com")
    }

    $SortedExcelServerList
}

#------------------------------------ Grab all servers from AD so we can use to compare against our list - also trimany whitespaces from output

$Servers = (dsquery * -filter "(&(objectClass=Computer)(objectCategory=Computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(operatingSystem=*Server*))" -limit 0 -attr Name | sort).trim()

#------------------------------------ Compare our list to servers in AD and filter out appliances

$FilteredServersResult = $Null

$FilteredServersResult = ForEach ($Item in $FilteredServers) 
{
    If (($item.servername -in $Servers) -or ($item.DMZ -eq $True))
    {
        $Item
    }
}

#---------------------------- Perform our search. In this case all Monthly Updates and filter it to only show updates for Servers

$Updates = (Get-MSCatalogUpdate -Search $Search -AllPages ) | Where-Object { ($_.Title -like "*Server*") -OR ($_.Products -like "*Server*") }

#------------------------------------ Multithreading Magic

$FilteredServersResult | Start-RSJob -Throttle 50 -Batch "Test" -ScriptBlock {
    Param($Server)

    #Ping servers to make sure they're responsive
    if($NULL -ne (Get-CimInstance -ClassName Win32_PingStatus -Filter "Address='$($Server.servername)' AND Timeout=100").ResponseTime)
    { 
        Try
        {
            $ServerKBs = (((Get-HotFix -ComputerName $($Server.servername)) | Select-Object HotFixID,InstalledOn) | Where-Object {($_.InstalledOn -like "$ServerUpdateSearch" )}).HotFixID

            foreach($Update in $Updates)
            {
                If (($Server.OS -like "*2016*") -And $Update.Products -match "*2016*")
                {
                    #Check if there are any missing Updates
                    #($Server | Add-Member -NotePropertyMembers @{"Missing Updates" = $Update} -PassThru)
                }
                If (($Server.OS -like "*2019*") -And $Update.Products -match "*2019*")
                {
                    #Check if there are any missing Updates
                    #($Server | Add-Member -NotePropertyMembers @{"Missing Updates" = $Update} -PassThru)
                }
            }
        }
        Catch
        {
            ($Server | Add-Member -NotePropertyMembers @{"Error" = [string]$Error} -PassThru) | Export-Csv -Path $using:ErrorFile -NoTypeInformation -Force -Append
        }
        
        #Get list of Updates for servers
        
    }

} | Wait-RSJob -ShowProgress | Receive-RSJob | Export-Csv -Path "$Path\Results.csv" -NoTypeInformation -Force

$End =  (Get-Date)

$End - $Start
b91juud3

b91juud31#

我可以使用3个不同的模块来实现这一点。

  1. MSCatalog -使用此模块查询Windows Update目录
  2. PoshRSJob -运行多线程作业的最简单方法
  3. ImportExcel -我发现使用电子表格的最简单和最好的方法
    我采取的高水平步骤如下:
    1.从电子表格导入服务器信息
    1.我做了一些过滤,将MZ服务器与普通服务器分开
    1.我将电子表格与AD中的内容进行比较,以确保不包括已禁用的设备或其他服务器对象
    1.接下来,我查询Windows更新目录以获取本月的最新更新,并将其过滤为仅显示服务器更新,而且我只对关键和安全更新感兴趣。
    1.在多线程脚本块中,我检查服务器上安装了哪些最新更新,并将其与我从Windows Update目录中提取的更新进行比较。
    下面是我使用的脚本:
CLS

#------------- MSCatalog (For Querying Windows Catalog)

if((Get-Module -ListAvailable -Name "MSCatalog") -or (Get-Module -Name "MSCatalog"))
{
        Import-Module MSCatalog
}
else
{   
    Install-Module -Name MSCatalog -Scope CurrentUser -Force -Confirm:$False
    Import-Module MSCatalog
}

#------------- PoshRSJob (multitasking)

if((Get-Module -ListAvailable -Name "PoshRSJob") -or (Get-Module -Name "PoshRSJob"))
{
        Import-Module PoshRSJob
}
else
{   
    Install-Module -Name PoshRSJob -Scope CurrentUser -Force -Confirm:$False
    Import-Module PoshRSJob
}

#------------- ImportExcel Module 

if((Get-Module -ListAvailable -Name "ImportExcel") -or (Get-Module -Name "ImportExcel"))
{
        Import-Module ImportExcel
}
else
{
    #Install NuGet (Prerequisite) first
    Install-PackageProvider -Name NuGet -Scope CurrentUser -Force -Confirm:$False
    
    Install-Module -Name ImportExcel -Scope CurrentUser -Force -Confirm:$False
    Import-Module ImportExcel
}

#Clear screen again
CLS

#----------------------------------------------------------------------------------------------------------------

#Start Timestamp
$Start = Get-Date

#Global Variables
$Path = (Split-Path $script:MyInvocation.MyCommand.Path)
$ErrorFile = (Split-Path $script:MyInvocation.MyCommand.Path) + "\ERROR.csv"
$ResultsFile = (Split-Path $script:MyInvocation.MyCommand.Path) + "\Results.csv"

#------------------------------------  Setup Excel Variables

#The file we will be reading from
$ExcelFile = (Get-ChildItem -Path "$Path\*.xlsx").FullName

#Worksheet we are working on (by default this is the 1st tab)
$worksheet = (((New-Object -TypeName OfficeOpenXml.ExcelPackage -ArgumentList (New-Object -TypeName System.IO.FileStream -ArgumentList $ExcelFile,'Open','Read','ReadWrite')).Workbook).Worksheets[1]).Name

$ExcelServers = Import-Excel -Path $ExcelFile -WorkSheetname $worksheet -StartRow 1

#------------------------------------ Populate our variable with data from spreadsheet

$ExcelServersList = foreach($ExcelServer in $ExcelServers) {
    $ExcelServer | Select-Object @{Name="ServerName";Expression={$_.Child}}, "Primary", @{Name="PatchWindow";Expression={$_."Patch Window"}}, @{Name="TestServer";Expression={$_."Test Server"}}, "DMZ", @{Name="OS";Expression={((($_."Operating System").Replace("Windows ","")) -replace "(\s\S+)$") }}
}

#------------------------------------ Remove Duplicate entries

$SortedExcelServersList = ($ExcelServersList | Sort-Object -Property ServerName -Unique)

#------------------------------------ Seperate Servers from DMZ Servers

$FilteredServers = ForEach($SortedExcelServerList in $SortedExcelServersList) {

    if(($($SortedExcelServerList.DMZ) -eq $true) -AND ($($SortedExcelServerList.ServerName) -notlike "*.dmz.com"))
    {
        $SortedExcelServerList.ServerName = [System.String]::Concat("$($SortedExcelServerList.ServerName)",".dmz.com")
    }

    $SortedExcelServerList
}

#------------------------------------ Grab all servers from AD so we can use to compare against our list - also trimany whitespaces from output

$Servers = (dsquery * -filter "(&(objectClass=Computer)(objectCategory=Computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(operatingSystem=*Server*))" -limit 0 -attr Name | sort).trim()

#------------------------------------ Compare our list to servers in AD and filter out appliances

$FilteredServersResult = ForEach($Item in $FilteredServers) {

    If (($item.ServerName -in $Servers) -or ($item.DMZ -eq $True))
    {
        $Item
    }
}

#---------------------------- Perform our search. In this case all Monthly Updates and filter it to only show updates for Servers

$Search = (Get-Date -Format "yyyy-MM")

$Updates = (Get-MSCatalogUpdate -Search $Search -AllPages -ErrorAction Stop)

$WinCatalog = foreach($Update in $Updates) {
    $Update | Where { (($_.Title -like "*Server*") -OR ($_.Products -like "*Server*")) -AND (($_.Classification -eq "Critical Updates") -OR ($_.Classification -eq "Security Updates")) } | Select-Object @{Name="Title";Expression={([regex]::Matches(($($_).Title), '(?<=\().+?(?=\))')).Value}}, @{Name="Products";Expression={(($_."Products").Replace("Windows Server ",""))}}, "Classification", "LastUpdated", "Size"
}

#------------------------------------ Multithreading Magic

$DMZAccount = Get-Credential ((whoami).replace("domain","dmz.com"))

$FilteredServersResult | Start-RSJob -Throttle $($FilteredServersResult.Count) -Batch "Test" -ErrorAction Stop -ScriptBlock {
    
    Param($Server)
    $Check = $False

    #------------------------------------ Ping servers to make sure they're responsive

    Try
    {
        if($NULL -ne (Get-CimInstance -ClassName Win32_PingStatus -Filter "Address='$($Server.servername)' AND Timeout=100" -ErrorAction Stop).ResponseTime)
        {
            #------------------------------------ Get Server KB's

            $NeedsUpdates = $ServerKBs = $NULL

            Try
            {
                [ScriptBlock]$SB = {
                    
                    $DateSearch = (Get-Date -Format "*MM*yyyy*")

                    $updateSession = New-Object -ComObject Microsoft.Update.Session
                    $updateSearch = $updateSession.CreateUpdateSearcher()
                    $updateCount = $updateSearch.GetTotalHistoryCount()

                    $ServerUpdates = ($updateSearch.QueryHistory(0,$updateCount) | Select Date,Title,Description | Where-Object { ($PSItem.Title) -AND ($_.Date -like $DateSearch) })

                    ([regex]::Matches(($($ServerUpdates).Title), '(?<=\().+?(?=\))')).Value
                }

                if($Server.DMZ -eq $TRUE)
                {
                    $ServerKBs = Invoke-Command -ComputerName $($Server.servername) -Credential $using:DMZAccount -ErrorAction Stop -ScriptBlock $SB | where {$_ -like "KB*"}
                    $Check = $True
                }
                else
                {
                    $ServerKBs = Invoke-Command -ComputerName $($Server.servername) -ErrorAction Stop -ScriptBlock $SB | where {$_ -like "KB*"}
                    $Check = $True
                }
            }
            Catch
            {
                if($Check -eq $False)
                {
                    Try
                    {
                        $DateSearch = (Get-Date -Format "*MM*yyyy*")

                        $ServerKBs = (((Get-HotFix -ComputerName $($Server.servername) -ErrorAction Stop) | Select-Object HotFixID,InstalledOn) | Where {($_.InstalledOn -like "$DateSearch" )}).HotFixID
                        $Check = $True

                    }
                    Catch
                    {
                        ($Server | Add-Member -NotePropertyMembers @{"Error" = [string]$Error} -Force -PassThru) | Export-Csv -Path $using:ErrorFile -NoTypeInformation -Force -Append
                    }
                }
            }

            #---------------------------- Compare Updates on server to WIndows Update Catalog to determine which updates are missing

            $NeedsUpdates = foreach($item in $using:WinCatalog) {

                #Match up the Update for the OS of the server

                if($item.Products -eq $($Server.OS))
                {
                    #Now check if the update is missing
                    if(($NULL -eq $ServerKBs) -OR ($item.Title -notin $ServerKBs))
                    {
                        $item.Title
                    }
                }
            }

            if($NULL -ne $NeedsUpdates)
            {
                ($Server | Add-Member -NotePropertyMembers @{"KBs" = (@($($NeedsUpdates)) -join ', ')} -PassThru)
            }
        }
    }
    Catch
    {
        ($Server | Add-Member -NotePropertyMembers @{"Error" = [string]$Error} -Force -PassThru) | Export-Csv -Path $using:ErrorFile -NoTypeInformation -Force -Append
    }

} | Wait-RSJob -ShowProgress -Timeout 30 | Receive-RSJob | Export-Csv -Path $ResultsFile -NoTypeInformation -Force

$End =  (Get-Date)

$End - $Start

相关问题