PowerShell 技能连载 - 执行策略和下载的脚本文件

当您从 internet 下载了一个文件,它可能会被 Windows 标记(通过 NTFS 流),并且 PowerShell 可能会拒绝执行它:

1
2
3
4
5
6
7
8
9
PS> & "$home\desktop\Rick.ps1"
& : File C:\Users\tobwe\desktop\Rick.ps1 cannot be loaded. The file C:\Users\tobwe\desktop\Rick.ps1 is not digitally signed. You cannot run this script on the
current system. For more information about running scripts and setting execution policy, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:3
+ & "$home\desktop\Rick.ps1"
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SecurityError: (:) [], PSSecurityException
+ FullyQualifiedErrorId : UnauthorizedAccess

通常,当执行策略没有设置,或者设成 “RemoteSigned” 的时候会出现这种情况。这是普通 PowerShell 用户推荐的设置。以下是启用设置的方法:

1
PS> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

当启用以后,您可以运行任何本地脚本文件,或域之内的网络文件,但您不再能运行标记为“下载”的脚本,或从您域之外的网络位置下载的脚本。

要运行屏蔽的脚本,以下是您的选项:

  • 取消对该文件的屏蔽,主要是通过打开它的属性对话框,单击“解除锁定”
  • 使用 Unblock-File
  • 将文件内容拷贝到一个新的文件
  • 将执行策略设为 “Bypass”
  • 用不会标记下载文件的浏览器来下载,或用 Invoke-WebRequest 来下载:
1
PS> Invoke-WebRequest -Uri "http://bit.ly/e0Mw9w" -UseBasicParsing -OutFile  "$home\Desktop\Rick.ps1"

Invoke-WebRequest 不会对下载的文件做标记,而且允许通过执行策略,这是挺令人意外的行为。

PowerShell 技能连载 - 下载脚本文件的最佳方式

有时候,PowerShell 脚本的作者将脚本放在直接下载的服务器上。让我们寻找一种最有效的通过 PowerShell 下载文本文件的方法。我们将以 PowerShell Team 成员 Lee Holmes 发布的著名的 “Dancing Rick ASCII” 脚本作为我们的例子。它的下载地址位于这里(需要翻墙):

http://bit.ly/e0Mw9w

当在浏览器中打开时,您将会见到以纯文本方式显示的 PowerShell 源代码,并且原始的 URL 将会显示在浏览器的地址栏里:

http://www.leeholmes.com/projects/ps_html5/Invoke-PSHtml5.ps1

许多用户像这样使用 .NET 方法来下载文本文件:

1
2
3
4
5
6
7
# download code
$url = "http://bit.ly/e0Mw9w"
$webclient = New-Object Net.WebClient
$code = $webclient.DownloadString($url)

# output code
$code

其实并不需要这样,因为 Invoke-WebRequest 是对该对象的更好的封装:

1
2
3
4
# download code
$url = "http://bit.ly/e0Mw9w"
$page = Invoke-WebRequest -Uri $url -UseBasicParsing
$code = $page.Content

通过它的参数,它能直接支持代理并且支持凭据。

还有一个更方便的 cmdlet Invoke-RestMethod。它基本上做的是相同的是,不过返回的数据是文本,JSON,或 XML:

1
2
3
# download code
$url = "http://bit.ly/e0Mw9w"
$code = Invoke-RestMethod -Uri $url -UseBasicParsing

假设您信任这段代码,相信它不会损害您的系统,您可以这样执行它:

1
2
# invoke the code
Invoke-Expression -Command $code

或者,您可以将它保存到磁盘,并且以一个普通的 PowerShell 脚本的方式执行它:

1
2
3
4
5
6
7
8
# download code
$url = "http://bit.ly/e0Mw9w"
$code = Invoke-RestMethod -Uri $url -UseBasicParsing

# save to file and run
$outPath = "$home\Desktop\dancingRick.ps1"
$code | Set-Content -Path $outPath -Encoding UTF8
Start-Process -FilePath powershell -ArgumentList "-noprofile -noexit -executionpolicy bypass -file ""$outPath"""

如果您打算先将内容保存到一个文件,那么 Invoke-WebRequest 是一个更好的选择,因为它可以直接将内容保存到文件:

1
2
3
4
5
# download code
$url = "http://bit.ly/e0Mw9w"
$outPath = "$home\Desktop\dancingRick.ps1"
$code = Invoke-WebRequest -Uri $url -UseBasicParsing -OutFile $outPath
& $outPath

