PowerShell 技能连载 - 等待进程退出

有时候,一个 PowerShell 脚本需要等待外部进程结束。以下是一些用户的做法:

1
2
3
4
5
$processNameToWaitForExit = 'notepad'
do
{
Start-Sleep -Seconds 1
} while (Get-Process -Name $processNameToWaitForExit -ErrorAction SilentlyContinue)

这种做法不太理想,因为它至少等待了一秒钟,即便进程已经不在运行了。以下是一个更好的方法:

1
2
$processNameToWaitForExit = 'notepad'
Wait-Process -Name $processNameToWaitForExit -ErrorAction SilentlyContinue

不仅代码更短,Wait-Process 也支持超时时间。如果等待的时间过长,您可以通过超时时间来结束等待。

PowerShell 技能连载 - 对启用 PIN 的用户使用 PowerShell Remoting

如果您设置了 PIN 用来登录您的电脑,对您自己的机器使用 PowerShell remoting 可能会失败,提示如下奇怪的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\>  Invoke-Command { "Hello" } -ComputerName $env:computername
[DESKTOP-7AAMJLF] Connecting to remote server DESKTOP-7AAMJLF failed with the following error message : WinRM cannot process the request. The following error with errorcode 0x8009030e occurred while using Negotiate authentication: A specified logon session does not exist. It may already have been terminated.
Possible causes are:
-The user name or password specified are invalid.
-Kerberos is used when no authentication method and no user name are specified.
-Kerberos accepts domain user names, but not local user names.
-The Service Principal Name (SPN) for the remote computer name and port does not exist.
-The client and remote computers are in different domains and there is no trust between the two domains.
After checking for the above issues, try the following:
-Check the Event Viewer for events related to authentication.
-Change the authentication method; add the destination computer to the WinRM TrustedHosts configuration setting or use HTTPS transport.
Note that computers in the TrustedHosts list might not be authenticated.
-For more information about WinRM configuration, run the following command: winrm help config. For more information, see the
about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (DESKTOP-7AAMJLF:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : 1312,PSSessionStateBroken

要解决这个问题,您可以有两个选择:

  • 设置一个使用密码的用户账户(需要本地管理员权限)。然后,运行 Invoke-Command 的时候使用 -Credential 参数,然后指定账户和密码。
  • 如果您的电脑没有加入域,那么您需要启用 Negotiate 认证来进行 PowerShell remoting 操作。,并且使用机器的 IP 地址而不是计算机名。

PowerShell 技能连载 - 按属性值分割结果

如果您使用 PowerShell 远程操作来接收远程机器的信息,您可以指定多个计算机名(扇出)。PowerShell 会自动逐台访问所有的机器,这样可以节省很多时间(当然,这些操作的前提是设置并启用了 PowerShell,这里不再赘述)。

返回的结果顺序是随机的,因为所有被访问的机器都会在它们准备好数据的时候返回各自的信息。

要将结果数据按每台机器分割,请使用 Group-Object 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$pc1 = $env:computername
$pc2 = '192.168.2.112'

$code =
{
Get-Service | Where-Object Status -eq Running
}

# get all results
$result = Invoke-Command -ScriptBlock $code -ComputerName $pc1, $pc2

# separate per computer
$groups = $result | Group-Object -Property PSComputerName -AsHashTable
$groups

# access per computer results separately
$groups.$pc1
$groups.$pc2

当您指定了 -AsHashTable 参数时,Groutp-Object 创建了一个以计算机名为键的哈希表。通过这种方法,您可以并发执行操作以节约时间,并仍然按每台机器来区分数据。

PowerShell 技能连载 - 分析结果出现次数(不浪费内存)

Group-Object 可以基于共享的属性值对对象分组,但请不要忘记使用 -NoElement 参数来忽略实际的对象而只返回出现次数。

这行简单的代码告诉您指定文件夹中有哪些文件类型:

1
Get-ChildItem -Path c:\Windows -File | Group-Object -Property Extension -NoElement

结果看起来如下:

1
2
3
4
5
6
7
8
Count Name
----- ----
11 .exe
1 .dat
9 .log
4 .xml
1 .txt
...

指定了 -NoElement 之后,您可以节约相当客观的内存,因为原对象不再包括在结果中。

PowerShell 技能连载 - 查找不合规的命令动词

Cmdlet 和函数只能用认可的动词以便于用户查找命令,并且保持一致性。

以下是一个快速的审计代码,能够显示不符合这个规定的所有命令:

1
2
3
4
$approved = Get-Verb | Select-Object -ExpandProperty Verb

Get-Command -CommandType Cmdlet, Function |
Where-Object { $approved -notcontains $_.Verb }

这里返回的是所有不符合规定或根本没有命令动词的 cmdlet 和函数。

PowerShell 技能连载 - 最常用的动词

让我们来看看您的 PowerShell 中哪个命令动词是最常用的:

1
2
3
Get-Command -CommandType cmdlet, function |
Group-Object -Property Verb |
Sort-Object -Property Count -Descending

这是我们系统的输出结果:

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
89
90
91
92
93
94
95
96
97
98
Count Name                      Group
----- ---- -----
456 Get {Get-AppBackgroundTask, Get-AppvVirtualProcess...
210 Set {Set-AssignedAccess, Set-AutologgerConfig, Set...
120 Remove {Remove-AutologgerConfig, Remove-BCDataCacheEx...
102 New {New-AutologgerConfig, New-DAEntryPointTableIt...
72 Enable {Enable-BCDistributed, Enable-BCDowngrading, E...
70 {A:, ABC, AfterAll, AfterAll...}
67 Add {Add-BCDataCacheExtension, Add-BitLockerKeyPro...
65 Disable {Disable-BC, Disable-BCDowngrading, Disable-BC...
28 Start {Start-Animation, Start-AppBackgroundTask, Sta...
26 Update {Update-Disk, Update-DscConfiguration, Update-...
24 Clear {Clear-AssignedAccess, Clear-BCCache, Clear-Bi...
22 Export {Export-Application, Export-BCCachePackage, Ex...
22 Invoke {Invoke-AsWorkflow, Invoke-Background, Invoke-...
20 Import {Import-BCCachePackage, Import-BCSecretKey, Im...
19 Stop {Stop-DscConfiguration, Stop-Dtc, Stop-DtcTran...
19 Rename {Rename-DAEntryPointTableItem, Rename-MaskingS...
19 Test {Test-Ancestor, Test-Descendent, Test-Dtc, Tes...
16 Register {Register-ClusteredScheduledTask, Register-Dns...
15 ConvertTo {ConvertTo-DataTemplate, ConvertTo-GridLength,...
14 Write {Write-DtcTransactionsTraceSession, Write-Prin...
14 Show {Show-Clock, Show-Details, Show-NetFirewallRul...
14 Reset {Reset-BC, Reset-DAClientExperienceConfigurati...
13 Unregister {Unregister-AppBackgroundTask, Unregister-Clus...
11 Out {Out-GridViewVertical, Out-Notepad, Out-Voice,...
11 Copy {Copy-DependencyProperty, Copy-NetFirewallRule...
9 Send {Send-EtwTraceSession, Send-PSCONFConfirmation...
9 Find {Find-Command, Find-DscResource, Find-Module, ...
9 ConvertFrom {ConvertFrom-SddlString, ConvertFrom-TypeToScr...
7 Save {Save-Module, Save-NetGPO, Save-NetworkSwitchC...
7 Repair {Repair-FileIntegrity, Repair-VirtualDisk, Rep...
7 Debug {Debug-FileShare, Debug-MMAppPrelaunch, Debug-...
7 Format {Format-Hex, Format-Volume, Format-Custom, For...
6 Suspend {Suspend-BitLocker, Suspend-PrintJob, Suspend-...
6 Install {Install-Dtc, Install-ISEPreviewShortcut, Inst...
6 Publish {Publish-BCFileContent, Publish-BCWebContent, ...
6 Resume {Resume-BitLocker, Resume-PrintJob, Resume-Bit...
6 Move {Move-Control, Move-SmbWitnessClient, Move-App...
5 Restore {Restore-DscConfiguration, Restore-NetworkSwit...
5 Restart {Restart-NetAdapter, Restart-PcsvDevice, Resta...
5 Select {Select-Date, Select-UIType, Select-Object, Se...
5 Mount {Mount-DiskImage, Mount-AppvClientConnectionGr...
4 Uninstall {Uninstall-Dtc, Uninstall-Module, Uninstall-Sc...
4 Disconnect {Disconnect-IscsiTarget, Disconnect-VirtualDis...
4 Connect {Connect-IscsiTarget, Connect-VirtualDisk, Con...
4 Receive {Receive-DtcDiagnosticTransaction, Receive-Job...
4 Unblock {Unblock-FileShareAccess, Unblock-SmbShareAcce...
4 Assert {Assert-MockCalled, Assert-MockCalled, Assert-...
4 Wait {Wait-Debugger, Wait-Event, Wait-Job, Wait-Pro...
3 Complete {Complete-BitsTransfer, Complete-DtcDiagnostic...
3 Resize {Resize-Partition, Resize-StorageTier, Resize-...
3 Optimize {Optimize-StoragePool, Optimize-Volume, Optimi...
3 Initialize {Initialize-Disk, Initialize-EventHandler, Ini...
3 Expand {Expand-Archive, Expand-WindowsCustomDataImage...
3 Dismount {Dismount-DiskImage, Dismount-AppxVolume, Dism...
3 Close {Close-Control, Close-SmbOpenFile, Close-SmbSe...
3 Convert {Convert-Alias, Convert-Path, Convert-String}
2 Use {Use-Transaction, Use-WindowsUnattend}
2 Undo {Undo-DtcDiagnosticTransaction, Undo-Transaction}
2 Trace {Trace-Command, Trace-SteroidsOutput}
2 Split {Split-Path, Split-WindowsImage}
2 Resolve {Resolve-DnsName, Resolve-Path}
2 Measure {Measure-Command, Measure-Object}
2 Join {Join-DtcDiagnosticResourceManager, Join-Path}
2 Exit {Exit-PSHostProcess, Exit-PSSession}
2 Read {Read-PrinterNfcTag, Read-Host}
2 Block {Block-FileShareAccess, Block-SmbShareAccess}
2 Enter {Enter-PSHostProcess, Enter-PSSession}
2 Revoke {Revoke-FileShareAccess, Revoke-SmbShareAccess}
2 Do {Do-Something, Do-SomethingWeird}
2 Sync {Sync-NetIPsecRule, Sync-AppvPublishingServer}
2 Edit {Edit-StringList, Edit-CIPolicyRule}
2 Hide {Hide-UIElement, Hide-VirtualDisk}
2 Grant {Grant-FileShareAccess, Grant-SmbShareAccess}
1 Unlock {Unlock-BitLocker}
1 Unpublish {Unpublish-AppvClientPackage}
1 Unprotect {Unprotect-CmsMessage}
1 Open {Open-NetGPO}
1 Lock {Lock-BitLocker}
1 Tee {Tee-Object}
1 Switch {Switch-Certificate}
1 Sort {Sort-Object}
1 Backup {Backup-BitLockerKeyProtector}
1 Protect {Protect-CmsMessage}
1 Compress {Compress-Archive}
1 Pop {Pop-Location}
1 Merge {Merge-CIPolicy}
1 Encrypt {Encrypt-Text}
1 Limit {Limit-EventLog}
1 Group {Group-Object}
1 ForEach {ForEach-Object}
1 Confirm {Confirm-SecureBootUEFI}
1 Decrypt {Decrypt-Text}
1 Compare {Compare-Object}
1 Checkpoint {Checkpoint-Computer}
1 Push {Push-Location}
1 Where {Where-Object}

更有趣的是,以下是 PowerShell cmdlet 前六个最常用的动词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> Get-Command -CommandType cmdlet, function  |
Group-Object -Property Verb |
Sort-Object -Property Count -Descending |
Where-Object { $_.Name } |
Select-Object -First 6 -Property Count, Name

Count Name
----- ----
456 Get
210 Set
120 Remove
102 New
72 Enable
67 Add

所以说这头六个动词只占动词总数的 6%,但是占了所有命令的 60% 以上。

PowerShell 技能连载 - 安全地对文本加解密

当您加密保密信息时,主要的问题是要寻找一个合适的密钥。一个特别安全的密钥是您的 Windows 身份,它和您的计算机身份绑定。这可以用来在特定的机器上加密敏感的个人信息。

以两个函数演示了如何实现:

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
function Decrypt-Text
{

param
(
[String]
[Parameter(Mandatory,ValueFromPipeline)]
$EncryptedText
)
process
{
$secureString = $EncryptedText | ConvertTo-SecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
}
}

function Encrypt-Text
{

param
(
[String]
[Parameter(Mandatory,ValueFromPipeline)]
$Text
)
process
{
$Text |
ConvertTo-SecureString -AsPlainText -Force |
ConvertFrom-SecureString
}
}

'PowerShell Rocks' | Encrypt-Text
'Hello, World!' | Encrypt-Text | Decrypt-Text

您可以将密文安全地保存到文件里。只有您可以读取并解密该文件,而且只能在加密用的电脑上完成。

PowerShell 技能连载 - 同时使用 -Force 和 -WhatIf 时请注意

-WhatIf 通用参数可以打开模拟模式,这样一个 cmdlet 执行的时候并不会改变任何东西,而是汇报它“将会”改变什么。它能工作得很好, 除非开发者没有正确地实现 -WhatIf

有一种比较少见的情况:当您同时指定了 -Force-WhatIf 参数,正确的结果是 -WhatIf 具有更高的优先级。有一些开发者过于关注 -Force 的功能,而让 -Force 优先级更高。例如请试试 Remove-SmbShare

PowerShell 技能连载 - 创建文件共享

在 Server 2012 R2 和 Windows .1 中,有许多有用的新模块,包含了许多新的 cmdlet,例如 New-SmbShare 可以快速地创建新的文件共享。

如果您没有这些 cmdlet,您通常可以使用 WMI。那需要更多的研究和搜索,但是一旦您有了一个代码模板,它就能很好地工作了。

例如要以管理员身份创建一个新的文件共享,试试以下代码:

1
2
3
4
5
6
7
$share = [wmiclass]"Win32_Share"
$path = 'c:\logs'
$name = 'LogShare'
$maxallowed = 10
$description = 'Place log files here'

$share.Create( $path, $name, 0, $maxallowed,$description,$null,$null)

PowerShell 技能连载 - 使用自定义作用域来屏蔽任何输出

昨天我们看了自定义作用域能够自动还原变量并在您的代码之后清除现场。

自定义作用域也可以用来忽略域里任何一段代码输出的任何结果。要实现它,请使用这样的解构:$null = .{[code]}。无论您在方括号里执行什么代码,您创建的所有的变量和函数在域外都能使用,但是不会产生任何输出。

让我们看看这个函数:

1
2
3
4
5
6
7
function Out-Voice ($Text)
{
$sapi = New-Object -ComObject Sapi.SpVoice
$sapi.Speak($Text)
}

Out-Voice -Text 'Hello, Dude.'

当你运行它时,它将能播放语音,但也输出了数字“1”。所以 Speak() 方法会造成这样的现象——当您的代码变得庞大而复杂时,有许多地方在输出不必要的数字。

以下是一个极简单的“补丁”函数能产生相同的小郭,但是保证不会返回任何值:

1
2
3
4
5
6
7
8
9
function Out-Voice ($Text)
{
$null = . {
$sapi = New-Object -ComObject Sapi.SpVoice
$sapi.Speak($Text)
}
}

Out-Voice -Text 'Hello, Dude.'