PowerShell 技能连载 - 打开关闭 Windows 的对话框

以下是打开关闭 Windows 对话框的一行代码:

1
(New-Object -ComObject Shell.Application).ShutdownWindows()

使用此行代码,它变成了名为 “bye” 的新命令:

1
function bye { (New-Object -ComObject Shell.Application).ShutdownWindows() }

如果将此行放在 $profile 中的自动配置文件 (start) 脚本中(可能需要先创建该文件),则完成脚本时,您现在可以简单地输入 “bye” 以关闭您的 Windows 会话。

PowerShell 技能连载 - 查看所有模块的细节

powershellgallery.com 是找到新的免费 PowerShell 扩展模块的好地方,可以为您的 PowerShell 添加新的 cmdlet。

但是,在 Web 界面中查看所有模块详细信息可能会有点麻烦。这就是为什么通过 RESTful WebService 检索模块信息可能会有所帮助。

这是一个脚本,它传入 PowerShell Gallery 中托管的(任何)模块的名称。然后,它检索所有详细信息(例如版本历史记录、下载计数、更新日期和发行说明),并以一种使信息易于访问的方式准备它们。特别是,将检索到的基于 XML 的信息转换为简单对象:

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
# replace module name with any module name hosted
# in the PowerShell Gallery (https://powershellgallery.com)
$ModuleName = 'MicrosoftTeams'

$baseUrl = 'https://www.powershellgallery.com/api/v2/FindPackagesById()?id='
$escaped = [Uri]::EscapeDataString("'$ModuleName'")
$url = $baseUrl + $escaped

# properties to exclude (add or remove as needed)
$blacklist = 'FileList', 'Tags'

$data = Invoke-RestMethod -Uri $url -UseBasicParsing |
ForEach-Object {
$hash = [Ordered]@{}
$moduleInfo = $_.Properties
foreach($_ in $moduleInfo.PSObject.Properties)
{
# name of property
$name = $_.Name
# if it is in blacklist, skip and continue with next property
if ($name -in $blacklist) { continue }
# if it is the property "name", then skip
# all remaining (xml default properties)
if ($name -eq 'Name') { break }

# if type is "xmlelement", retrieve underlying text value in #text
if ($_.TypeNameOfValue -eq 'System.Xml.XmlElement')
{
$hash[$name] = $moduleInfo.$name.'#text'

# if a datatype is assigned, try and convert to appropriate type
if ($moduleInfo.$name.type -like 'Edm.*')
{
$typename = $moduleInfo.$name.type.replace('Edm.','')
$hash[$name] = $hash[$name] -as $typename
}
}
else
{
$hash[$name] = $_.Value
}
}

# convert a hash table to object and return it
[PSCustomObject]$hash
}

$data | Out-GridView

PowerShell 技能连载 - 识别主 PowerShell 模块位置

PowerShell 只是一个脚本引擎。 其所有 cmdlet 来自外部模块,环境变量 $env:PSModulePath 返回 PowerShell 自动扫描模块的文件夹:

1
2
3
4
PS> $env:PSModulePath -split ';'
C:\Users\username\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

同样,Get-Module 查找位于其中一个文件夹中的所有模块和 cmdlet:

1
Get-Module -ListAvailable

当您以专家的身份使用 PowerShell 时,确保所有所需的模块(以及其 cmdlet)都可以使用,将越来越重要。因此,第一步是选择一个好的位置来存储新模块,下一步是良好地部署和更新这些模块。

本地存储模块的最佳位置是代表 “AllUsers” 范围的文件夹。在 Windows 系统上,此文件夹位于 Program Files 中,您需要管理员权限来更改它。

在大型企业中部署和更新模块的最佳方法是使用现有的软件部署基础架构和部署模块及其更新,以及之前识别的 “AllUsers” 文件夹。

该文件夹的路径可能会根据您使用的 PowerShell 版本而异。以下是一个脚本,用于计算所有用户的模块位置的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# determine the primary module location for your PowerShell version
$path = if ('Management.Automation.Platform' -as [Type])
{
# PowerShell CLR
if ([Environment]::OSVersion.Platform -like 'Win*' -or $IsWindows) {
# on Windows
Join-Path -Path $env:ProgramFiles -ChildPath 'PowerShell'
}
else
{
# on Non-Windows
$name = [Management.Automation.Platform]::SelectProductNameForDirectory('SHARED_MODULES')
Split-Path -Path $name -Parent
}
}
else
{
# Windows PowerShell
Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell"
}

