PowerShell 技能连载 - 管理 Windows 授权密钥(第 2 部分)

大多数的授权密钥管理任务都可以通过 slmgr 命令完成。

This command is actually an ancient VBScript. To read all of your license settings, for example, try this:
这个命令实际上是一个古老的 VBScript。例如要读取所有许可证设置,请使用以下代码:

1
PS> slmgr.vbs /dlv

这将打开一个独立的窗口,并且显示许多许可证激活细节。在对话框窗口中显示信息对于 PowerShell 和自动化操作帮助不大,而且通过 cscript.exe 运行该 VBScript,可能会失败:

1
2
3
4
5
6
7
8
9
PS> slmgr.vbs /dlv

PS> cscript.exe slmgr.vbs /dlv
Microsoft (R) Windows Script Host, Version 5.812
Copyright (C) Microsoft Corporation. All Rights Reserved.

Script file "C:\Users\tobwe\slmgr.vbs" not found.

PS>

您可以将缺省的 VBS 宿主改为 cscript.exe,但是一个更好的不改变全局设置的做法是:找出这个 VBScript 的完整路径,然后用绝对路径执行它。以下是获得 VBScript 路径的办法:

1
2
PS> Get-Command slmgr.vbs | Select-Object -ExpandProperty Source
C:\WINDOWS\system32\slmgr.vbs

这段代码将读取该信息输出到 PowerShell 控制台:

1
2
3
$Path = Get-Command slmgr.vbs | Select-Object -ExpandProperty Source
$Data = cscript.exe //Nologo $Path /dlv
$Data

不过,$Data 包含了难以阅读的纯文本,而且是本地化的。用这种方法获取许可证和激活信息不太优雅。

由于 slmgr.exe 可以做许多神奇的许可证任务,就如打开它的帮助时看到的那样,让我们在随后的技能中检查它的源码,并且跳过以前的 VBScript 直接获取信息。

1
PS> slmgr /?

今日知识点:

  • Windows 许可证管理通常是通过一个 VBScript slmgr.vbs 来实现。当您通过 “/?“ 调用它,将会获得该命令参数的帮助信息。
  • PowerShell 可以调用 slmgr。要接收执行结果,请通过 cscript.exe 调用它,并且向 slmgr.vbs 提供完整的(绝对)路径。
  • Get-Command 是一个查找系统文件夹(在 $env:PATH 中列出的)中的文件的完整(绝对)路径的方法。

PowerShell 技能连载 - 管理 Windows 许可证密钥(第 1 部分)

我们来看看这个单行的解析 Windows 许可证密钥的迷你系列:

1
2
3
4
PS> $key = (Get-WmiObject -Class SoftwareLicensingService).OA3xOriginalProductKey

PS> $key
KJU8F-XXUZH-UU776-IUZGT-HHGR5

今日知识点:

  • WMI 可以获取许多关于 Windows 许可证和 Windows 许可证状态的信息。
  • Windows 用于许可证管理的一个 WMI 名为 “SoftwareLicensingService”。它可以提供 Windows 许可证号码。
  • 同一个类叶包含了许多额外的信息。请看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS> Get-WmiObject -Class SoftwareLicensingService


...
KeyManagementServiceUnlicensedRequests : 4294967295
OA2xBiosMarkerMinorVersion : 1
OA2xBiosMarkerStatus : 1
OA3xOriginalProductKey : XXXXXXXXX
OA3xOriginalProductKeyDescription : [4.0] Professional OEM:DM
OA3xOriginalProductKeyPkPn : [TH]X19-99481
PolicyCacheRefreshRequired : 0
RemainingWindowsReArmCount : 1001
RequiredClientCount : 4294967295
TokenActivationAdditionalInfo :
TokenActivationCertificateThumbprint :
TokenActivationGrantNumber : 4294967295
TokenActivationILID :
TokenActivationILVID : 4294967295
Version : 10.0.17134.471
VLActivationInterval : 4294967295
VLRenewalInterval : 4294967295
PSComputerName : DESKTOP-7AAMJLF

title: “PowerShell 技能连载 - 管理 Windows 许可证密钥”
description: PowerTip of the Day - Managing Windows License Key
categories:

  • powershell
  • tip
    tags:
  • powershell
  • tip
  • powertip
  • series

我们来看看这个单行的解析 Windows 许可证密钥的迷你系列:

1
2
3
4
PS> $key = (Get-WmiObject -Class SoftwareLicensingService).OA3xOriginalProductKey

PS> $key
KJU8F-XXUZH-UU776-IUZGT-HHGR5

今日知识点:

  • WMI 可以获取许多关于 Windows 许可证和 Windows 许可证状态的信息。
  • Windows 用于许可证管理的一个 WMI 名为 “SoftwareLicensingService”。它可以提供 Windows 许可证号码。
  • 同一个类叶包含了许多额外的信息。请看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS> Get-WmiObject -Class SoftwareLicensingService


...
KeyManagementServiceUnlicensedRequests : 4294967295
OA2xBiosMarkerMinorVersion : 1
OA2xBiosMarkerStatus : 1
OA3xOriginalProductKey : XXXXXXXXX
OA3xOriginalProductKeyDescription : [4.0] Professional OEM:DM
OA3xOriginalProductKeyPkPn : [TH]X19-99481
PolicyCacheRefreshRequired : 0
RemainingWindowsReArmCount : 1001
RequiredClientCount : 4294967295
TokenActivationAdditionalInfo :
TokenActivationCertificateThumbprint :
TokenActivationGrantNumber : 4294967295
TokenActivationILID :
TokenActivationILVID : 4294967295
Version : 10.0.17134.471
VLActivationInterval : 4294967295
VLRenewalInterval : 4294967295
PSComputerName : DESKTOP-7AAMJLF

PowerShell 技能连载 - 创建 ASCII 艺术

PowerShell 的功能令人惊叹:只需要几行代码,就可以将任意照片和图片转化为一段 ASCII 艺术。PowerShell 只需要加载图片,然后然后逐行逐列扫描它,然后基于每个像素的亮度将每个像素替换为一个 ASCII 字符。

以下是该函数:

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
 function Convert-ImageToAsciiArt
{
param(
[Parameter(Mandatory)][String]
$Path,

[ValidateRange(20,20000)]
[int]$MaxWidth=80,

# character height:width ratio
[float]$ratio = 1.5
)

# load drawing functionality
Add-Type -AssemblyName System.Drawing

# characters from dark to light
$characters = '$#H&@*+;:-,. '.ToCharArray()
$c = $characters.count

# load image and get image size
$image = [Drawing.Image]::FromFile($path)
[int]$maxheight = $image.Height / ($image.Width / $maxwidth)/ $ratio

# paint image on a bitmap with the desired size
$bitmap = new-object Drawing.Bitmap($image,$maxwidth,$maxheight)


# use a string builder to store the characters
[System.Text.StringBuilder]$sb = ""

# take each pixel line...
for ([int]$y=0; $y -lt $bitmap.Height; $y++){
# take each pixel column...
for ([int]$x=0; $x -lt $bitmap.Width; $x++){
# examine pixel
$color = $bitmap.GetPixel($x,$y)
$brightness = $color.GetBrightness()
# choose the character that best matches the
# pixel brightness
[int]$offset = [Math]::Floor($brightness*$c)
$ch = $characters[$offset]
if (-not $ch){ $ch = $characters[-1] }
# add character to line
$null = $sb.Append($ch)
}
# add a new line
$null = $sb.AppendLine()
}

# clean up and return string
$image.Dispose()
$sb.ToString()
}

以下是它的使用方法:

1
2
3
4
5
6
7
8
$Path = "C:\Users\Tobias\Desktop\Somepic.jpg"
$OutPath = "$env:temp\asciiart.txt"

Convert-ImageToAsciiArt -Path $Path -MaxWidth 150 |
Set-Content -Path $OutPath -Encoding UTF8


Invoke-Item -Path $OutPath