您可以通过调用操作符 (&) 在您自己的 PowerShell 会话中运行下载的文件,而不是使用 Start-Process。如果执行失败,通常是因为您的执行策略不允许 PowerShell 脚本。请按如下方法改变设置,然后重试:

1
PS> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned

PowerShell 技能连载 - 对 Cmdlet 的输出着色

从 PowerShell 5.1 开始,PowerShell 控制台支持 VT 转义序列,它可以用于对控制台文本定位和格式化。请注意它只对控制台有效,而对 PowerShell ISE 无效。另外还请注意您需要 Windows 10 或者类似 ConEmu 等模拟器。

当您向 Select-Object 命令传入一个哈希表时,该哈希表能够产生“计算的”列。它提供了两块信息:名字(列名)和表达式(一个生成列内容的脚本块)。

这对为 cmdlet 的输出着色十分有用。只需要创建一个添加彩色的 VT 转义序列的表达式即可。在以下例子中,一些文件类型被着色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ColoredName = @{
Name = "Name"
Expression =
{
switch ($_.Extension)
{
'.exe' { $color = "255;0;0"; break }
'.log' { $color = '0;255;0'; break }
'.ini' { $color = "0;0;255"; break }
default { $color = "255;255;255" }
}
$esc = [char]27
"$esc[38;2;${color}m$($_.Name)${esc}[0m"
}
}

Get-ChildItem $env:windir |
Select-Object -Property Mode, LastWriteTime, Length, $ColoredName

PowerShell 技能连载 - 在 PowerShell 控制台中使用颜色

从 PowerShell 5.1 开始,PowerShell 控制台支持 VT 转义序列,它可以用于对控制台文本定位和格式化。请注意它只对控制台有效,而对 PowerShell ISE 无效。另外还请注意您需要 Windows 10 或者类似 ConEmu 等模拟器。

要对一段文字着色,您在任何 PowerShell 版本中都可以使用 Write-Host 和它的 -ForegroundColor-BackgroundColor 属性:

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
foreach($color1 in (0..15))
{
foreach($color2 in (0..15))
{
Write-Host -ForegroundColor ([ConsoleColor]$color1) -BackgroundColor ([ConsoleColor]$color2) -Object "X" -NoNewline
}

Write-Host
}



XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX

通过使用 VT 转义序列,您可以使用范围更广的颜色。可以将前景色和背景色设为任意的 RGB 颜色。每个通道可以有 8 比特 (0-255):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Red/Green/Blue foreground
$r = 0
$g = 20
$b = 255

# Red/Green/Blue background
$rback = 255
$gback = 100
$bback = 0

$esc = [char]27

# compose escape sequence
"$esc[38;2;$r;$g;$b;48;2;$rback;$gback;${bback}mCOLORFUL TEXT$esc[0m"

以下代码创建所有可能的颜色,并且将它们显示在由 256 个字符组成的一行上,并且每一行叠加在前一行之上,这样可以获得一个渐变发光的效果:

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
$esc = [char]27
$setCursorTop = "$esc[0;0H"

foreach($rback in (0..255))
{
foreach($gback in (0..255))
{
foreach($bback in (0..255))
{

foreach($r in (0..255))
{
foreach($g in (0..255))
{
[System.Text.StringBuilder]$line = ""
foreach($b in (0..255))
{
$null = $line.Append("$esc[38;2;$r;$g;$b;48;2;$rback;$gback;${bback}mX$esc[0m")
}

$text = $line.ToString()
Write-Host "$setCursorTop$Text"
}

Write-Host
}
}
}
}

PowerShell 技能连载 - PowerShell 控制台光标定位

从 PowerShell 5.1 开始,PowerShell 控制台支持 VT 转义序列,它可以用于对控制台文本定位和格式化。请注意它只对控制台有效,而对 PowerShell ISE 无效。另外还请注意您需要 Windows 10 或者类似 ConEmu 等模拟器。

VT 转义序列可以将控制台光标设置到控制台窗口的任意位置。例如,要将光标设置到左上角,请使用以下代码:

1
2
3
4
$esc = [char]27
$setCursorTop = "$esc[0;0H"

Write-Host "${setCursorTop}This always appears in line 0 and column 0!"

