PowerShell 技能连载 - 检测按键

PowerShell 可能需要知道当前是否按下了给定的键。这样,您的配置文件脚本就可以在 PowerShell 启动期间基于您所按下的键执行一些操作。例如,在启动 PowerShell 时按住 CTRL 键,配置文件脚本可以预加载某些模块或连接到服务器。

以下是 Powershell 检测按键的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# this could be part of your profile script

Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore

# assume the script is doing something
# (so you can get ready and press left Ctrl!)
Start-Sleep -Seconds 2

# choose the key you are after
$key = [System.Windows.Input.Key]::LeftCtrl
$isCtrl = [System.Windows.Input.Keyboard]::IsKeyDown($key)

if ($isCtrl)
{
'You pressed left CTRL, so I am now doing extra stuff'
}

PowerShell 技能连载 - 覆盖 Out-Default(第 3 部分)

高级的 PowerShell 用户常常发现他们在做以下三件事之一:

  • 他们运行前面的命令并添加 Get-Member,以了解产生的对象的更多信息
  • 他们运行前面的命令并添加 Select-Object * 来查看所有属性
  • 他们运行前面的命令并将其通过管道传输到 Out-GridView 来查看图形化结果

这三条都可以更容易地实现,并且在所有 PowerShell 中都可工作,包括控制台、PowerShell ISE,或 Visual Studio Code。只需要覆盖 Out-Default 并且监听按键。当按下特定按键及回车键时,Out-Default 将会自动为您执行以上额外任务:

  • 左方向键 + 回车按下时,对执行结果运行 Get-Member 并且将结果显示在网格视图中
  • 右方向键 + 回车按下时,将所有执行结果显示在一个网格试图窗口中,这样您可以接触到数据
  • TAB + 回车按下时,将对结果执行 Select-Object *,同时也将结果显示在网格视图中
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
cls
Write-Host ([PSCustomObject]@{
'Left Arrow + ENTER' = 'Show Member'
'Right Arrow + ENTER' = 'Echo results'
'Tab + ENTER' = 'Show all properties'
} | Format-List | Out-String)