请确保调整了代码中的路径。将会在缺省的文本编辑器中打开 ASCII 艺术。请确保禁用了换行,选择一个等宽的字体和一个足够小的字号!

                                         ;@&&&&&&@&&&&@+-
                                       :@&&&&&&&&&&&&&&&&&&:.
                                     ,&&&&&&&&&&&&&&&&&&&&&&&&&&*
                                  -@&&&&&&&&&&&HHHHHH&&&&&&&&&&&&&*
                               :&&&&&&&&&&&&H&&&&&&&&&&&&&&&&&&&&&&&@
                              *&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&,
                             :&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&-
                            *&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*
                           &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
                          @&&&&&&&&H&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&-
                         &&&&&&&&H&&&@;:+&&&&&&&*-....,:@&&&&&&&&&&&&&&&&&&&@
                        &&&&&&&&H&&;........................*&&&&&&&H&&&&&&&&&.
                      .&&&&&&&&&H;...........................&&&&&&&&H&&&&&&&&&-
                     ;&&&&&&&&&&:............................@@&&&&&&H&&&&&&&&&&
                    &&&&&&&&&&&.+...........................-.;&&&H&&H&&&&&&&&&&@
                   *&H&&&&&&&&;;..............................+@+&&HH&&&&&&&&&&&&
                   ;&H&&&&&&&&................................@*.&&&H&&&&&&&&&&&&:
                   :&H&&&&&&&*...............................:*..&H&&&&&&&&&&&&&&@
                   ,&&H&&&@&@...................................,&&&&&&&&&&&&&&&&&,
                   ,&&&&&&:+....................................+*.;:&&&&&&&&&&&&&&
                   +&&&&&&............................................@*@+&&&&&&&&&,
                   &&&&&&;............................................,-..-+&&&&&&&
                  :&&&&&@-...................................................:&&&&&,
                  &&&&&&&.....................................................&&&&&-
                  &&&&&&+.....................................................&&&&&,,
                  :&&&&&;.....................................................&&&H&
                   &&&&&;.......................................,.,...........&&&&H&@
                  *-&&&&:...*&&&&&&&&&&&&&@*,.............-*&&&&&&&&&&&&:.....@&&&&+
                    &&&&:.+&&HHHHHHHHHHHHH&&&&..........+&&&&HHHHHHHHHHHHH*...+&&&&@
                   ,###H*+;;:,...........-;*&&##########&&&*:..........:;*@:;*&###H:
                   :##H*.......................#@;---+#;.......................@###.
                  ,@###+............+HH&+.....-&......-*......;&H&*............&##:.
                   ,&&#-...........HHH&+H;....@;......,#.....@HHH;H@...........HH&-.
                   ..;#,...........HHHHHH+....#........H,....&HHHHH&..........-#&:-
                   ...H*...........-;;;;;....-H........;@....,;;;;;,..........;#&..
                   ...&#.....................#-.........#.....................&H*..
                   ...:H&........,.........-#@..........-#;..........,,......:#&,..
                    ...-,&###&+::-,,::+&###+......  ......-###H*:::,,::+@####,....
                    ..........,,,,,,,,...........   ............,,,,,,,,..........
                     ............................   ..............................
                     ............................   .............................
                      .....................,.....   ............................
                       ....................,.....   .......,...................
                         ..................,,,...   .......,.................
                          ......................,.  ..,......................
                          ........................,,,.......................
                          ..................................................
                           .................................................
                           .............,-..................--.............
                            ...............--,,-----:-,. --...............
                             ..................,,----,,...................
                              ...........................................
                               .........................................
                                ......................................
                                 ....................................
                                   ................................
                                    ..............................
                                     .,,,,,,,,,,,,,,,,,,,,,,,,,,
                                   ,&...,,,,,,,,,,,,,,,,,,,,,,...@,
                                &HHH......,,,,,,,,,,,,,,,,,,.....-HHH&
                            :HHHHHHH&.........,,,,,,,,,,,........HHHHHHHH;
                         *HHHHHHHHHHHH+........................*HHHHHHHHHHHH@
                      -HHHHHHHHHHHHHHHHH&;..................;&HHHHHHHHHHHHHHHHH,
                     HHHHHHHHHHHHHHHHHHHHHHHH&*;:---::;*&HHHHHHHHHHHHHHHHHHHHHHH&
                    HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH

这儿有一个很酷的优化技巧:在 Convert-ImageToAsciiArt 函数中,请注意 $characters。它是一个包含 ASCII 艺术中使用的字符的字符串,而这些字符是按亮度降序排列的。对于黑白作品,可以使用:

1
$characters = [char]0x2588, ' '

PowerShell 技能连载 - 验证本地用户账户密码

在前一个技能VS,我们通过 Active Directory 来验证用户账户密码。同样的,对于本地账户也可以。PowerShell 代码可以使用本地账户密码来管理对脚本的存取,或部分限制脚本的功能。当然,您也可以用以下代码来创建自己的基本的密码穷举工具。

缺省情况下,以下代码使用您当前的用户名。请确保 $UserName 是某个本地账户的用户名:

1
2
3
4
5
6
7
8
9
# specify local user name and password to test
$UserName = $env:USERNAME
$Password = Read-Host -Prompt "Enter password to test"

# test password
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$type = [DirectoryServices.AccountManagement.ContextType]::Machine
$PrincipalContext = [DirectoryServices.AccountManagement.PrincipalContext]::new($type)
$PrincipalContext.ValidateCredentials($UserName,$Password)

今日的知识点:

  • PowerShell 可以请求本地的 Windows 用户数据库来验证密码。通过这种方式,您可以使用 Windows 维护的密码来决定一个脚本是否允许执行,或允许某个用户执行哪一部分。
  • 请注意向用户询问密码是一种不安全的做法,因为他们不知道密码将会被用在什么地方。

PowerShell 技能连载 - 校验域账户密码

PowerShell 可以轻松地校验一个域账号的密码。换句话说,您可以为 Active Directory 维护的密码绑定一个脚本逻辑。

以下是将密码发送到 AD 并获取回一个 Boolean 值的代码:如果密码正确,返回 $true,否则返回 $false

1
2
3
4
5
6
7
8
9
10
# specify user name and user domain
$UserDomain = $env:USERDOMAIN
$UserName = $env:USERNAME
$Password = Read-Host -Prompt "Enter password to test"

# test password
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain
$PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ContextType, $UserDomain)
$PrincipalContext.ValidateCredentials($UserName,$Password)

请注意这段代码需要 Active Directory 环境,并且不支持本地账号。缺省情况下,它使用您当前账号的详细信息。请根据实际情况调整 $UserDomain$UserName$Password 变量。也请注意 ValidateCredentials() 检查的是明文字符串密码。请谨慎处理并且不要在脚本存储明文密码。同时,不要要求用户以明文输入密码。

今日知识点:

  • PowerShell 可以轻松地连接到 Active Directory 并进行密码验证。

PowerShell 技能连载 - 等待服务状态变化

当您启动或停止一个服务时,可能需要一些时间才能确保服务进入指定的状态——或者它当然可能会失败。当您使用 Stop-Service 时,PowerShell 将等待该服务状态已确认。如果您希望获得其它地方初始化的服务响应,以下是一段监听代码,它将会暂停 PowerShell 直到服务变为指定的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
# wait 5 seconds for spooler service to stop
$serviceToMonitor = Get-Service -Name Spooler
$desiredStatus = [System.ServiceProcess.ServiceControllerStatus]::Stopped
$maxTimeout = New-TimeSpan -Seconds 5

try
{
$serviceToMonitor.WaitForStatus($desiredStatus, $maxTimeout)
}
catch [System.ServiceProcess.TimeoutException]
{
Write-Warning 'Service did not reach desired status within timeframe.'
}

您可以使用这段代码来响应由外部系统触发的服务改变,或者当您要求服务状态更改后做二次确认。

今日的知识点:

  • 您从 cmdlet 获得的多数对象(例如 Get-Service)有许多有用的方法。所有服务对象都有一个 WaitForStatus 方法。在我们的例子中演示了如何使用它。
  • 要发现隐藏在对象中的其它方法,请使用以下代码:
1
2
3
4
5
# get some object
$objects = Get-Process

# dump the methods
$objects | Get-Member -MemberType *method* | Select-Object -Property Name, Definition

PowerShell 技能连载 - 将文本转为图像

WPF (Windows Presentation Foundation) 不仅仅是创建 UI 的技术。您可以用它来创建任意类型的矢量图并将它保存为图形文件。

