PowerShell 技能连载 - 使用 GeoCoding(第 1 部分)

去年,Google 改变了他们的服务条款,要求使用一个独立的 API 入口来使用他们的 geocode API。幸运的是,还有免费的替代,所以在这个迷你系列中我们想向您展示 PowerShell 可以如何处理地址和坐标。

所有这些功能需要 REST API 调用,所以无论需要通过 REST API 发送什么信息,都需要先编码成能做为 Web URL 的一部分发送的格式。

我们先聚焦在如何将文本编码为 REST API 调用的一部分:

1
2
3
$address = 'Bahnhofstrasse 12, Hannover'
$encoded = [Net.WebUtility]::UrlEncode($address)
$encoded

当您查看结果时,您可以很容易地看到进行了许多修改:

Bahnhofstrasse+12%2C+Hannover

PowerShell 技能连载 - 在 Web Request 中使用会话变量

有些时候,Web Request 在浏览器中可以正常工作,但是在 PowerShell 中却不能。例如,当您在浏览器中导航到 http://www.geocode.xyz/Bahnhofstrasse,Hannover?json=1,可以获得 JSON 格式的坐标地址。

而当在 PowerShell 中做同样操作时,会得到奇怪的异常:

1
2
$url = 'http://www.geocode.xyz/Bahnhofstrasse,Hannover?json=1'
Invoke-RestMethod -Uri $url

结果如下:

1
Invoke-RestMethod : { "success": false, "error": { "code": "006", "message": "Request Throttled." } }

这里的秘密是需要先获取会话状态,会话状态包括 cookie 和其它细节,然后再根据会话状态重新提交 Web Service 请求。以下是它的工作方式:

1
2
3
4
5
$url = 'http://www.geocode.xyz'
$urlLocation = "$url/Bahnhofstrasse,Hannover?json=1"

$null = Invoke-RestMethod -Uri $url -SessionVariable session
Invoke-RestMethod -Uri $urlLocation -WebSession $session

现在结果看起来正确了:

1
2
3
4
5
6
standard  : @{stnumber=1; addresst=Bahnhofstrasse; postal=30159; region=DE; prov=DE; city=Hannover;
countryname=Germany; confidence=0.8}
longt : 9.73885
alt :
elevation :
latt : 52.37418

PowerShell 技能连载 - Left Side of Comparison

当使用比较运算符时,请确保明确的部分放在左边。这是因为 PowerShell 查找运算符的左边并且可能会自动改变右侧的数据类型。并且,当左侧是数组类型时,比较运算符可以作为过滤器使用。

请查看差异:

1
2
3
4
5
6
7
$array = @()

'-'* 80
$array -eq $null
'-'* 80
$null -eq $array
'-'* 80

由于要比较数组,比较操作符的工作原理类似于过滤器,在第一个比较中不返回任何内容。而当您交换操作数,结果会返回 $false,这才是正确的答案:一个数组,甚至是一个空的数组,不等于 null。

PowerShell 技能连载 - 订阅锁定和解锁事件

当一个用户锁定或者解锁机器时,Windows 会发出事件。PowerShell 可以订阅这些事件并且进行操作,例如使用文字转语音引擎来发出问候。

但是还有许多有用的操作。您也许希望在锁定计算机时做清理操作,关闭所有的 Windows 资源管理器窗口,开始备份,或做其它事情。以下是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Start-LogMessage {
$null = Register-ObjectEvent -InputObject ([Microsoft.Win32.SystemEvents]) -SourceIdentifier SessSwitch -EventName "SessionSwitch" -Action {
Add-Type -AssemblyName System.Speech

$synthesizer = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer

switch($event.SourceEventArgs.Reason) {
'SessionLock'
{ $synthesizer.Speak("Have a good one, $env:username!") }
'SessionUnlock'
{ $synthesizer.Speak("Heya, nice to see you $env:username again!") }
}
}
}

function Stop-LogMessage {
$events = Get-EventSubscriber -SourceIdentifier SessSwitch
$jobs = $events | Select-Object -ExpandProperty Action
$events | Unregister-Event
$jobs | Remove-Job
}

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

Windows 更新客户端维护了它自己的已安装更新。下面的示例代码不是查询一般的系统事件日志,也不是主动搜索可能需要一些时间的更新,而是查询和读取 Windows Update 客户端的安装历史:

1
2
3
4
5
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(1,$HistoryCount) |
Select-Object Date, Title, Description, SupportUrl

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