PowerShell 技能连载 - 清理 PowerShell 模块(第 1 部分)

有很多脚本可以通过转换一系列二进制值来读取注册表中原始的 Windows 10 产品密钥。

在这个迷你系列的第一部分中,我们将查看 PowerShell 保存其模块的位置,以及您可以采取什么措施删除不再需要的模块。

最安全的方法是完全专注于通过 Install-Module 安装的模块,因为这样您永远不会意外删除 Windows 或属于其他软件产品的一部分的模块。

这行代码列出了由 Install-Module 安装的所有模块,并让您选择要(永久)删除的模块。

1
2
3
4
Get-InstalledModule |
Out-GridView -Title 'Select module(s) to permanently delete' -PassThru |
Out-GridView -Title 'Do you REALLY want to remove the modules below? CTRL+A and OK to confirm' -PassThru |
Uninstall-Module

注意:如果模块安装在 “AllUsers” 范围中,则可能需要管理员特权。

注意:删除模块时,它将从硬盘驱动器中永久删除。确保您知道它发布了哪些 cmdlet,并且确信不再需要它们。如果您不小心删除了模块,则可以随时通过 Install-Module 重新安装它。

PowerShell 技能连载 - 快速查找过期的 PowerShell 模块

在最简单的情况下,您可以仅使用单行代码(删除 -WhatIf 以实际执行更新)检查所有已安装的模块以进行更新:

1
PS C:\> Get-InstalledModule | Update-Module -WhatIf

Get-InstalledModule 列出了以“托管”方式安装的所有模块(使用 Install-Module),并包含有关该模块的安装位置的信息(即 PowerShell Gallery 网站)。这就是 Update-Module 用来检查新版本所需要的信息。

如果您只是想看看是否有模块需要更新,并且仅专注于 PowerShell Gallery 安装的模块,那么以下是检查更新的一种更快的方法:

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
function Test-GalleryModuleUpdate
{
param
(
[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[string]
$Name,

[Parameter(Mandatory,ValueFromPipelineByPropertyName)]
[version]
$Version,

[switch]
$NeedUpdateOnly

)

process
{
$URL = "https://www.powershellgallery.com/packages/$Name"
$page = Invoke-WebRequest -Uri $URL -UseBasicParsing -Maximum 0 -ea Ignore
[version]$latest = Split-Path -Path $page.Headers.Location -Leaf
$needsupdate = $Latest -gt $Version

if ($needsupdate -or (!$NeedUpdateOnly.IsPresent))
{
[PSCustomObject]@{
ModuleName = $Name
CurrentVersion = $Version
LatestVersion = $Latest
NeedsUpdate = $needsupdate
}
}
}
}

Get-InstalledModule | Where-Object Repository -eq PSGallery |
Test-GalleryModuleUpdate #-NeedUpdateOnly

Test-GalleryModuleUpdate 函数读取了 Get-InstalledModule 返回的模块,并检查在 powershellgallery.com 上是否发布了新版本。此检查是由通过解析 URL 快速完成的。如果添加 -NeedUpdateOnly switch 参数,则 Test-GalleryModuleUpdate 仅返回需要更新的模块(可能没有结果)。

这是示例输出:

ModuleName    CurrentVersion LatestVersion NeedsUpdate
----------    -------------- ------------- -----------
ImportExcel   7.5.2          7.5.3                True
PSEventViewer 1.0.17         1.0.22               True
Az.Tools.P... 0.5.0          1.0.1                True
Microsoft.... 16.0.21116.... 16.0.22413...        True
MicrosoftT... 2.3.1          4.4.1                True
PSReadLine    2.2.2          2.2.5                True
PSWriteHTML   0.0.172        0.0.174              True
...

PowerShell 技能连载 - 解锁多个文件

当您从 Internet 下载文件,或将文件从不信任源复制到 NTFS 文件系统的驱动器时,Windows 将秘密的 NTFS 流添加到这些文件中,以作为额外的安全性层。

“锁定”的文件将无法执行,并且像 DLL 这样的“锁定”二进制文件无法被加载。这就是为什么在使用此类文件之前取消锁定这些文件的原因。从本质上讲,是通过删除隐藏的 NTFS 流来完成解密,该流将文件标记为来自“不受信任的来源”。

PowerShell 用 Unblock-File cmdlet 清除文件的隐藏的 NTFS 流。要解开多个文件,即整个子文件夹的完整内容,只需使用 Get-ChildItem 并将结果通过管道输送来解开文件:

1
Get-ChildItem -Path $home\desktop -File -Recurse | Unblock-File

PowerShell 技能连载 - PowerShell 扩展的重要更新

如果您过去曾尝试过 VS Code,但由于速度和稳定性而感到失望,那么您现在可能想再次看一下。5 月 3 日,Powershell 团队的 Sydney Smith 宣布对 PowerShell 扩展进行了重大改革和重写。

具体而言,核心管道的执行已重新设计,IntelliSense 等编辑服务现在以单独的同步线程运行,以实现更高的速度、更少的阻塞和竞态条件。

在 Windows 上,稳定的的 Powershell ISE 仍然可以使用,并有许多粉丝。在其他平台上,或有更复杂的或多种编程语言的项目,那么是时候给 VS Code 第二次机会,如果您刚开始选择的话。

在这儿阅读完整的声明:https://devblogs.microsoft.com/powershell/major-update-to-the-powershell-extension-for-visual-studio-code/

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
$Name = 'cry*'

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

# get first service name
$servicenames[0]

要将数字签名添加到 PowerShell 脚本文件(或其他能够为此问题携带数字签名的文件),请使用 Set-AuthenticodeSignature。运行以下演示代码(根据需要调整文件和证书的路径):

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]

