PowerShell 技能连载 - 检测多语言在线文档(第 2 部分)

要如何检测一份在线文档支持哪些语言?

如果 URL 使用语言 ID,则很容易创建包含所有可用语言 ID 的 URL 列表。这就是我们到目前为止第一部分中所做的:

1
$list  =  RL  -f

在第二部分中,我们现在确定列表中哪些 URL 实际中可用。但是,只是通过 Invoke-Webrequest 访问该 URL 是不够的:

1
$list  =  RL  -f

事实证明,所有 URL 都能正常地访问 Microsoft WEB 服务器,并返回状态 “OK”(包括不存在的文档地址):

1
PS> New-SCode

这是因为 Microsoft WEB 服务器(与许多其它的一样)首先接受所有 URL。然后,在内部,WEB 服务器弄清楚下一步该怎么做,并将新的 URL 返回到浏览器中。可能返回的是原始的 URL(如果 WEB 服务器找到了资源),也可能是一个全新的URL,例如通用搜索站点或自定义的“未找到”通知。状态 “OK” 与 URL 的有效性并没有关联。

您实际上可以通过禁止自动重定向来查看内部工作过程。对 Invoke-WebRequest 命令添加参数 "-MaximumRedirection 0 -ErrorAction Ignore"

1
$list  =  RL  -f

现在,您看到 Web 服务器如何告诉浏览器,URL 跳转至其他地方,有效地将浏览器重定向到新的 URL。

检查 URL 是否存在,取决于特定的 Web 服务器的工作原理。在微软的例子中,事实证明有效的 URL 会导致单次重定向,而无效的 URL 会导致多次重定向。用重定向的次次数是否为一次,可以区分合法和非法的 URL。

这是最终解决方案,它还支持实时进度条。

它在网格视图窗口中显示可用的本地化在线文档,您可以选择一个或多个以在浏览器中显示。您也可以以 $result 的形式获取结果,然后将其打印到 PDF 并将其提交给其它语言的员工。

1
$list  =  $h.Keys  |  ForEach-Object { $URL  -f

PowerShell 技能连载 - 小心使用数组

使用 PowerShell,您永远不知道 cmdlet 是返回数组还是单个对象。这是因为一旦命令返回多个项目,PowerShell 就会自动包装成数组:

1
2
3
4
5
6
7
# no array:
$test = Get-Service -Name Spooler
$test -is [Array]

# array:
$test = Get-Service -Name S*
$test -is [Array]

理解这一点很重要,因为这意味着运行时条件可以确定变量的类型。这可能会导致意外情况。以下是说明问题的一个示例:

下面的代码返回以 “C” 开头的所有服务的名称,然后取第一个服务名称。这是有可能的,因为不仅有一个以 “C” 开头的服务,因此 PowerShell 返回 $ServiceNames 中的数组,然后您可以在此数组中使用数字索引来选择特定的元素:

1
2
3
4
5
6
7
$Name = 'c*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

但是,您不能假设 $servicenames 始终是一个数组。如果在运行时只有一项与您的请求匹配的服务,则结果不再是一个数组,而是直接是服务名称。

为什么(以及何时)这有关系?当您的代码采用数组特定功能的那一刻,它就十分重要。因为在某些情况下可能不存在该功能或行为不同。

为了说明这一点,下面的代码现在列出了以 “cry” 开头的所有服务。只有一项服务与请求匹配。因此,$servicenames 不再是一个数组。现在是一个字符串。当您在字符串上使用索引时,您会得到该字符串中的一个字母。

现在,相同的代码返回的是一个字符,而不是服务名称:

1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

这些示例似乎有些人为构造,但是您可以在许多难以找到的脚本错误的内部中找到潜在的问题。这就是为什么重要的是要始终确保您在代码使用数组功能时获得的真正是一个数组。

确保您获得数组的一种简单方法是构造器 @():括号中的任何内容都以数组的形式返回。这就是为什么下面代码有效的原因,无论命令是否返回一个或多个结果:

1
2
3
4
5
6
7
8

$Name = 'cry*'

# get service names
$servicenames = @(Get-Service -Name $Name | Select-Object -ExpandProperty Name)

# get first service name
$servicenames[0]
1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
[array]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]
1
2
3
4
5
6
7
$Name = 'cry*'