在 Windows 上,PowerShell 7 和 Windows PowerShell 可以共享一个文件夹,因此如果您不想专门为 PowerShell 7 部署模块,则可以进一步简化脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# determine the primary module location for your PowerShell version
$path = if ([Environment]::OSVersion.Platform -like 'Win*' -or $IsWindows)
{
# Windows
Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell"
}
else
{
# Non-Windows
$name = [Management.Automation.Platform]::SelectProductNameForDirectory('SHARED_MODULES')
Split-Path -Path $name -Parent
}

$path

PowerShell 技能连载 - 测试 URL 是否完整

PowerShell 经常基于 API,您不需要深入正则表达式和文本模式。相反,.NET Framework 中可以使用多种专业的测试方法。困难的是找到并知道它们,而不是运行它们和进行测试。

例如,要测试 URL 是否正确,请尝试:

1
2
3
$url = 'powershell.one'
$kind = [UriKind]::Absolute
[Uri]::IsWellFormedUriString($url, $kind)

结果将是 false ,因为 “powershell.one” 不是一个绝对的 URL。在前面添加 “https://“,结果会变为 true。

PowerShell 技能连载 - 是否在 Windows PowerShell 中运行(第 2 部分)

在上一个技能中,我们介绍了一个向后兼容的单行代码,能够判断您的脚本是否运行在传统的 Windows PowerShell 环境中,还是运行在新的 PowerShell 7 便携版 shell 中。

如果您使用的是跨平台的 PowerShell 7,那么有一个名为 [Management.Automation.platform] 的新类型,能返回更多的平台信息。Windows PowerShell 尚未包含此类型,因此您可以使用此类型来确定您是否当前正在 Windows PowerShell 上运行。如果没有,则该类型提供了额外的平台信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# testing whether type exists
$type = 'Management.Automation.Platform' -as [Type]
$isWPS = $type -eq $null

if ($isWPS)
{
Write-Warning 'Windows PowerShell'
} else {
# query all public properties
$properties = $type.GetProperties().Name
$properties | ForEach-Object -Begin { $hash = @{} } -Process {
$hash[$_] = $type::$_
} -End { $hash }
}

在 Windows PowerShell 上,脚本只会产生警告。 在 PowerShell 7 上,它返回一个哈希表,其中包含所有相关平台信息:

Name                           Value
----                           -----
IsStaSupported                 True
IsLinux                        False
IsCoreCLR                      True
IsWindows                      True
IsNanoServer                   False
IsMacOS                        False
IsWindowsDesktop               True
IsIoT                          False

PowerShell 技能连载 - 是否在 Windows PowerShell 中运行(第 1 部分)

现在的 PowerShell 可以在各种平台上运行,并且在上一个技能中,我们解释了如何查看脚本运行的操作系统。

如果操作系统是 Windows,您仍然不能知道您的脚本是由内置 Windows PowerShell 还是新的便携式 PowerShell 7 运行。

以下是一种安全和向后兼容的方式,可以了解您的脚本是否在 Windows PowerShell 上运行:

1
2
3
4
$RunOnWPS = !($PSVersionTable.ContainsKey('PSEdition') -and
$PSVersionTable.PSEdition -eq 'Core')

"Runs on Windows PowerShell? $RunOnWPS"

PowerShell 技能连载 - 决定您的平台

现在的 PowerShell 已是跨平台的,因此即使能在 Windows 服务器上正常使用 Windows PowerShell,您的脚本也有可能在不同的操作系统上停止运行。

如果您的脚本想要知道它正在运行的平台,以向后兼容的方式运行,请尝试这些代码:

1
2
3
4
5
$RunOnWindows = (-not (Get-Variable -Name IsWindows -ErrorAction Ignore)) -or $IsWindows
$RunOnLinux = (Get-Variable -Name IsLinux -ErrorAction Ignore) -and $IsLinux
$RunOnMacOS = (Get-Variable -Name IsMacOS -ErrorAction Ignore) -and $IsMacOS

Get-Variable -Name RunOn*

在 Windows 系统上,结果如下所示:

Name                           Value
----                           -----
RunOnLinux                     False
RunOnMacOS                     False
RunOnWindows                   True

您现在可以安全地检查先决条件,并确保您的脚本代码仅在适当的情况下运行。

PowerShell 技能连载 - 转义独立的字符

在前一个技能中,我们解释了如何转义整个字符串序列。如果您只需要转义单个字符,请使用 HexEscape() 如:

1
2
PS> [Uri]::HexEscape('a')
%61

此方法实际上是检索 ASCII 代码并将其转换为十六进制。

实际上,还可以进行相反的操作,您可以将转义的字符转换回正常字符。例如,”a” 的 ASCII 代码为 65,它的十六进制表达是 41。因此,”A” 的转义表示为 “%41”,这行代码将得到 “A”:

