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

Get-Hotfix 返回已安装的更新,但实际上只是 Win32 Win32_QuickFixEngineering WMI 类的一个包装器。它并不会返回所有已安装的更新。

一个更好的方法可能是查询事件日志:

1
2
3
4
5
6
7
Get-EventLog  -LogName System -InstanceId 19  |
ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeGenerated
Update = $_.ReplacementStrings[0]
}
}

虽然这可能不完整,并且事件日志项有可能被清除。唯一权威的答案可能来自 Windows Update 客户端,它实际上是查看系统中的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
$pattern = 'KB\d{6,9}'

$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateupdateSearcher()
$Updates = @($UpdateSearcher.Search("IsInstalled=1").Updates)
$Updates | ForEach-Object {
$kb = 'N/A'
if ($_.Title -match $pattern) { $kb = $matches[0] }
[PSCustomObject]@{
KB = $kb
Title = $_.Title
}
}

PowerShell 技能连载 - 寻找丢失的更新

PowerShell 可以使用 Windows Update 客户端相同逻辑查询缺少的更新:

1
2
3
4
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateupdateSearcher()
$Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates)
$Updates | Select-Object Title

下面是返回更新标题和知识库编号(如果可用)的更复杂的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateupdateSearcher()
$Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates)
$Updates |
ForEach-Object {
$pattern = 'KB\d{6,9}'
if ($_.Title -match $pattern)
{
$kb = $matches[0]
}
else
{
$kb = 'N/A'
}
[PSCustomObject]@{
Title = $_.Title
KB = $kb
}
} | Out-GridView

PowerShell 技能连载 - 谁执行了隐藏的程序?

有没有想过为什么 CPU 负载有时会这么高,或者为什么黑色的窗口会一闪而过?我们可以检查程序启动的事件日志,并找出什么时候什么程序自动启动了:

1
2
3
4
5
6
7
8
9
10
11
12
13
Get-EventLog -LogName System -InstanceId 1073748869 |
ForEach-Object {

[PSCustomObject]@{
Date = $_.TimeGenerated
Name = $_.ReplacementStrings[0]
Path = $_.ReplacementStrings[1]
StartMode = $_.ReplacementStrings[3]
User = $_.ReplacementStrings[4]


}
} | Out-GridView

PowerShell 技能连载 - 查找登录事件

假设您有管理员特权,以下是一个快速、简单地转储所有登录事件的方法。这样你就可以知道谁登录了一台特定的电脑,以及使用了哪种身份验证类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#requires -RunAsAdministrator

Get-EventLog -LogName Security -InstanceId 4624 |
ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeGenerated
LogonType = $_.ReplacementStrings[8]
Process = $_.ReplacementStrings[9]
Domain = $_.ReplacementStrings[5]
User = $_.ReplacementStrings[6]
Method = $_.ReplacementStrings[10]
Source = $_.Source

}
} | Out-GridView

PowerShell 技能连载 - 解析 URL

经常有一种情况,URL 重定向到另一个最终的 URL。这种情况下,如果您希望知道一个指定的 URL 究竟指向哪,可以用类似这样的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Resolve-Url
{
[CmdletBinding()]
param
(
[Parameter(Mandatory)]
[string]
$url
)

$request = [System.Net.WebRequest]::Create($url)
$request.AllowAutoRedirect=$false
$response = $request.GetResponse()
$url = $response.GetResponseHeader("Location")
$response.Close()
$response.Dispose()

return $url
}

例如,最新的 PowerShell 总是在这个 URL 发布:https://github.com/PowerShell/PowerShell/releases/latest

解析这个 URL,就可以获取最新的 URL。这是找到可用的最新 PowerShell 版本的快速方法:

1
2
3
4
5
6
7
8
9
10
11
PS C:\> Resolve-Url -url https://github.com/PowerShell/PowerShell/releases/latest
https://github.com/PowerShell/PowerShell/releases/tag/v6.2.1