以下是一个简单的例子,输入任意的文字和字体,然后将它渲染为一个 PNG 文件:

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
function Convert-TextToImage
{
param
(
[String]
[Parameter(Mandatory)]
$Text,

[String]
$Font = 'Consolas',

[ValidateRange(5,400)]
[Int]
$FontSize = 24,

[System.Windows.Media.Brush]
$Foreground = [System.Windows.Media.Brushes]::Black,

[System.Windows.Media.Brush]
$Background = [System.Windows.Media.Brushes]::White
)

$filename = "$env:temp\$(Get-Random).png"

# take a simple XAML template with some text
$xaml = @"
<TextBlock
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">$Text</TextBlock>
"@

Add-Type -AssemblyName PresentationFramework

# turn it into a UIElement
$reader = [XML.XMLReader]::Create([IO.StringReader]$XAML)
$result = [Windows.Markup.XAMLReader]::Load($reader)

# refine its properties
$result.FontFamily = $Font
$result.FontSize = $FontSize
$result.Foreground = $Foreground
$result.Background = $Background

# render it in memory to the desired size
$result.Measure([System.Windows.Size]::new([Double]::PositiveInfinity, [Double]::PositiveInfinity))
$result.Arrange([System.Windows.Rect]::new($result.DesiredSize))
$result.UpdateLayout()

# write it to a bitmap and save it as PNG
$render = [System.Windows.Media.Imaging.RenderTargetBitmap]::new($result.ActualWidth, $result.ActualHeight, 96, 96, [System.Windows.Media.PixelFormats]::Default)
$render.Render($result)
Start-Sleep -Seconds 1
$encoder = [System.Windows.Media.Imaging.PngBitmapEncoder]::new()
$encoder.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($render))
$filestream = [System.IO.FileStream]::new($filename, [System.IO.FileMode]::Create)
$encoder.Save($filestream)

# clean up
$reader.Close()
$reader.Dispose()

$filestream.Close()
$filestream.Dispose()

# return the file name for the generated image
$filename
}

以下是使用方法:

1
2
3
PS> $file = Convert-TextToImage -Text 'Red Alert!' -Font Stencil -FontSize 60 -Foreground Red -Background Gray

PS> Invoke-Item -Path $file

今日的知识点:

  • 通过 XAML,一个基于 XML 的 UI 描述语言,您可以定义图像。
  • PowerShell 可以使用 [Windows.Markup.XAMLReader] 类来快速地将任意合法的 XAML 转换为一个 UIElement 对象。
  • UIElement 对象可以保存成图形文件,例如 PNG 图像,可以在窗口中显示,或者打印出来。在这个例子中,我们主要是将它保存为文件,然后我们用了一个非常简单的 XAML 定义。您现在可能会感到很好奇。通过 Google 搜索一下这个例子中的方法,您将会找到很多知识。

PowerShell 技能连载 - 转换 IEEE754 (Float)(第 2 部分)

昨天我们研究了用 PowerShell 如何将传感器返回的 IEEE754 浮点值转换为实际值。这需要颠倒字节顺序并使用 BitConverter 类。

如果您收到了一个十六进制的 IEEE754 值,例如 0x3FA8FE3B,第一个任务就是将十六进制值分割为四个字节。实现这个目标简单得让人惊讶:将该十六进制值视为一个 IPv4 地址。这些地址内部也是使用四个字节。

以下是一个快速且简单的方法,能够将传感器的十六进制数值转换为一个有用的数值:

1
2
3
4
5
6
$hexInput = 0x3FA8FE3B

$bytes = ([Net.IPAddress]$hexInput).GetAddressBytes()
$numericValue = [BitConverter]::ToSingle($bytes, 0)

"Sensor: $numericValue"

今日的知识点:

  • 将数值转换为 IPAddress 对象来将数值分割为字节。这种方法也可以用来得到一个数字的最低有效位 (LSB) 或最高有效位 (MSB) 形式。

PowerShell 技能连载 - 转换 IEEE754 (Float)(第 1 部分)

PowerShell 是非常多功能的,并且现在常常用于 IoT 和传感器。一些 IEEE754 浮点格式往往是一系列四字节的十六进制数。

我们假设一个传感器以 IEEE754 格式返回一个十六进制的数值 3FA8FE3B。那么要如何获取真实值呢?

技术上,您需要颠倒字节顺序,然后用 BitConverter 来创建一个 “Single” 值。

以 3FA8FE3B 为例,将它逐对分割,颠倒顺序,然后转换为一个数字:

1
2
$bytes = 0x3B, 0xFE, 0xA8, 0x3F
[BitConverter]::ToSingle($bytes, 0)

实际结果是,十六进制数值 0x3FA8FE3B 返回了传感器值 1.320258。今天,我们研究 BitConverter 类,这个类提供了多个将字节数组转换为数值的方法。明天,我们将查看另一部分:将十六进制文本数值成对分割并颠倒顺序。

今日的知识点:

  • 使用 [BitConverter] 来将原始字节数组转换为其它数字格式。这个类有大量方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS> [BitConverter] | Get-Member -Static | Select-Object -ExpandProperty Name