function Out-Default
{
param(
[switch]
${Transcript},

[Parameter(ValueFromPipeline=$true)]
[psobject]
${InputObject})

begin
{
$scriptCmd = {& 'Microsoft.PowerShell.Core\Out-Default' @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore
$showGridView = [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::Right)
$showAllProps = [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::Tab)
$showMember = [System.Windows.Input.Keyboard]::IsKeyDown([System.Windows.Input.Key]::Left)

if ($showGridView)
{
$cmd = {& 'Microsoft.PowerShell.Utility\Out-GridView' -Title (Get-Date -Format 'HH:mm:ss') }
$out = $cmd.GetSteppablePipeline()
$out.Begin($true)

}
if ($showMember)
{

$cmd = {& 'Microsoft.PowerShell.Utility\Get-Member' | Select-Object -Property @{Name = 'Type';Expression = { $_.TypeName }}, Name, MemberType, Definition | Sort-Object -Property Type, {
if ($_.MemberType -like '*Property')
{ 'B' }
elseif ($_.MemberType -like '*Method')
{ 'C' }
elseif ($_.MemberType -like '*Event')
{ 'A' }
else
{ 'D' }
}, Name | Out-GridView -Title Member }
$outMember = $cmd.GetSteppablePipeline()
$outMember.Begin($true)

}

}

process
{
$isError = $_ -is [System.Management.Automation.ErrorRecord]

if ($showMember -and (-not $isError))
{
$outMember.Process($_)
}
if ($showAllProps -and (-not $isError))
{
$_ = $_ | Select-Object -Property *
}
if (($showGridView) -and (-not $isError))
{
$out.Process($_)
}
$steppablePipeline.Process($_)
}

end
{
$steppablePipeline.End()
if ($showGridView)
{
$out.End()
}
if ($showMember)
{
$outMember.End()
}

}
}

要移除这个覆盖函数,只需要运行:

1
PS C:\> del function:Out-Default

PowerShell 技能连载 - 覆盖 Out-Default(第 2 部分)

当您覆盖 Out-Default 指令,做一些有意义的事情时,您需要确保原始的行为没有丢失,而只是加入新的功能。以下是一个使用“代理函数“概念的示例。

原始输入被转发(代理)到原始的 Out-Default cmdlet。此外,函数打开自己的私有 Out-GridView 窗口并将输出回显到该窗口。

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
function Out-Default
{
param(
[switch]
$Transcript,

[Parameter(ValueFromPipeline=$true)]
[psobject]
$InputObject
)

begin
{
$pipeline = { Microsoft.PowerShell.Core\Out-Default @PSBoundParameters }.GetSteppablePipeline($myInvocation.CommandOrigin)
$pipeline.Begin($PSCmdlet)

$grid = { Out-GridView -Title 'Results' }.GetSteppablePipeline()
$grid.Begin($true)

}

process
{
$pipeline.Process($_)
$grid.Process($_)
}

end
{
$pipeline.End()
$grid.End()
}

}
`

要移除覆盖函数,只需要运行:

```powershell
PS C:\> del function:Out-Default

PowerShell 技能连载 - 覆盖 Out-Default(第 1 部分)

Out-Default 是一个隐藏的 PowerShell cmdlet,它在每个命令执行结束时被调用,并且将结果输出到控制台。您可以将这个函数覆盖为自己的版本,例如忽略所有输出,或输出一条“机密”消息:

1
2
3
4
function Out-Default
{
'SECRET!'
}

以下是移除自定义覆盖函数的方法:

1
PS C:\> del function:Out-Default

PowerShell 技能连载 - 用 Out-GridView 做为输出窗口

通常,Out-GridView 打开一个窗口并且显示所有通过管道传输到该 cmdlet 的内容:

1
PS C:\> Get-Service | Out-GridView

然而,通过一点技巧,Out-GridView 就会变得更强大。您可以随时将信息通过管道传输到相同的输出窗口。

First, get yourself an instance of Out-GridView that thinks it is running in a pipeline:
首先,获取一个 Out-GridView 的实例,然后认为它在一个管道中运行:

1
2
$pipeline = { Out-GridView }.GetSteppablePipeline()
$pipeline.Begin($true)

现在,您可以通过调用 “Process“ 来输出任何信息。每次调用 Process() 都好比将一个元素通过管道传给 cmdlet:

1
2
3
$pipeline.Process('Hello this is awesome!')
Start-Sleep -Seconds 4
$pipeline.Process('You can output any time...')

当操作完成时,调用 End() 结束管道:

1
$pipeline.End()

通过这种方式,您可以将信息记录到网格视图中,或者将其用作向用户显示结果的通用输出窗口。

PowerShell 技能连载 - 啤酒挑战结果:最短的密码分析代码

最近在 psconf.eu 上有一个挑战,用最短的代码来检查以前密码被入侵的频率。以下是结果(感谢 Daniel Rothgänger):

1
[Net.ServicePointManager]::SecurityProtocol='Tls12''P@ssw0rd'|sc p -N;$a,$b=(FileHash p -A SHA1|% h\*)-split'(?<=^.{5})';((irm api.pwnedpasswords.com/range/$a)-split"$b`:(\d+)")[1]

您可以用这段代码做为思维游戏来了解它的功能,或者简单地使用它:它接受一个密码(在我们的示例中是“P@ssw0rd”)并返回一个数字。这个数字是这个特定密码在以前的攻击中出现的频率。任何被看到的密码都被认为是不安全的。只有不返回数字的密码才是安全的。

PowerShell 技能连载 - 使用 GeoCoding:情感分析(第 5 部分)

某些地理编码 API 提供了复杂的情感分析,如下面的示例所示:

1
2
3
4
5
6
"Most important museums of Amsterdam are located on the Museumplein, located at the southwestern side of the Rijksmuseum." |
ForEach-Object -Begin {$url='https://geocode.xyz'
$null = Invoke-RestMethod $url -S session
} -Process {
Invoke-RestMethod $url -W $session -Method Post -Body @{scantext=$_;geoit='json';sentiment='analysis'}
}

一段文本被发送到 API,并且 API 分析了地理内容且输出文本中找到的地理位置的详细信息:

1
2
3
4
5
6
7
8
9
sentimentanalysis : @{allsentiments=; sentimentwords=; mainsentiment=}
longt : 4.88702
matches : 3
match : {@{longt=4.88355; location=RIJKSMUSEUM, AMSTERDAM, NL; matchtype=street;
confidence=1.0; MentionIndices=108,26; latt=52.35976}, @{longt=4.88334;
location=MUSEUMPLEIN, AMSTERDAM, NL; matchtype=street; confidence=1.0;
MentionIndices=55,26; latt=52.35747}, @{longt=4.89416; location=Amsterdam,NL;
matchtype=locality; confidence=0.4; MentionIndices=26; latt=52.36105}}
latt : 52.35943

PowerShell 技能连载 - 使用 GeoCoding:文本扫描(第 4 部分)

地理编码可以自动从文本中提取地理信息。这个例子还展示了向服务器提交信息的一种新方法:在下面的例子中,数据用的是 HTML 表单中使用的相同机制发布到服务器。这样做通常是为了避免使用 URL 编码大量数据,因为当使用 POST 时,数据在 request 的 header 而不是 URL 中传输。这也允许发送更多的数据:

1
2
3
4
5
6
7
'Ma io stasera volevo cenare al lume di candela, non cucinarci! #Milano #blackout' |
ForEach-Object -Begin {$url='https://geocode.xyz'
$null = Invoke-RestMethod $url -S session
} -Process {
Invoke-RestMethod $url -W $session -Method Post -Body @{scantext=$_;geoit='json'}

}

结果如下:

longt   matches match
-----   ------- -----
9.18067 1       {@{longt=9.18067; location=MILANO,IT; matchtype=locality; confidence=0.9; MentionIndices...

PowerShell 技能连载 - 使用 GeoCoding:将地址转换为经纬度(第 2 部分)

让我们从将地址转换为经纬度坐标开始。我们假设您已经阅读了前面的部分以完全理解代码示例。

下面是一些示例代码,它接受任意数量的地址,并返回它们的经纬度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'One Microsoft Way, Redmond',
'Bahnhofstrasse 12, Hannover, Germany' |
ForEach-Object -Begin {$url = 'https://geocode.xyz'
$null = Invoke-RestMethod $url -S session
} -Process {
$address = $_
$encoded = [Net.WebUtility]::UrlEncode($address)
Invoke-RestMethod "$url/${encoded}?json=1" -W $session|
ForEach-Object {
[PSCustomObject]@{
Address = $address
Long = $_.longt
Lat = $_.latt
}
}
}

结果看起来类似这样:

Address                              Long       Lat
-------                              ----       ---
One Microsoft Way, Redmond           -122.13061 47.64373
Bahnhofstrasse 12, Hannover, Germany 9.75195    52.37799