当您运行这段代码时,文字总是定位在第 0 行 第 0 列。您可以使用这种技术来创建您自己的进度指示器——只需要记住:这一切只在控制台窗口中有效,在 PowerShell ISE 中无效。

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
function Show-CustomProgress
{
try
{
$esc = [char]27

# let the caret move to column (horizontal) pos 12
$column = 12
$resetHorizontalPos = "$esc[${column}G"
$gotoFirstColumn = "$esc[0G"

$hideCursor = "$esc[?25l"
$showCursor = "$esc[?25h"
$resetAll = "$esc[0m"

# write the template text
Write-Host "${hideCursor}Processing %." -NoNewline

1..100 | ForEach-Object {
# insert the current percentage
Write-Host "$resetHorizontalPos$_" -NoNewline
Start-Sleep -Milliseconds 100
}
}
finally
{
# reset display
Write-Host "${gotoFirstColumn}Done. $resetAll$showCursor"
}
}

运行这段代码后,执行 Show-CustomProgress 命令,您将会见到一个不断增长的自定义进度指示器。控制台隐藏了闪烁的光标提示。当进度指示器结束时,或者当按下 CTRL + C 时,进度指示器将会隐藏,

PowerShell 技能连载 - 在同一行输出日志信息

从 PowerShell 5.1 开始,PowerShell 控制台支持 VT 转义序列,它可以用于对控制台文本定位和格式化。请注意它只对控制台有效,而对 PowerShell ISE 无效。另外还请注意您需要 Windows 10 或者类似 ConEmu 等模拟器。

VT 转义序列可以将控制台光标设置到当前行的任意位置。通过这种方式,您可以方便地创建一个函数,输出状态或者日志信息到控制台。并且每条新信息覆盖之前的信息而不是增加新的行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Write-ConsoleMessage([string]$Message)
{
$esc = [char]27
$consoleWidth = [Console]::BufferWidth
$outputText = $Message.PadRight($consoleWidth)
$gotoFirstColumn = "$esc[0G"
Write-Host "$gotoFirstColumn$outputText" -NoNewline
}

function test
{
Write-ConsoleMessage -Message 'Starting...'
Start-Sleep -Seconds 1
Write-ConsoleMessage -Message 'Doing something!'
Start-Sleep -Seconds 1
Write-ConsoleMessage -Message 'OK.'
}

test

PowerShell 技能连载 - 为控制台输出加下划线

从 PowerShell 5.1 开始,PowerShell 控制台支持 VT 转义序列,它可以用于对控制台文本定位和格式化。请注意它只对控制台有效,而对 PowerShell ISE 无效。另外还请注意您需要 Windows 10 或者类似 ConEmu 等模拟器。

要实验这个功能,请在 PowerShell 控制台中运行以下代码:

1
2
$esc = [char]27
"$esc[4mOutput is now underlined!"

PowerShell 现在对所有的输出加下划线。输入文本并没有加下划线(由于 PSReadLine 处理所有输入文本的格式)。

要关闭格式化功能,请运行这段代码:

1
2
$esc = [char]27
"$esc[0mReset"

我们将会在未来的技能中介绍更多的控制台文字格式化技术。以下是 VT 转义序列的更深入介绍:https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences.

PowerShell 技能连载 - 安全地嵌入变量

当您在 PowerShell 中使用双引号时,您可以向字符串中增加变量,PowerShell 能自动将它们替换成它们的值——这并不是什么新鲜事:

1
2
3
$ID = 234

"Server $ID Rack12"

然而,PowerShell 自动判断一个变量的结束位置,所以当您希望在一个文本中插入一个不含空格的数字时,这种写法可能会失败:

1
2
3
$ID = 234

"Server$IDRack12"

如同语法高亮的信息,PowerShell 会将变量识别成 $IDRack12 因为它无法意识到变量名提前结束。

在这些情况下,只需要用大括号将变量名括起来即可:

1
2
3
$ID = 234

"Server${ID}Rack12"

PowerShell 技能连载 - 语音合成 – 使用不同的语音(第 4 部分)

Windows 10 自带优秀的文本转语音功能,以及不同的高品质语音。要查看哪些语音可用,请试试以下代码:

1
2
3
4
5
6
Add-Type -AssemblyName System.speech
$synthesizer = New-Object System.Speech.Synthesis.SpeechSynthesizer