1
2
PS C:\> [Uri]::HexUnescape('%41',[ref]0)
A

(第二个参数表示要转换转义的字符在字符串中的位置)。

有了这个,您现在可以生成一个范围内的字母:首先,生成所需字母的 ASCII 代码,并以十六进制形式手动转换它们。-f 运算符可以执行此转换:

1
2
3
4
PS> $decimal = 65
PS> $hex = '{0:X}' -f $decimal
PS> $hex
41

以下是来自 A 到 Z 的转义字母:

1
65..90 | ForEach-Object { '%{0:X}' -f $_ }

反转义它们的方法:

1
65..90 | ForEach-Object { [Uri]::HexUnescape( ('%{0:X}' -f $_), [ref]0) }

不过,不要爱上这个过度的技巧。类型转换可以让您更轻松实现:

1
[char[]](65..90)

PowerShell 技能连载 - 安全地转义数据字符串

通常,您使用像 EscapeUriString() 这样的方法来安全地转义要追加到 URL 的字符串数据(如我们之前的技能中所指出的)。

但是,这可能会导致头疼的情况,因为 EscapeUriString() 专门设计用于转义包括域名部分的完整URL,而不仅仅是您的参数。这就是为什么它可能损坏您要发送给其他人的数据,即莫尔斯 Web Service。尝试运行以下代码:

1
2
3
4
5
6
$url = "https://api.funtranslations.com/translate/morse.json?text=One&Two"

$Escaped = [Uri]::EscapeUriString($url)

$result = Invoke-RestMethod -Uri $url
$result.contents

返回的结果可以看出问题:

translated text translation
---------- ---- -----------
--- -. .   One  morse

即使您将文本 “One&Two” 发送给 WebService,它也仅会返回 “One” 的摩尔斯代码。当您查看 $Escaped 的内容时,就能发现原因:

1
2
PS C:\> $escaped
https://api.funtranslations.com/translate/morse.json?text=One&Two

EscapeUriString 没有将 & 字符转义——因为 & 是URL的有效部分,它用于分割参数。实质上,WebService 接收了两个参数,因为它只支持一个,所以它丢弃了第二个参数。

虽然 EscapeUriString() 能很方便快速转义完整的网址,但它具有严重的缺点。要解决此问题,请务必确保将 base URL 和数据参数分开。您可以使用 EscapeDataString(),来代替 EscapeUriString() 来确保所有特殊字符被正确转义:

1
2
3
4
5
6
7
$Text = 'One&Two'
$Escaped = [Uri]::EscapeDataString($Text)
$baseurl = "https://api.funtranslations.com/translate/morse.json?text="
$url = $baseurl + $Escaped

$resultok = Invoke-RestMethod -Uri $url
$resultok.contents

现在结果是正确的(用于演示的 WebService 确实具有速率限制,因此如果您过于频繁调用它,将需要等待一小时来验证):

这是因为 $escaped 现在转义了所有特殊字符,包括 & 符号:

1
2
PS> $escaped
One%26Two

此外,您还有一张“返程票:UnescapeDataString(),它能将转义后的数据恢复正常:

1
2
3
4
5
6
7
8
9
# escape special characters
$text = 'This is a text with [special] characters!'

$escaped = [Uri]::EscapeDataString($text)
$escaped

# unescape escaped strings
$unescaped = [Uri]::UnescapeDataString($escaped)
$unescaped

结果如下所示:

This%20is%20a%20text%20with%20%5Bspecial%5D%20characters%21
This is a text with [special] characters!

PowerShell 技能连载 - 转义 URL 字符串

将字符串信息添加到 URL 时,即要构造用于调用 REST Web 服务的请求时,重要的一环是要转义特殊字符。[Uri] 类型为 URL 提供了转义与反转义方法:

1
2
3
$Text = 'SOS Save me please!'
$Escaped = [Uri]::EscapeUriString($Text)
$Escaped

结果如下所示:

SOS%20Save%20me%20please!

现在,您可以安全地将转义的字符串数据发送到 RESTful Web 服务。以下代码将文本转换为摩尔斯码:

1
2
3
4
5
6
7
8
$Text = 'SOS Save me please!'
$url = "https://api.funtranslations.com/translate/morse.json?text=$Text"

$Escaped = [Uri]::EscapeUriString($url)


$result = Invoke-RestMethod -Uri $url
$result.contents.translated

结果现在看起来像这样:

... --- ...     ... .- ...- .     -- .     .--. .-.. . .- ... . ---.

有些时候,使用此方法转义字符串可能会破坏某些查询字符串。要解决这个问题,请查看明天的技能。