PowerShell 技能连载 - 从 Windows 中转储个人密码

Windows 有一个受保护的密码库,它可以在其中存储您的密码,因此您不必始终在 InternetExplorer 或 Edge 中手动输入密码。

如果您习惯了自动密码管理器,可能偶尔会忘记原始密码。下面是一个超级简单的 PowerShell 方法来转储存储在 Windows 密码库中的所有密码:

1
2
3
4
5
6
7
8
9
10
11
# important: this is required to load the assembly
[Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime]


(New-Object Windows.Security.Credentials.PasswordVault).RetrieveAll() |
ForEach-Object {
$_.RetrievePassword()
$_
} |
Select-Object -Property Username, Password, Resource |
Out-GridView

请注意,如果您从未在 InternetExplorer 或 Edge 中存储凭据,则不会得到任何结果。还请注意,此代码设计的工作方式:只有您只能检索密码,就像您在浏览器中访问网站并要求浏览器为您填写凭据一样。

转储所有存储的密码说明了为什么在您不在时始终锁定您的计算机是如此重要。如果您让您的计算机无人值守的话,任何人都可以运行此 PowerShell 代码来转储您的个人密码。

PowerShell 技能连载 - 通过 PowerShell 安装 Google Chrome

要下载并安装谷歌Chrome浏览器,只需几个常见的 PowerShell 命令组合:

1
2
3
4
5
$Installer = "$env:temp\chrome_installer.exe"
$url = 'http://dl.google.com/chrome/install/375.126/chrome_installer.exe'
Invoke-WebRequest -Uri $url -OutFile $Installer -UseBasicParsing
Start-Process -FilePath $Installer -Args '/silent /install' -Wait
Remove-Item -Path $Installer

PowerShell 技能连载 - 使用始终可见的弹出对话框

在上一个技能中,我们使用了一种古老的 COM 技术来显示带有内置超时的弹出框。除了对话框有时会被覆盖在 PowerShell 窗口下之外,这种方法运行得非常好。

通过一个鲜为人知的技巧,你可以确保对话框总是打开在所有其他窗口的顶部:

1
2
3
4
$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup("You can't cover me!", 5, 'Example', 17 + 4096)

"Choice: $value"

关键是在参数中添加 4096 来控制按钮和图标。这将该对话框转换为模态对话框:它保证在所有现有窗口之上打开,并且永远不会被覆盖。

使用哈希表来包装所有这些幻数,这又是一个好办法:

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
$timeoutSeconds = 5
$title = 'Example'
$message = "You can't cover me!"

$buttons = @{
OK = 0
OkCancel = 1
AbortRetryIgnore = 2
YesNoCancel = 3
YesNo = 4
RetryCancel = 5
}

$icon = @{
Stop = 16
Question = 32
Exclamation = 48
Information = 64
}

$clickedButton = @{
-1 = 'Timeout'
1 = 'OK'
2 = 'Cancel'
3 = 'Abort'
4 = 'Retry'
5 = 'Ignore'
6 = 'Yes'
7 = 'No'
}

$ShowOnTop = 4096

$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup($message, $timeoutSeconds, $title, $buttons.Ok + $icon.Exclamation + $ShowOnTop)

Switch ($clickedButton.$value)
{
'OK' { 'you clicked OK' }
'Timeout'{ 'you did not click anything, timeout occurred' }
}

PowerShell 技能连载 - 用哈希表提高代码可读性

也许你在过去偶然见过这样的代码:

1
2
3
4
$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup('Restart Computer?', 5, 'Important', 36)

"Choice: $value"

这段代码打开一个对话框,询问用户是否可以重启计算机。弹出对话框有一个内置超时设置,因此即使在无人值守的情况下运行,代码也不会停止。

但是,由于 PowerShell 使用的是一种旧的 COM 方法,它是由加密的 ID 来控制。用户无法理解 “36” 表示一个带有 YesNo 按钮和问号的对话框。那么如何转义返回的值呢?

哈希表可以以一种简单的方法来包装代码数字,并使代码更具可读性。请看:

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
$timeoutSeconds = 5
$title = 'Important'
$message = 'Restart Computer?'

$buttons = @{
OK = 0
OkCancel = 1
AbortRetryIgnore = 2
YesNoCancel = 3
YesNo = 4
RetryCancel = 5
}

$icon = @{
Stop = 16
Question = 32
Exclamation = 48
Information = 64
}

$clickedButton = @{
-1 = 'Timeout'
1 = 'OK'
2 = 'Cancel'
3 = 'Abort'
4 = 'Retry'
5 = 'Ignore'
6 = 'Yes'
7 = 'No'
}

$shell = New-Object -ComObject WScript.Shell
$value = $shell.Popup($message, $timeoutSeconds, $title, $buttons.YesNo + $icon.Question)

"Raw result: $value"
"Cooked result: " + $clickedButton.$value

Switch ($clickedButton.$value)
{
'Yes' { 'restarting' }
'No' { 'aborted' }
'Timeout'{ 'you did not make a choice' }
}

多亏了哈希表,代码使用 $buttons.YesNo + $icon.Question 而不是指定 “36”,而且一旦运行了代码(这样就定义了哈希表),甚至可以获得可用选项的智能感知。

同样,通过使用原始返回值作为哈希表的键,可以轻松地将返回代码转换为人类可读的格式。通过这种方式,您可以使用 switch 语句并为用户单击的按钮分配脚本块,而不必知道单个按钮代码。

PowerShell 技能连载 - 提高管道速度

PowerShell 管道在处理大量元素时往往比较慢。可能需要很多时间:

1
2
3
4
$result = 1..15000 |
ForEach-Object {
"Line $_"
}

一种更快的方法是用匿名脚本块代替 ForEach-Object,它会带来 200 倍的速度提升:

1
2
3
4
$result = 1..15000 |
& { process {
"Line $_"
}}

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()

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