DoubleToInt64Bits
Equals
GetBytes
Int64BitsToDouble
IsLittleEndian
ReferenceEquals
ToBoolean
ToChar
ToDouble
ToInt16
ToInt32
ToInt64
ToSingle
ToString
ToUInt16
ToUInt32
ToUInt64

要查看其中某个方法的语法,请键入它们的名称,不包括圆括号:

1
2
3
4
5
PS> [BitConverter]::ToUInt32

OverloadDefinitions
-------------------
static uint32 ToUInt32(byte[] value, int startIndex)

PowerShell 技能连载 - 小心“Throw”语句(第 2 部分)

在前一个技能中我们解释了将 $ErrorActionPreference 设为 “SilentlyContinue” 将如何影响 throw 语句,而且该 throw 将不会正常地退出函数代码。以下还是我们使用过的例子:

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
function Copy-Log
{
"Doing prerequisites"
"Testing whether target path exists"
"If target path does not exist, bail out"
throw "Target path does not exist"
"Copy log files to target path"
"Delete log files in original location"
}



PS> Copy-Log
Doing prerequisites
Testing whether target path exists
If target path does not exist, bail out
Target path does not exist
In Zeile:8 Zeichen:3
+ throw "Target path does not exist"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Target path does not exist:String) [], RuntimeExceptio
n
+ FullyQualifiedErrorId : Target path does not exist


PS> $ErrorActionPreference = 'SilentlyContinue'

PS> Copy-Log
Doing prerequisites
Testing whether target path exists
If target path does not exist, bail out
Copy log files to target path
Delete log files in original location

虽然 ErrorAction 为默认值时,throw 语句能够退出函数,但是当 ErrorAction 设为 “SilentlyContinue” 时代码将继续执行。这很可能是一个 bug,因为 ErrorAction 的值设为 “Continue” 和 “SilentlyContinue” 的唯一区别只应该是错误信息可见与否。这些设置不应该影响实际执行的代码。

这个 throw 中的 bug 只发生在 throw 抛出的终止错误没有被处理的时候。当您使用 try..catch 语句或者甚至添加一个简单的(完全为空的)trap 语句时,一切都正确了,throw 能够像预期地工作了:

1
2
3
4
5
6
7
# add a trap to fix
trap {}

$ErrorActionPreference = "SilentlyContinue"
Copy-Log
$ErrorActionPreference = "Continue"
Copy-Log

一旦添加了捕获语句,这段代码将会在 throw 语句处跳出,无论 $ErrorActionPreference 的设置为什么。您可以在 PowerShell 用户设置脚本中添加一个空白的捕获语句来防止这个 bug,或者重新考虑是否使用 throw 语句。

重要的知识点:

  • throw 是错误处理系统的一部分,通过 throw 抛出的异常需要用 try..catch 或者 trap 语句。如果异常没有捕获,throw 的工作方式可能和预期的不一致。
  • 因为存在这个问题,所以对于暴露给最终用户的函数,不要用 throw 来退出函数代码。对于最终用户,与其抛出一个(丑陋的)异常,还不如用 Write-WarningWrite-Host 抛出一条对人类友好的错误信息,然后用 return 语句友好地退出代码。
  • 如果您必须抛出一个异常,以便调用者能够在他们的错误处理器中捕获它,但也需要确保无论 $ErrorActionPreference 为何值都能可靠地退出代码,请使用 Write-Errorreturn 语句的组合:
1
2
3
4
5
6
7
8
9
function Copy-Log
{
"Doing prerequisites"
"Testing whether target path exists"
"If target path does not exist, bail out"
Write-Error "Target path does not exist"; return
"Copy log files to target path"
"Delete log files in original location"
}

由于 return 语句不受 $ErrorActionPreference 的影响,您的代码总是能够退出。让我们做个测试:

1
2
3
4
5
6
7
8
9
10
PS> Copy-Log
Doing prerequisites
Testing whether target path exists
If target path does not exist, bail out
Copy-Log : Target path does not exist
In Zeile:1 Zeichen:1
+ Copy-Log
+ ~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Copy-Log

如果将 $ErrorActionPreference 设为 SilentlyContinue,错误信息和预期一致地被屏蔽了,但是代码确实退出了:

1
2
3
4
5
6
PS> $ErrorActionPreference = 'SilentlyContinue'

PS> Copy-Log
Doing prerequisites
Testing whether target path exists
If target path does not exist, bail out