# get service names
[string[]]$servicenames = Get-Service -Name $Name | Select-Object -ExpandProperty Name

# get first service name
$servicenames[0]

但是,[array] 更容易使用,因为无论数据类型如何,它总是可以使用,并且 [array] 对于不熟悉类型的用户也更容易理解。

PowerShell 技能连载 - Determining Language Packs (Part 3)

在本系列的第 2 部分中,您已经看到了使用 WMI 与使用命令行工具(如 dism.exe)相比,使用 WMI 查询安装的操作系统语言列表的速度要容易得多,且更快。但是,使用 WMI 仍然需要您知道适当的 WMI 类名。

这就是为什么 PowerShell 提供一个全能的 cmdlet Get-ComputerInfo 的原因。它为您查询各种与计算机相关的信息,然后将其与您联系。 我们也可以通过这种方法解决这个问题:

1
2
$a = Get-ComputerInfo
$a.OsMuiLanguages

但不幸的是,Get-ComputerInfo 总是查询完整的信息集,这使得它很缓慢。但总比没有好,甚至比 dism.exe 更好,而第 2 部分的直接 WMI 查询仍然是效率最高的方法。

PowerShell 技能连载 - 当格式化失败时

您在使用 PowerShell 时可能会遇到一个奇怪的格式问题:当您一行一行执行代码时,得到的输出结果和以整体的方式执行一段代码有所不同。

这是要执行的示例代码:

Get-Process -Id $pid
Get-Date
Get-Service -Name Spooler

当您将三行代码作为脚本整体运行时,只有第一个命令返回表格,后两个显示列表。但是,当您逐行执行代码时,它们的格式不同,并显示为表格,甚至是单行纯文本。

这是 PowerShell 实时输出格式的副作用:当输出格式化器遇到第一个返回数据时,它必须决定格式化和写入,即表头。所有剩余的数据将插入到该输出格式中。

每当您的脚本返回多个对象并且这些对象具有不同类型时,PowerShell 就会意识到这些对象不适合现有表设计。在这种情况下,所有后续对象将格式化成列表视图。

如果您想更好地控制此行为,则可以随时将输出发送到 Out-Default,这将关闭当前的输出。任何后续对象都将启动新的输出格式。以下代码将始终显示相同的显示格式,无论您是作为脚本运行还是单独运行命令:

1
2
3
Get-Process -Id $pid | Out-Default
Get-Date | Out-Default
Get-Service -Name Spooler

PowerShell 技能连载 - 获取系统正常运行时间

The Get-ComputerInfo cmdlet 可以提供有关 Windows 客户端或服务器的大量信息,例如正常运行时间和其他相关信息。

试试以下代码:

1
2
3
4
5
PS> Get-ComputerInfo | Select-Object -Property *Upt*

CsWakeUpType OsLastBootUpTime OsUptime
------------ ---------------- --------
PowerSwitch 25.10.2022 17:32:23 6.23:07:47.4872044

该命令获取所有名字中包含 “upt” 的属性,这些属性恰好都包含了和系统运行时间有关的信息。

当然,您还可以将信息存储到变量中,并单独查询属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS> $info = Get-ComputerInfo | Select-Object -Property *Upt*

PS> $info.OsUptime


Days : 6
Hours : 23
Minutes : 9
Seconds : 28
Milliseconds : 617
Ticks : 6017686177952
TotalDays : 6,96491455781481
TotalHours : 167,157949387556
TotalMinutes : 10029,4769632533
TotalSeconds : 601768,6177952
TotalMilliseconds : 601768617,7952


PS> $info.OsUptime.TotalHours
167,157949387556

由于属性值显示在一列中,它们显示为一行字符串。如果您单独查询它们,例如 OsUptime,它们将暴露它们的所有自身属性。

PowerShell 技能连载 - 无人值守读取 PFX 证书

PowerShell 配备了一个名为 Get-PfxCertificate 的 cmdlet,您可以用来将证书和私钥加载到内存中。但是如果证书受密码保护,则有一个强制性提示来输入密码。您不能通过参数提交密码,因此该 cmdlet 不能无人值守使用。