运行此代码时,在 $Path 中指定的脚本文件将打开并显示添加到脚本底部的数字签名:

Hello World!

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 技能连载 - 检测多语言在线文档(第 2 部分)

要如何自动检查在线文档所支持的语言?

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

1
$list = RL -f

在第二部分中,我们现在确定列表中实际存在的 URL。但是,仅仅试图通过 Invoke-WebRequest 访问 URL 是不够的:

1
$list = RL -f

事实证明,所有 URL 访问的都是微软的 WEB 服务器,并且返回的状态都是 “OK”(包括不存在的语言):

1
PS> New-SCode

这是因为微软的 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
2
$list = $h.Keys |
ForEach-Object { $URL -f

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

在之前的技能中,我们介绍了官方的 PowerShell 语言规范,该规范提供了许多不同语言的在线版本。这引出了有关受支持的语言的问题。

通常,多语言在线文档使用相同的URL,只需更改语言 ID 即可。例如,英语和德语版在其语言 ID 上有所不同:

https://docs.microsoft.com/de-de/powershell/scripting/lang-spec/chapter-01?view=powershell-7.2

https://docs.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-01?view=powershell-7.2

为了自动找出所有可用的翻译,我们首先需要一个有可能的语言 ID 列表并构建假想的URL。然后,我们需要一种方法来检查这些 URL 是否真正存在。让我们解决第一部分:

可以这样检索所有可用语言 ID 的列表:

1
[CultureInfo]::GetCultures([Globalization.CultureTypes]::SpecificCultures)

这将创建一个哈希表,可轻松查找使用连字符的 ID(因为 Web URL 通常仅使用带有连字符的 ID):

1
2
3
$h = [CultureInfo]::GetCultures([Globalization.CultureTypes]::SpecificCultures) |
Where-Object {$_ -like '*-*' } |
Group-Object -Property Name -AsHashTable

现在,我们可以获取可用语言 ID 的列表,我们也可以查找其显示名称:

1
2
3
4
5
6
7
# list all languages:
$h.Keys



PS C:\> $h['de-de'].DisplayName
German (Germany)

要获取可能的 URL 的列表,只需遍历所有可用的语言 ID,然后将它们一个接一个地插入 URL 即可。

请注意,我如何将占位符({0})替换为特定语言的 ID,例如 “en -us”:使用操作符 -f 来格式化,然后在循环体中插入语言 ID:

1
2
3
4
5
6
$URL = 'https://docs.microsoft.com/{0}/powershell/scripting/lang-spec/chapter-01'

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

$list

我们现在有一个可能的 URL 列表。 在第二部分中,我们将检查此列表以查看实际存在哪些 URL。

PowerShell 技能连载 - 值得一读:PowerShell 语言规范

今天我很荣幸地向您介绍微软官方的 PowerShell 语言规范。作为经验丰富的 Powershell 脚本编写者,您可以通过查看 PowerShell 语言本身的规范来获得很多内部知识。

语言规范解释了数十亿个和操作相关的细节,例如预定义变量,内置关键字,操作符优先级等的全面列表。您可以在此处找到在线的语言规范:

https://docs.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-01?view=powershell-7.2

这 13 章也已翻译成多种语言。 中文版可以在这里找到:

https://docs.microsoft.com/zh-cn/powershell/scripting/lang-spec/chapter-01?view=powershell-7.2

PowerShell 技能连载 - 解析 URL

URL 并不总是(直接)指向资源。通常,URL 充当始终指向最新版本的快捷方式或静态地址。PowerShell 可以揭示资源的真实URL,您可以在许多情况下使用它。

这是一个如何解析快捷链接的示例:

1
2
3
4
5
6
7
8
# this is the URL we got:
$URLRaw = 'http://go.microsoft.com/fwlink/?LinkID=135173'
# we do not allow automatic redirection and instead read the information
# returned by the webserver ourselves:
$page = Invoke-WebRequest -Uri $URLRaw -UseBasicParsing -MaximumRedirection 0 -ErrorAction Ignore
$target = $page.Headers.Location

"$URLRaw -> $target"

这是关于如何从静态链接解析产品版本的示例。最新版的 PowerShell 总是可以使用此静态 URL 获取:

https://github.com/PowerShell/PowerShell/releases/latest

如果您想知道最新版本的实际版本号,请尝试解析 URL:

1
2
3
4
5
6
7
8
$URLRaw = 'https://github.com/PowerShell/PowerShell/releases/latest'
# we do not allow automatic redirection and instead read the information
# returned by the webserver ourselves:
$page = Invoke-WebRequest -Uri $URLRaw -UseBasicParsing -MaximumRedirection 0 -ErrorAction Ignore
$realURL = $page.Headers.Location
$version = Split-Path -Path $realURL -Leaf

"PowerShell 7 latest version: $version"

同样的方法也适用于 PowerShell Gallery 模块:

1
2
3
4
5
6
7
8
9
10
# name of a module published at powershellgallery.com
$ModuleName = 'ImportExcel'

$URL = "https://www.powershellgallery.com/packages/$ModuleName"
# get full URL (including latest version):
$page = Invoke-WebRequest -Uri $URL -UseBasicParsing -MaximumRedirection 0 -ErrorAction Ignore
$realURL = $page.Headers.Location
# return version only:
$latest = Split-Path -Path $realURL -Leaf
"Module $ModuleName latest version: $latest"

PowerShell 技能连载 - 简单的类似 grep 的文本过滤器(第 1 部分)

PowerShell 是面向对象的,因此与 Linux 和 grep 相比,文本过滤和正则表达式的应用不多。然而,有时候通过简单的文本模式来过滤命令对象是高效舒适的。

它的工作方式类似。PowerShell 确实带有类似 grep 的命令,即 Select-String cmdlet 。虽然它擅长过滤文件内容,但在尝试过滤命令输出时,它没什么用。

假设您只对运行中的服务感兴趣。按 “running” 来过滤,结果一无所获:

1
PS> Get-Service | Select-String Running

不过,这不是 Select-String 的错。Select-String 需要文本输入,而 cmdlet 通常返回强类型的对象,而不是文本。 通过将命令输出转换为字符串,就能正常工作了:

1
2
3
4
5
6
7
8
PS> Get-Service | Out-String -Stream | Select-String Running

Running AdobeARMservice Adobe Acrobat Update Service
Running AgentShellService Spiceworks Agent Shell Service
Running Appinfo Application Information
Running AppMgmt Application Management
Running AppXSvc AppX Deployment Service (AppXSVC)
...

如果您不介意最终结果是纯文本,那就太好了。这基本上就是简单文本过滤的处理代价。

不,其实不是。您可以创建自己的过滤器命令,该命令 暂时 将传入的对象转换为文本,进行文本模式匹配,然后返回未更改的强类型对象。听起来很简单,确实如此。这是 Powershell 的 grep:

1
2
3
4
filter grep ([string]$Pattern)
{
if ((Out-String -InputObject $_) -match $Pattern) { $_ }
}

运行代码,然后尝试:

1
2
3
4
5
6
7
8
9
PS> Get-Service | grep running

Status Name DisplayName
------ ---- -----------
Running AdobeARMservice Adobe Acrobat Update Service
Running AgentShellService Spiceworks Agent Shell Service
Running Appinfo Application Information
Running AppMgmt Application Management
...

它的使用非常易于使用,最重要的是,输出强类型的对象,因此您仍然可以访问其属性

1
2
3
4
5
6
7
8
9
10
PS> Get-Service | grep running | Select-Object Name, StartType, Status

Name StartType Status
---- --------- ------
AdobeARMservice Automatic Running
AgentShellService Automatic Running
Appinfo Manual Running
AppMgmt Manual Running
AppXSvc Manual Running
AudioEndpointBuilder Automatic Running