PowerShell 技能连载 - More Control with Strict Mode

有时,PowerShell 可能会出现意外行为。例如,当您输入一个不带引号的 IPv4 地址时,PowerShell 能够正常地接受它,但什么也不做。为什么?

1
PS> 1.2.3.4

在这种情况下,激活“严格模式”可能会有好处,当出现问题时会发出更严格的异常:

1
2
3
4
5
PS> Set-StrictMode -Version Latest

PS> 1.2.3.4
The property '3.4' cannot be found on this object. Verify that the property exists.
At line:1 char:1

启用严格模式后,PowerShell 会这样解释输入“1.2.3.4”:获取到浮点数 1.2,然后查询其中的 “3” 属性和其中的 “4” 属性。当然,这些属性是不存在的。禁用严格模式后,PowerShell 不会抱怨不存在的属性,只会返回“什么也没有”。这就是所发生的事情。

启用严格模式还有助于识别代码中的拼写错误。只需确保您在脚本开发机器上专门启用严格模式即可。永远不要将它添加到生产代码中。严格模式只是脚本开发人员的帮手。一旦脚本完成并移交给其他用户,请不要将开发工具留在其中。

Set-StrictMode 永远不应该放在您的代码中(并且始终以交互方式输入或作为配置文件脚本的一部分)的原因很简单:其他 PowerShell 脚本开发人员可能故意选择依赖松散的异常,并且当您的脚本强制严格模式,它也适用于从那里使用的所有代码(和模块)。在生产中启用严格模式可能会使运行良好的代码突然产生过多的红色异常。

PowerShell 技能连载 - 管理 Wi-Fi 配置文件

在Windows上,你可以使用旧的控制台命令来发现Wi-Fi配置文件:

1
PS> netsh wlan show profiles

从这里开始,您甚至可以查看个人配置文件的详细信息,并获得高速缓存的明文密码。然而,所有这些都是基于控制台的,所以它不是面向对象的,需要大量的字符串操作,当概要文件使用特殊字符或您的计算机使用不同的区域设置时,可能会返回意外的信息。

一个更好的方法是使用本地Windows API。在PowerShell图库上有一个可以使用的公共模块。这样安装:

1
PS> Install-Module -Name WifiProfileManagement -Scope CurrentUser

其余的都是微不足道的。要转储所有保存的Wi-Fi配置文件(包括名称中有特殊字符的文件),请使用Get-WiFiProfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS C:\> Get-WiFiProfile

ProfileName ConnectionMode Authentication Encryption Password
----------- -------------- -------------- ---------- --------
HOTSPLOTS_WR_Muehlenberg manual open none
Zudar06_Gast auto WPA2PSK AES
management auto WPA3SAE AES
MagentaWLAN-X5HZ auto WPA3SAE AES
Alando-Whg.17 auto WPA2PSK AES
internet-cafe auto WPA2PSK AES
Training manual WPA2PSK AES
QSC-Guest auto open none
ibisbudget manual open none
Leonardo auto open none
ROOMZ-GUEST auto open none
Freewave auto open none
PS Saturday auto WPA2PSK AES
WIFIonICE manual open none
Airport Hotel auto WPA2PSK AES

并且,要查看缓存的Wi-Fi密码,只需添加-ClearKey参数。高速缓存的密码现在将以明文形式出现在“密码”列中。

如果您有兴趣直接在您自己的代码中使用此功能,只需查看模块中的源代码即可。它是高度复杂的,但本地的电源外壳。任何正在寻找直接与Wi-Fi子系统对话的原生API方式的人都应该深入研究这些代码。

PowerShell 技能连载 - Automating User Confirmation

Some commands seem to require user input no matter what. While you can try parameters such as -Confirm:$false to get rid of default confirmations, some commands either do not support that parameter, or show their very own user requests. Here is an example:

PS> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

When you run this command (elevated) on Windows 10 or 11, at the end of the process the cmdlet wants to know whether a restart is OK:

Do you want to restart the computer to complete this operation now?
[Y] Yes  [N] No  [?] Help (default is "Y"):

The cmdlet halts until the user either enters “Y” or “N”.