PS C:\> ((Resolve-Url -url https://github.com/PowerShell/PowerShell/releases/latest) -split '/')[-1]
v6.2.1

PS C:\> [version](((Resolve-Url -url https://github.com/PowerShell/PowerShell/releases/latest) -split '/')[-1] -replace 'v')

Major Minor Build Revision
----- ----- ----- --------
6 2 1 -1

PowerShell 技能连载 - 查看下载文件的大小

当您用 PowerShell 从 internet 下载文件,您可能会想知道下载需要多少时间。您可以检查已下载的数据大小,而且在知道总下载尺寸的情况下可以计算进度百分比。

以下是得到文件尺寸的快速方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Get-DownloadSize
{
[CmdletBinding()]
param
(
[Parameter(Mandatory,ValueFromPipeline)]
[String]
$Url
)

process
{
$webRequest = [System.Net.WebRequest]::Create($Url)
$response = $webRequest.GetResponse()
$response.ContentLength
$response.Dispose()
}
}

以下是一个示例:

1
2
3
PS> "https://github.com/PowerShell/PowerShell/releases/download/v6.2.1/PowerShell-6.2.1-win-x64.zip" | Get-DownloadSize

58716786

PowerShell 技能连载 - 检查按键

有些时候如果一个脚本能检测按键,而不需要干预脚本和输入,那是很棒的事情。通过这种方式,您可以增加按住 SHIFT 键的逻辑,例如退出脚本或者启用详细日志。在用户数据脚本中,您可以在 PowerShell 启动时根据是否按下某些键加载模块以及进行其它调整。

这归结到一个问题:最好的以及最少干预的检测按键的方式是什么?以下是解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore

1..1000 | ForEach-Object {
"I am at $_"
$isDown = [Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::LeftShift)
if ($isDown)
{
Write-Warning "ABORTED!!"
break
}

Start-Sleep -Seconds 1
}

增加了两个缺省的程序集之后,您的脚本可以操作 Windows.Input.Keyboard 类。这个类有一个 IsKeyDown() 方法。它可以检测键盘的按键,并且当该按键当前处于按下状态时会返回 $true

以上示例代码持续运行知道用户按下左侧的 SHIFT 键。

PowerShell 技能连载 - 异步使用 FileSystemWatcher

在前一个技能中我们介绍了 FileSystemWatcher 对象以及如何用它监控文件夹的变化。不过,为了不错过所有变化,需要使用异步的方法,类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
$FileSystemWatcher.Path = "$home\Desktop"
$FileSystemWatcher.IncludeSubdirectories = $true
$FileSystemWatcher.EnableRaisingEvents = $true
Register-ObjectEvent -InputObject $FileSystemWatcher -SourceIdentifier Monitoring1 -EventName Created -Action {

$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated

Write-Host $Object -ForegroundColor Green
}

在这个例子中,当检测到任何一个变化事件,就会触发一个事件,并且 FileSystemWatcher 继续监听。由 PowerShell 负责响应这个事件,并且它使用一个事件处理器来响应。事件处理器是在后台执行的。

如果不想使用后台事件管理器,请运行这行代码:

1
Get-EventSubscriber -SourceIdentifier Monitoring1 | Unregister-Event

请注意每个支持的变更类型会发出不同的事件。在这个例子中,我们关注 “Created“ 事件,当创建新文件或新文件夹的时候会发出这个事件。要响应其它变化类型,请添加更多的事件处理器。这将返回所有支持的事件名称:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS C:\> $FileSystemWatcher | Get-Member -MemberType *Event


TypeName: System.IO.FileSystemWatcher

Name MemberType Definition
---- ---------- ----------
Changed Event System.IO.FileSystemEventHandler Changed(System.Object, System.IO.FileSystemEventArgs)
Created Event System.IO.FileSystemEventHandler Created(System.Object, System.IO.FileSystemEventArgs)
Deleted Event System.IO.FileSystemEventHandler Deleted(System.Object, System.IO.FileSystemEventArgs)
Disposed Event System.EventHandler Disposed(System.Object, System.EventArgs)
Error Event System.IO.ErrorEventHandler Error(System.Object, System.IO.ErrorEventArgs)
Renamed Event System.IO.RenamedEventHandler Renamed(System.Object, System.IO.RenamedEventArgs)

PowerShell 技能连载 - 同步使用 FileSystemWatcher

以下是一段演示 PowerShell 如何用 FileSystemWatcher 同步地监控一个包含字文件夹的文件夹变化:

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
$folder = $home
$filter = '*'


try
{
$fsw = New-Object System.IO.FileSystemWatcher $folder, $filter -ErrorAction Stop
}
catch [System.ArgumentException]
{
Write-Warning "Oops: $_"
return
}

$fsw.IncludeSubdirectories = $true
$fsw.NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'

do
{
$result = $fsw.WaitForChanged([System.IO.WatcherChangeTypes]::All, 1000)
if ($result.TimedOut) { continue }

$result
Write-Host "Change in $($result.Name) - $($result.ChangeType)"

} while ($true)

这段代码将会监测用户配置文件中 FileNameLastWrite 属性的变化。

一个同步的监听器会导致 PowerShell 处于忙碌状态,所以要退出监听,需要设置一个 1000ms 的超时值,PowerShell 将会将控制权返回给你,而如果您没有按下 CTRL+C,则循环会继续。

请注意同步的 FileSystemWatcher 可能会错过改变:正好当 WaitForChange() 返回时一个文件发生改变,那么在下一次调用 WaitForChange() 之前的文件变化都会丢失。

如果不想错过任何变化,请使用异步的方法(请见下一个技能)。

PowerShell 技能连载 - 使用代理服务器的缺省凭据

如果您的公司使用一个需要身份认证的代理服务器,PowerShell 可能有时候无法访问 Internet。您可能需要通知代理服务器使用凭据缓存中的缺省凭据:

1
[System.Net.WebRequest]::DefaultWebProxy.Credentials=[System.Net.CredentialCache]::DefaultCredentials