检查是否在多台服务器上安装了 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
我正在尝试检查我在变量列表中设置的指定 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