这是一个替代的函数,允许通过参数输入密码,从而允许以无人值守的方式即时加载 pfx 证书:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Get-PfxCertificateUnattended
{
param
(
[String]
[Parameter(Mandatory)]
$FilePath,

[SecureString]
[Parameter(Mandatory)]
$Password
)

# get clear text password
$plaintextPassword = [PSCredential]::new("X", $Password).GetNetworkCredential().Password


[void][System.Reflection.Assembly]::LoadWithPartialName("System.Security")
$container = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$container.Import($FilePath, $plaintextPassword, 'PersistKeySet')
$container[0]
}

请注意,该功能始终返回 pfx 文件中发现的第一个证书 如果您的PFX文件包含多个证书,则可能需要在最后一行代码中调整索引。

PowerShell 技能连载 - 创建新的代码签名测试证书

PowerShell 配备了一个名为 New-SelfSignedCertificate 的 cmdlet,可以创建各种自签名的测试证书。但是,使用它为 PowerShell 代码签名创建证书并不直观,更不用说在测试机上确保测试证书值得信任。

所以我们编写了一个函数将上述 cmdlet 包装起来,使得创建既持久且可导出的代码签名证书变得更加容易:

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
function New-CodeSigningCert
{
[CmdletBinding(DefaultParametersetName="__AllParameterSets")]
param
(
[Parameter(Mandatory)]
[String]
$FriendlyName,

[Parameter(Mandatory)]
[String]
$Name,

[Parameter(Mandatory,ParameterSetName="Export")]
[SecureString]
$Password,

[Parameter(Mandatory,ParameterSetName="Export")]
[String]
$FilePath,

[Switch]
$Trusted
)

# create new cert
$cert = New-SelfSignedCertificate -KeyUsage DigitalSignature -KeySpec Signature -FriendlyName $FriendlyName -Subject "CN=$Name" -KeyExportPolicy ExportableEncrypted -CertStoreLocation Cert:\CurrentUser\My -NotAfter (Get-Date).AddYears(5) -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3')


if ($Trusted)
{
$Store = New-Object system.security.cryptography.X509Certificates.x509Store("Root", "CurrentUser")
$Store.Open("ReadWrite")
$Store.Add($cert)
$Store.Close()
}


$parameterSet = $PSCmdlet.ParameterSetName.ToLower()

if ($parameterSet -eq "export")
{
# export to file
$cert | Export-PfxCertificate -Password $Password -FilePath $FilePath

$cert | Remove-Item
explorer.exe /select,$FilePath
}
else
{
$cert
}
}

PowerShell 技能连载 - 获取卷 ID(第 2 部分)

在 Windows 10 及以上版本,您可以使用 Get-Volume 获取有关驱动器的卷 ID 和其他信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> Get-Volume

DriveLetter FriendlyName FileSystemType DriveType HealthStatus OperationalStatus SizeRemaining Size
----------- ------------ -------------- --------- ------------ ----------------- ------------- ----
WINRETOOLS NTFS Fixed Healthy OK 315.58 MB 990 MB
C OS NTFS Fixed Healthy OK 154.79 GB 938.04 GB
Image NTFS Fixed Healthy OK 107.91 MB 12.8 GB
DELLSUPPORT NTFS Fixed Healthy OK 354.46 MB 1.28 GB
``

请注意,尽管您最初只会看到一小部分可用信息。但将数据发送到管道可以查看到所有信息,包括卷 ID:

```powershell
Get-Volume | Select-Object -Property *

这是一个示例:

1
2
3
4
5
6
7
8
PS> Get-Volume | Select-Object -Property DriveLetter, FileSystemLabel, Size, Path

DriveLetter FileSystemLabel Size Path
----------- --------------- ---- ----
WINRETOOLS 1038086144 \\?\Volume{733298ae-3d76-4f5f-acc4-50fdca0c6401}\
C OS 1007210721280 \\?\Volume{861c48b0-d434-48d3-995a-0573c1336eb7}\
Image 13739487232 \\?\Volume{9dc0ed9d-86fd-4cd5-9ed8-3249f57720ad}\
DELLSUPPORT 1371533312 \\?\Volume{b0f36c9e-2372-47f9-8b84-cdf65447c9c6}\