$synthesizer.GetInstalledVoices().VoiceInfo |
Where-Object { $_.Name -notlike 'Microsoft Server*' } |
Select-Object -Property Name, Gender, Age, Culture

结果类似如下(根据您的 windows 版本、语言文化,和安装的组件可能有所不同):

1
2
3
4
5
Name                    Gender   Age Culture
---- ------ --- -------
Microsoft Zira Desktop Female Adult en-US
Microsoft David Desktop Male Adult en-US
Microsoft Hedda Desktop Female Adult de-DE

要使用这些语音,请用 SelectVoice()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$sampleText = @{
[System.Globalization.CultureInfo]::GetCultureInfo("en-us") = "Hello, I am speaking English! I am "
[System.Globalization.CultureInfo]::GetCultureInfo("de-de") = "Halli Hallo, man spricht deutsch hier! Ich bin "
[System.Globalization.CultureInfo]::GetCultureInfo("es-es") = "Una cerveza por favor! Soy "
[System.Globalization.CultureInfo]::GetCultureInfo("fr-fr") = "Vive la france! Je suis "
[System.Globalization.CultureInfo]::GetCultureInfo("it-it") = "Il mio hovercraft è pieno di anguille! Lo sono "


}


Add-Type -AssemblyName System.speech
$synthesizer = New-Object System.Speech.Synthesis.SpeechSynthesizer

$synthesizer.GetInstalledVoices().VoiceInfo |
Where-Object { $_.Name -notlike 'Microsoft Server*' } |
Select-Object -Property Name, Gender, Age, Culture |
ForEach-Object {
$_
$synthesizer.SelectVoice($_.Name)
$synthesizer.Speak($sampleText[$_.Culture] + $_.Name)

}

哪些语音可用,依赖于系统已安装了哪些语言。以下链接解释了不同语言的 Windows 10 自带了哪些语音:

https://support.microsoft.com/en-us/help/22797/windows-10-narrator-tts-voices.
请注意 SeletVoice() 并不能使用所有已安装的语音。

PowerShell 技能连载 - 合成语音 – 使用语音合成标记语言 SSML(第 3 部分)

Windows 内置的文字转语音引擎可以输入纯文本,并且将它转换为语音,但它也可以通过“语音合成标记语言”来控制。通过这种方式,您可以对语音调优,控制音调,以及语言。

Windows 自带本地的语音引擎,所以最好控制一下采用的语言。否则,在德文系统上,您的英文文本发音听起来会很奇怪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Add-Type -AssemblyName System.speech
$synthesizer = New-Object System.Speech.Synthesis.SpeechSynthesizer

$Text = '
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
xml:lang="en-US">
<voice xml:lang="en-US">
<prosody rate="1">
<p>Normal pitch. </p>
<p><prosody pitch="x-high"> High Pitch. </prosody></p>
</prosody>
</voice>
</speak>
'
$synthesizer.SpeakSsml($Text)

根据已经安装的语音引擎,您现在甚至可以切换语言:

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
Add-Type -AssemblyName System.speech
$synthesizer = New-Object System.Speech.Synthesis.SpeechSynthesizer

$Text1 = '
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
xml:lang="en-US">
<voice xml:lang="en-US">
<prosody rate="1">
<p>Normal pitch. </p>
<p><prosody pitch="x-high"> High Pitch. </prosody></p>
</prosody>
</voice>
</speak>
'

$text2 = '<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
xml:lang="en-US">
<voice xml:lang="de-de">
<prosody rate="1">
<p>Normale Tonhöhe. </p>
<p><prosody pitch="x-high"> Höhere Tonlage. </prosody></p>
</prosody>
</voice>
</speak>'

$synthesizer.SpeakSsml($Text1)
$synthesizer.SpeakSsml($Text2)

如果您想在文字中混合多种语言,您也可以使用传统的 COM 对象 Sapi.SpVoice。以下代码来自前一个技能:

1
2
3
4
5
6
7
8
9
$text = "<LANG LANGID=""409"">Your system will restart now!</LANG>
<LANG LANGID=""407""><PITCH MIDDLE = '2'>Oh nein, das geht nicht!</PITCH></LANG>
<LANG LANGID=""409"">I don't care baby</LANG>
<LANG LANGID=""407"">Ich rufe meinen Prinz! Herbert! Tu was!</LANG>
"

$speaker = New-Object -ComObject Sapi.SpVoice
$speaker.Rate = 0
$speaker.Speak($text)