To fully automate cmdlets like this, either take a closer look at its remaining parameters. Often, there are ways to articulate your choice and leave no room for ambiguities that need manual resolution. In the example above, by adding “-NoRestart”, you could deny automatic restarts and then explicitly restart the machine using Restart-Computer.

Or, you can pipe user input to a new PowerShell instance. PowerShell takes the input and places it into the keyboard input buffer. Whenever a command requests user input, it is taken from this buffer. Use comma-separated values to submit multiple items of user input to PowerShell.

Here is an example illustrating how you can submit a “N” to the command above:

PS> "N" | powershell.exe -noprofile -command Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All

PowerShell 技能连载 - Using Predictive IntelliSense

Predictive IntelliSense is a new feature in PSReadLine 2.1 and was improved in version 2.2. As such, it is available in all PowerShell consoles starting in PowerShell version 5.1. If you are uncertain whether your PowerShell console is using the latest version of PSReadLine, try this:

PS> Update-Module -Name PSReadLine

If you can’t update the module because it shipped with Windows, or you are missing privileges, try and freshly install it like so:

PS> Install-Module -Name PSReadLine -Scope CurrentUser -Force

Next, restart you PowerShell console.

The reason why you may not have heard anything about “predictive IntelliSense” is that it is turned off by default. To turn it on, run this:

PS> Set-PSReadLineOption -PredictionSource HistoryAndPlugin

In Windows PowerShell, you are limited to the option “History” only:

PS> Set-PSReadLineOption -PredictionSource History

Immediately thereafter, when you type commands in your console, you start seeing shadowed (darker) IntelliSense suggestions while you type. These suggestions are taken primarily from your command history so PowerShell starts suggesting command parameters that you commonly use. The auto-suggestion is an individualized experience, and the actual suggestions depend on your previous PowerShell commands:

PS> Get-Service | import-database -Database $db

If you don’t like this “real-time” IntelliSense, turn it off again like so:

PS> Set-PSReadLineOption -PredictionSource None

PowerShell 技能连载 - Update PowerShell’s PSReadLine

Do you know the PSReadLine module? It’s included in PowerShell 5 and 7 by default, and this module is responsible for the convenient color coding in PowerShell consoles, among a number of additional benefits that makes handling code easier. PowerShell loads this module by default in console-based environments (it’s not being used in the PowerShell ISE).

To check the current version, run this:

PS> try { (Get-Module -Name PSReadLine).Version.ToString() } catch { Write-Warning 'PSReadLine not used in this host' }
2.1.0

The line either returns the current PSReadLine version used by your PowerShell host, or emits a warning that PSReadline isn’t used by your host (i.e. inside the PowerShell ISE host which takes care of color coding internally).

You should make sure you are using the latest version of PSReadline. This line would try and update it to the latest version:

PS> Update-Module -Name PSReadLine

Just in case you can’t update the module because it shipped with Windows, try and freshly install it like so:

PS> Install-Module -Name PSReadLine -Scope CurrentUser -Force

Make sure you relaunch PowerShell after you have updated PSReadLine to load the latest version.

PSReadLine comes with powerful new features such as “predictive IntelliSense” and dynamic help that are explained here: https://devblogs.microsoft.com/powershell/psreadline-2-2-ga/

ReTweet this Tip!

PowerShell 博客文章汇总 (2021-04 ~ 2022-03)

2021 年 04 月

2021 年 05 月

2021 年 06 月

2021 年 07 月

2021 年 08 月

2021 年 09 月

2021 年 10 月

2021 年 11 月

2021 年 12 月

2022 年

2022 年 01 月

2022 年 02 月

2022 年 03 月

PowerShell 技能连载 - 在 PowerShell 使用高效的列表

默认情况下,当您定义列表、命令返回多个结果或需要在变量中存储多个内容时,PowerShell 使用简单的“对象数组”。

默认对象数组是可以的,但是一旦你创建了它们,它们的容量就不能增长。如果仍然尝试使用 “+=“ 运算符,脚本可能会突然花费很长时间或永远不会完成:

1
2
3
4
5
6
7
8
9
10
# default array
$array = @()

1..100000 | ForEach-Object {
# += is actually creating a new array each time with one more entry
# this is very slow
$array += "adding $_"
}

$array.count

那是因为 “+=“ 其实是个谎言,PowerShell 实际上需要创建一个新的更大的数组并将内容从旧数组复制到新数组。如果只是几个元素,那么还好但是如果要添加的元素较多,就会导致指数级延迟。

最常见的解决方法是使用可以动态增长的 System.Collections.ArrayList 类型。您可以简单地将默认数组强制转换为这种类型。

这是一种常用的方法,速度更快:

1
2
3
4
5
6
7
8
9
10
# use a dynamically extensible array
$array = [System.Collections.ArrayList]@()

1..100000 | ForEach-Object {
# use the Add() method instead of "+="
# discard the return value provided by Add()
$null = $array.Add("adding $_")
}

$array.count

请注意它如何使用 Add() 方法而不是 “+=“ 运算符。

不过,System.Collections.ArrayList 有两个缺点:它的 Add() 方法返回添加新元素的位置,并且由于此信息没有意义,因此需要手动丢弃它,即将其分配给 $null。并且 ArrayLists 不是特定于类型的。它们可以存储任何数据类型,这使得它们虽然灵活但效率不高。

泛型列表要好得多,使用它们只是使用不同类型的问题。一方面,泛型列表可以限制为给定类型,因此它们可以以最有效的方式存储数据并提供类型安全。以下是字符串列表的示例:

1
2
3
4
5
6
7
8
9
10
# use a typed list for more efficiency
$array = [System.Collections.Generic.List[string]]@()

1..100000 | ForEach-Object {
# typed lists support Add() as well but there is no
# need to discard a return value
$array.Add("adding $_")
}

$array.count

如果您需要一个整形列表,只需替换泛型列表类型名称中的类型:

1
[System.Collections.Generic.List[int]]

强类型限制只是“可以”,而不是“必须”。如果您希望泛型列表与 ArrayList 一样灵活并接受任何类型,请使用 “object“ 类型:

1
[System.Collections.Generic.List[object]]

PowerShell 技能连载 - 从 Internet 下载文件

Invoke-WebRequest 不仅能向 web 服务发送请求。此 cmdlet 可以与远程系统通信并来回传输数据。这就是为什么您可以使用它以非常简单直接的方式从 Internet 下载文件:

1
2
3
4
5
6
7
$url = 'https://www.nasa.gov/sites/default/files/thumbnails/image/iss065e009613.jpg'
$destination = "$env:temp\picture_nasa.jpg"

Invoke-WebRequest -UseBasicParsing -Uri $url -OutFile $destination


Invoke-Item -Path $destination

第一部分下载 NASA 图片并将其保存在本地,下一部分在您的默认查看器中打开下载的图像。

请注意,Invoke-WebRequest 可能不适用于较旧的网络和 TLS 协议。

这里还有两点需要注意:

  • -UseBasicParsing 阻止 cmdlet 使用旧的和已弃用的 “Internet Explorer” 对象模型,这种方法在近期可能导致问题。使用 IE 库从网站解析原始 HTML 曾经很有用。
  • Invoke-WebRequest 有个大哥叫 Invoke-RestMethod。两者的工作方式相同,但 Invoke-RestMethod 会自动将下载的数据转换为正确的格式,即 XML 或 JSON。对于此处示例中的简单二进制下载,该方法没有帮助。

PowerShell 技能连载 - Defender: 清空威胁列表和相关设置

默认情况下,Windows Defender 会在 15 天后自动清除它识别为威胁的项目。可以使用 PowerShell 直接配置此首选项以及许多其他首选项。只需确保启动提升权限的 PowerShell。

此示例查询清除间隔,然后设置新的清除时间间隔并对其进行验证:

1
2
3
4
5
6
7
PS> (Get-MpPreference).ScanPurgeItemsAfterDelay
15

PS> Set-MpPreference -ScanPurgeItemsAfterDelay 10

PS> (Get-MpPreference).ScanPurgeItemsAfterDelay
10