PowerShell 技能连载 - 隐藏启动 PowerShell 脚本

没有内置的方法可以隐藏启动 PowerShell 脚本:即使您运行 powershell.exe 并指定 -WindowStyle Hidden,PowerShell 控制台仍将一闪而过。

要隐藏启动 PowerShell 脚本,可以使用 VBScript:

1
2
3
4
5
6
Set objShell = CreateObject("WScript.Shell")
path = WScript.Arguments(0)

command = "powershell -noprofile -windowstyle hidden -executionpolicy bypass -file """ & path & """"

objShell.Run command,0

将 test.vbs 保存,然后确保以 ANSI 编码保存(用记事本并在另存为对话框的地步下拉列表中选择编码)。VBS 无法处理 UTF8 编码的脚本。当您试图运行这样的脚本时,会得到一个非法字符的异常。

要隐藏启动一个 PowerShell 脚本,可以运行这行命令:

请注意,虽然 wscript.exe 隐藏了 PowerShell 的控制台窗口,但您打开任何 WPF窗口(例如使用 Out-GridView)时,将继续工作并且正常显示。

1
Wscript.exe c:\pathtovbs.vbs c:\pathtoPS1file.ps1
评论

PowerShell 技能连载 - 杀死无响应的进程

Get-Process 返回的进程对象可以判断该进程当前是否正在响应窗口消息,从而是否正在响应用户请求。此行代码返回当前的 PowerShell 进程的 “Responding” 属性:

1
2
3
4
5
PS> Get-Process -Id $Pid | Select-Object *respond*

Responding
----------
True

进程偶尔会变得无响应是很常见的,例如由于高负载和较弱的软件体系结构。当某个进程在较长时间内没有响应时,就会“挂起”,会让用户发疯。

这行代码列出了当前未响应的进程:

1
2
3
4
5
6
7
8
9
PS> Get-Process | Where-Object { !$_.Responding }

Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
560 28 21752 580 0.38 18544 1 Calculator
915 65 26660 2528 0.39 14244 1 MicrosoftEdge
488 21 7108 7988 0.09 13400 1 MicrosoftEdgeCP
543 27 16148 520 0.31 21200 1 Time
1132 77 63544 836 1.55 15212 1 WinStore.App

请注意,此列表可能包括已启动但不再使用的 Windows 应用程序。由于属性“正在响应”仅描述当前状态,因此它无法确定进程多长时间未响应。

如果您想汇报(或杀死)一段时间未响应的所有进程,则需要自己进行反复检查并跟踪结果。

下面的代码在网格视图窗口中列出了 3 秒钟没有响应的所有进程。然后,用户可以选择一个或多个要杀死的进程(按住 CTRL 键选择多个进程)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# report processes hanging for more than 3 seconds
$timeout = 3

# use a hash table to keep track of processes
$hash = @{}

# use an endless loop and test processes
do
{
Get-Process |
# look at processes with a window only
Where-Object MainWindowTitle |
ForEach-Object {
# use process ID as key to the hash table
$key = $_.id
# if the process is responding, reset the counter
if ($_.Responding)
{
$hash[$key] = 0
}
# else, increment the counter by one
else
{
$hash[$key]++
}
}

# copy the hash table keys so that the collection can be
# modified
$keys = @($hash.Keys).Clone()

# emit all processes hanging for longer than $timeout seconds
# look at all processes monitored
$keys |
# take the ones not responding for the time specified in $timeout
Where-Object { $hash[$_] -gt $timeout } |
ForEach-Object {
# reset the counter (in case you choose not to kill them)
$hash[$_] = 0
# emit the process for the process ID on record
Get-Process -id $_
} |
# exclude those that already exited
Where-Object { $_.HasExited -eq $false } |
# show properties
Select-Object -Property Id, Name, StartTime, HasExited |
# show hanging processes. The process(es) selected by the user will be killed
Out-GridView -Title "Select apps to kill that are hanging for more than $timeout seconds" -PassThru |
# kill selected processes
Stop-Process -Force

# sleep for a second
Start-Sleep -Seconds 1

} while ($true)

当然,您可以轻松更改代码以生成挂起的进程报表。只需将 Stop-Process 替换为您想要执行的任何操作,即可使用 Add-Content 将流程写入日志文件。要避免一次次地记录相同的进程,您可能想要添加某种黑名单,以跟踪已经记录的进程。

评论

PowerShell 技能连载 - 测试网络连接(第 2 部分)

如果您想测试一台特定的计算机或 URL 是否在线,几十年来一直使用 ping 请求 (ICMP)。最近,许多服务器和防火墙关闭了 ICMP 以减少攻击面。默认情况下,Test-NetConnection 使用 ICMP,因此在不响应 ICMP 的计算机上会失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> Test-NetConnection -ComputerName microsoft.com
WARNING: Ping to 40.76.4.15 failed with status: TimedOut
WARNING: Ping to 40.112.72.205 failed with status: TimedOut
WARNING: Ping to 13.77.161.179 failed with status: TimedOut
WARNING: Ping to 40.113.200.201 failed with status: TimedOut
WARNING: Ping to 104.215.148.63 failed with status: TimedOut


ComputerName : microsoft.com
RemoteAddress : 40.76.4.15
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : False
PingReplyDetails (RTT) : 0 ms

Test-NetConnection 内置了另一种计算机无法回避的测试:端口测试。端口提供对特定服务的访问,因此端口必须在任何公共服务可用。对于 Web 服务器,可以使用 80 端口,例如:

1
2
3
4
5
6
7
8
9
PS> Test-NetConnection -ComputerName microsoft.com -Port 80


ComputerName : microsoft.com
RemoteAddress : 104.215.148.63
RemotePort : 80
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
TcpTestSucceeded : True

以下是常用的端口列表:

HTTP: Port 80
HTTPS: Port 443
FTP: Port 21
FTPS/SSH: Port 22
TELNET: Port 23
POP3: Port 110
POP3 SSL: Port 995
IMAP: Port 143
IMAP SSL: Port 993
WMI: Port 135
RDP: Port 3389
DNS: Port 53
DHCP: Port 67, 68
SMB/NetBIOS: 139
NetBIOS over TCP: 445
PowerShell Remoting: 5985
PowerShell Remoting HTTPS: 5986
评论

PowerShell 技能连载 - 测试网络连接(第 1 部分)

PowerShell 随附了 Test-NetConnection 命令,它可以像复杂的 ping 工具一样工作。默认情况下,您可以这样 ping 计算机:

1
2
3
4
5
6
7
8
9
PS> Test-NetConnection -ComputerName powershell.one


ComputerName : powershell.one
RemoteAddress : 104.18.46.88
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : True
PingReplyDetails (RTT) : 26 ms

使用 -TraceRoute,它包括路由跟踪,列出了用于传播到目标的所有网络节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> Test-NetConnection -ComputerName powershell.one -TraceRoute


ComputerName : powershell.one
RemoteAddress : 104.18.46.88
InterfaceAlias : Ethernet 4
SourceAddress : 192.168.2.108
PingSucceeded : True
PingReplyDetails (RTT) : 25 ms
TraceRoute : 192.168.2.1
62.155.243.83
62.154.2.185
62.157.250.38
195.22.215.192
195.22.215.59
104.18.46.88
评论

PowerShell 技能连载 - 探索即插即用设备(第 4 部分)

在前面的技能中,我们研究了 UPnP.UPnPDeviceFinder 以及如何识别网络中的设备。让我们看一些用例。显然,这些用例是您的灵感来源,因为您可以做的事情完全取决于连接到网络的实际设备。

我的第一个用例是管理 NAS 设备,名为 Synology Disk Station。可以通过 WEB 界面进行管理,但我总是会忘记 URL,当然 URL 和端口会根据配置而有所不同。

这是一种搜索任何磁盘站并自动打开其 Web 界面的方法。由于所有连接数据都是从设备中检索的,因此即使 IP 地址更改或您配置了别的端口,此操作也仍然有效。

1
2
3
4
5
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Where-Object ManufacturerName -eq 'Synology' |
Select-Object -ExpandProperty PresentationUrl |
ForEach-Object { Start-Process -FilePath $_ }

注意:如果您没有 Synology 磁盘站,请查看您拥有的其他设备。只需查看 FindByType() 返回的数据,然后根据需要定制 Where-Object

由于该代码枚举了所有 UPnP 设备,因此需要 10 到 20 秒。如果要加快处理速度,可以找出所用设备的 UDN(唯一名称),以后再使用这些 UDN:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Where-Object ManufacturerName -eq 'Synology' |
Select-Object -ExpandProperty UniqueDeviceName

UDN 是唯一的,并且因设备而异,因此,如果您需要多次访问同一设备,则此方法才有意义。在我的情况下,UDN 是 uuid:7379AA6F-6473-6D00-0000-001132283f5e,现在打开 Web 界面的速度要快得多:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("uuid:7379AA6F-6473-6D00-0000-001132283f5e", 0) |
Select-Object -ExpandProperty PresentationUrl |
Foreach-Object { Start-Process -FilePath $_ }

这引出了我的第二个使用场景:我正在使用 Philips Hue 系统管理家里的灯光。飞利浦提供了丰富的 REST API 来实现自动化。您所需要的是网桥的 IP 地址。

这段代码将列出本地网络中所有 Philips Hue 网桥的 IP 地址:

powershell $UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder $UPnPFinder.FindByType("upnp:rootdevice", 0) | Where-Object Description -like 'Philips hue*' | Select-Object -ExpandProperty FriendlyName | ForEach-Object { if ($_ -match '(?\w*).*?\((?.*)\)') { $null = $matches.Remove(0) [PSCustomObject]$matches } } `

该代码查找根设备,其描述以 “Philips hue” 开头的根设备,然后使用正则表达式拆分 “FriendlyName” 属性的内容并返回网桥名称及其 IP 地址。

就我而言,结果如下所示:

IP            BridgeName
--            ----------
192.168.22.10 BridgeOne
192.168.23.16 BridgeWork
评论

PowerShell 技能连载 - 探索即插即用设备(第 3 部分)

在前一个技能中我们演示了如何使用 UPnP.UPnPDeviceFinder 来查找网络中的设备。您已了解到如何枚举所有的根设备 (“upnp:rootdevice“),以及如何通过设备的唯一标识符来访问设备。

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("upnp:rootdevice", 0) | Out-GridView

在这一部分中,让我们完成搜索类型,看看如何枚举所有设备(而不仅仅是根设备),以及如何枚举设备类型组。

要列出所有设备,请使用 “ssdb:all" 而不是 "upnp:rootdevice`”:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) | Out-GridView

结果包括根设备(“IsRootDevice” 为 $true,“ParentDevice”为空)以及所有子设备(“IsRootDevice” 为 $false,“ParentDevice” 指向该设备链接到的上级设备)。

在 “UniqueDeviceName” 中,可以找到可用于直接访问设备的唯一设备名称:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByUDN("uuid:...", 0)

每个设备都属于一个类别,该类别在“Type”中显示。要查看类型列表,请尝试以下操作:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("ssdp:all", 0) |
Select-Object -ExpandProperty Type |
Sort-Object -Unique

结果取决于网络中找到的设备。这是我得到的清单:

urn:dial-multiscreen-org:device:dial:1
urn:schemas-upnp-org:device:Basic:1
urn:schemas-upnp-org:device:InternetGatewayDevice:1
urn:schemas-upnp-org:device:MediaRenderer:1
urn:schemas-upnp-org:device:WANConnectionDevice:1
urn:schemas-upnp-org:device:WANDevice:1

要查找特定类型的所有设备,请将该类型与 FindByType() 一起使用:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("urn:schemas-upnp-org:device:InternetGatewayDevice:1", 0)

最后一点:设备是否响应组搜索,甚至是“upnp:rootdevice”,都取决于设备及其实现。在我的场景中,即使存在那种类型的设备,我也无法获得“Basic”和“WANDevice”组的结果。

如果找不到特定设备,请尝试适用于所有设备的唯一搜索,然后通过“ssdp:all”列出所有设备。如果设备现在显示出来,则可以通过 Where-Object 使用“ssdp:all”和客户端过滤,或者通过查找唯一的设备标识符并通过其 UDN 和 FindByUDN() 直接访问特定设备来加快搜索速度。

评论

PowerShell 技能连载 - 探索即插即用设备(第 2 部分)

在前一个技能中,我们使用 UPnP.UPnPDeviceFinder 来发现连入您网络的智能设备。今天,让我们仔细看看返回的对象。

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$result = $UPnPFinder.FindByType("upnp:rootdevice", 0)

当您运行一个搜索(可能需要执行一定的时间),将会获取到许多信息,但是某些属性只是以 System.__ComObject 的形式返回。如何查看它们背后隐藏的信息?

让我们取其中的一个返回对象。在我的例子中,通过查看 $result,我注意到一个来自 “NetGear” 对象。接下来,我使用 Where-Object 来存取它。

在您的场景中,可用的对象明显不同,这取决于您网络中使用的设备,所以您需要调整过滤器表达式来匹配返回的对象之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PS> $switch = $result | Where-Object ManufacturerName -like *NetGear*

PS> $switch


IsRootDevice : True
RootDevice : System.__ComObject
ParentDevice :
HasChildren : True
Children : System.__ComObject
UniqueDeviceName : uuid:4d696e69-444c-164e-9d42-3894ed0e1db5
FriendlyName : RBR50 (Gateway)
Type : urn:schemas-upnp-org:device:InternetGatewayDevice:1
PresentationURL : http://www.orbilogin.net/
ManufacturerName : NETGEAR, Inc.
ManufacturerURL : http://www.netgear.com/
ModelName : NETGEAR Orbi Desktop AC3000 Router
ModelNumber : RBR50
Description : http://www.netgear.com/home/products/wirelessrouters
ModelURL : http://www.netgear.com/orbi
UPC : RBR50
SerialNumber : 5R21945T06DA4
Services : System.__ComObject

大多数属性使用普通的数据类型,例如 string 或者 integer。例如,”UniqueDeviceName” 返回一个设备的唯一名称(稍后会变得很重要)。然而某些属性,只是返回 “System.__ComObject”。让我们看看这些:

每个 PnP 设备可以是一个链条的一部分,并且由一个根设备开始。由于我们一开始搜索的是根设备,所以 “RootDevice” 属性总是对应返回的对象:

1
2
PS> $switch -eq $switch.RootDevice
True

要找出那些设备链接到根设备,请查看 “HasChildren” 和 “Children”:”HasChildren” 是一个简单的 Boolean 值。然而 “Children” 是一个 “System.__ComObject” 对象并且包含了我们想了解的信息:

1
2
PS> if ($switch.HasChildren) { $switch.Children.Count } else { 'no children' }
1

显然地,”Children” 看起来是某种数组。总之,一个根设备可以有许多子设备。通过存取该属性,PowerShell 自动将 “System.__ComObject” 转换为可读取的 .NET 对象,以下是我的 NetGear 设备的子设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS> $switch.Children


IsRootDevice : False
RootDevice : System.__ComObject
ParentDevice : System.__ComObject
HasChildren : True
Children : System.__ComObject
UniqueDeviceName : uuid:4d696e69-444c-164e-9d43-3894ed0e1db5
FriendlyName : WAN Device
Type : urn:schemas-upnp-org:device:WANDevice:1
PresentationURL :
ManufacturerName : NETGEAR
ManufacturerURL : http://www.netgear.com/
ModelName : NETGEAR Orbi Desktop AC3000 Router
ModelNumber : RBR50
Description : WAN Device on NETGEAR RBR50 Orbi Router
ModelURL : http://www.netgear.com/
UPC : RBR50
SerialNumber : 5R21945T06DA4
Services : System.__ComObject

这个子设备之下还有子设备。但是,继续深挖并列出子设备之下的子设备看起来失败了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS> # works:
PS> $switch.Children | Select-Object HasChildren, Children

HasChildren Children
----------- --------
True System.__ComObject



PS> # fails:
PS> $switch.Children.HasChildren
PS> $switch.Children.Children
PS> $switch.Children[0].HasChildren
Value does not fall within the expected range.
PS> $switch.Children[0].Children
Value does not fall within the expected range.

失败的原因是 COM 数组的一个特异性。它们使用一个非常规的枚举器,和普通的对象数组不同,所以 PowerShell 无法直接存取数组元素。一个简单的解决方案是使用 ForEach-Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PS> $child = $switch.Children

PS> $child | ForEach-Object { $_.Children }


IsRootDevice : False
RootDevice : System.__ComObject
ParentDevice : System.__ComObject
HasChildren : False
Children : System.__ComObject
UniqueDeviceName : uuid:4d696e69-444c-164e-9d44-3894ed0e1db5
FriendlyName : WAN Connection Device
Type : urn:schemas-upnp-org:device:WANConnectionDevice:1
PresentationURL :
ManufacturerName : NETGEAR
ManufacturerURL : http://www.netgear.com/
ModelName : NETGEAR Orbi Desktop AC3000 Router
ModelNumber : RBR50
Description : WANConnectionDevice on NETGEAR RBR50 Orbi Router
ModelURL : http://www.netgear.com/
UPC : 606449084528
SerialNumber : 5R21945T06DA4
Services : System.__ComObject

同样地,也适用于 “ParentDevice” 和 `Services”。我们来查看我的 NetGear 设备的更多服务。这次,我希望直接存取我的 NetGear 设备(这样比枚举所有设备快得多)。不过您需要知道它的唯一设备名。在我的例子中,”UniqueDeviceName” 的值是 “uuid:4d696e69-444c-164e-9d42-3894ed0e1db5”:

1
2
3
4
5
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
# the UDN is unique, so you need to find out the UDN for your device first
# you cannot use the UDN I used
$myNetgearSwitch = $UPnPFinder.FindByUDN('uuid:4d696e69-444c-164e-9d42-3894ed0e1db5')
$myNetgearSwitch

这次,几乎立即识别出我的设备。要列出它的服务,我只需要获取它的 “Services” 属性,并且 PowerShell 自动将该 COM 对象转为可见的属性:

1
2
3
4
5
PS> $myNetgearSwitch.Services

ServiceTypeIdentifier Id LastTransportStatus
--------------------- -- -------------------
urn:schemas-upnp-org:service:Layer3Forwarding:1 urn:upnp-org:serviceId:L3Forwarding1 0
评论

PowerShell 技能连载 - 探索即插即用设备(第 1 部分)

您可能已经生活在一个互联的智能家居,许多设备连接到您的网络。PowerShell 只需几行代码就可以帮助您找到您的设备:

1
2
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("upnp:rootdevice", 0)

请注意 UPnP 查找器组件需要一些时间来检测您的设备。结果看起来类似这样:

IsRootDevice     : True
RootDevice       : System.__ComObject
ParentDevice     :
HasChildren      : False
Children         : System.__ComObject
UniqueDeviceName : uuid:73796E6F-6473-6D00-0000-001132283f5e
FriendlyName     : Storage2 (DS414)
Type             : urn:schemas-upnp-org:device:Basic:1
PresentationURL  : http://192.168.2.107:5000/
ManufacturerName : Synology
ManufacturerURL  : http://www.synology.com/
ModelName        : DS414
ModelNumber      : DS414 5.1-5055
Description      : Synology NAS
ModelURL         : http://www.synology.com/
UPC              :
SerialNumber     : 001132283f5e
Services         : System.__ComObject

IsRootDevice     : True
RootDevice       : System.__ComObject
ParentDevice     :
HasChildren      : False
Children         : System.__ComObject
UniqueDeviceName : uuid:2f402f80-da50-11e1-9b23-001788ac0af1
FriendlyName     : BridgeOne (192.168.2.100)
Type             : urn:schemas-upnp-org:device:Basic:1
PresentationURL  : http://192.168.2.100/index.html
ManufacturerName : Signify
ManufacturerURL  : http://www.meethue.com/
ModelName        : Philips hue bridge 2015
ModelNumber      : BSB002
Description      : Philips hue Personal Wireless Lighting
ModelURL         : http://www.meethue.com/
UPC              :
SerialNumber     : 001788ac0af1
Services         : System.__ComObject
...

使用 Select-Object 来选择您感兴趣的属性:

1
2
3
4
$UPnPFinder = New-Object -ComObject UPnP.UPnPDeviceFinder
$UPnPFinder.FindByType("upnp:rootdevice", 0) |
Select-Object ModelName, FriendlyName, PresentationUrl |
Sort-Object ModelName

在我的家中,该列表看起来类似这样:

ModelName                          FriendlyName               PresentationURL
---------                          ------------               ---------------
AFTMM                              Tobias's 2nd Fire TV
AFTS                               Tobias's Fire TV
AFTT                               Tobias's 3rd Fire TV stick
DS414                              Storage2 (DS414)           http://192.168.2.107:5000/
NETGEAR Orbi Desktop AC3000 Router RBR50 (Gateway)            http://www.orbilogin.net/
Philips hue bridge 2015            BridgeOne (192.168.2.100)  http://192.168.2.100/index.html
Philips hue bridge 2015            BridgeWork (192.168.2.106) http://192.168.2.106/index.html
SoundTouch 20                      Bad
SoundTouch 30                      Portable
SoundTouch SA-4                    Garden
SoundTouch SA-4                    LivingRoom

我正在使用群晖 NAS,假设我忘了访问它的 URL,那么现在可以快速地找回它。我的飞利浦灯光系统也是这样:UPnP 查找器返回所有 hub 的 IP 地址。

请注意 UPnP 查找器只能返回和您的计算机连入相同网络的设备。如果您的设备 IP 地址是在其它子网,那么不能通过这种方式检测。

评论

PowerShell 技能连载 - 列出已安装的更新(第 2 部分)

在前一个技能中我们演示了如何通过 Windows Update 客户端获取当前已安装的更新。

可以对此列表进行润色,例如您可以使用哈希表创建计算的属性,提取默认情况下属于其他属性的信息,如知识库文章编号作为标题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$severity = @{
Name = 'Severity'
Expression = { if ([string]::IsNullOrEmpty($_.MsrcSeverity)) { 'normal' } else { $_.MsrcSeverity }}
}

$time = @{
Name = 'Time'
Expression = { $_.LastDeploymentChangeTime }
}

$kb = @{
Name = 'KB'
Expression = { if ($_.Title -match 'KB\d{6,9}') { $matches[0] } else { 'N/A' }}
}

$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSession.CreateupdateSearcher().Search("IsInstalled=1").Updates |
Select-Object $time, Title, $kb, Description, $Severity |
Out-GridView -Title 'Installed Updates'

结果显示在一个网格视图窗口中。如果您移除了 Out-GridView 那么信息看起来类似这样:

Time        : 9/10/2019 12:00:00 AM
Title       : 2019-09 Security Update for Adobe Flash Player for Windows 10 Version 1903 for
              x64-based Systems (KB4516115)
KB          : KB4516115
Description : A security issue has been identified in a Microsoft software product that could
              affect your system. You can help protect your system by installing this update
              from Microsoft. For a complete listing of the issues that are included in this
              update, see the associated Microsoft Knowledge Base article. After you install
              this update, you may have to restart your system.
Severity    : Critical

Time        : 10/8/2019 12:00:00 AM
Title       : Windows Malicious Software Removal Tool x64 - October 2019 (KB890830)
KB          : KB890830
Description : After the download, this tool runs one time to check your computer for infection by specific, prevalent malicious software (including Blaster, Sasser,
and Mydoom) and helps remove any infection that is found. If an infection is found, the tool will display a status report the next time that you start your computer. A new version of the tool will be offered every month. If you want to manually run the tool on your computer, you can download a copy from the Microsoft Download Center, or you can run an online version from microsoft.com. This tool is not a replacement for an antivirus product. To help protect your computer, you should use an antivirus product.
Severity    : normal

Time        : 10/8/2019 12:00:00 AM
Title       : 2019-10 Cumulative Update for .NET Framework 3.5 and 4.8 for Windows 10 Version
              1903 for x64 (KB4524100)
KB          : KB4524100
Description : Install this update to resolve issues in Windows. For a complete listing of the issues that are included in this update, see the associated Microsoft Knowledge Base article for more information. After you install this item, you may have to restart your computer.
Severity    : normal

Time        : 10/28/2019 12:00:00 AM
Title       : Update for Windows Defender Antivirus antimalware platform - KB4052623 (Version 4.18.1910.4)
KB          : KB4052623
Description : This package will update Windows Defender Antivirus antimalware platform’s components on the user machine.
Severity    : normal

...
评论

PowerShell 技能连载 - 列出已安装的更新(第 1 部分)

Get-Hotfix 只会列出操作系统相关的 hotfix:

1
Get-HotFix

实际上,它只是一个 WMI 查询的简单包装,结果是一样的:

1
Get-CimInstance -ClassName Win32_QuickFixEngineering

一个更简单更完整的方法是查询系统事件日志获取所有安装的更新:

1
2
3
4
5
6
7
8
9
10
Get-WinEvent @{
Logname='System'
ID=19
ProviderName='Microsoft-Windows-WindowsUpdateClient'
} | ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeCreated
Update = $_.Properties.Value[0]
}
}

显然,当系统事件日志清除之后,查询结果就不完整了。此外,该日志只是记录任何更新安装,因此随着时间的推移,新的更新可能取代旧的更新。

要保证获取到完整的已安装更新列表,您需要请求 Windows Update 客户端,从实际安装的更新中重建列表,这要消耗更多的时间:

1
2
3
4
$result = (New-Object -ComObject Microsoft.Update.Session).CreateupdateSearcher().Search("IsInstalled=1").Updates |
Select-Object LastDeploymentChangeTime, Title, Description, MsrcSeverity

$result | Out-GridView -Title 'Installed Updates'
评论