PowerShell 技能连载 - 使用 PowerShell 类(一)

从 PowerShell 5 开始,您可以定义类。它们有许多应用场景。一个是为有用的工具函数创建一个库来更好地整理它们。要实现这个功能,这个类要定义一些 “static” 方法。以下是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HelperStuff
{
# get first character of string and throw exception
# when string is empty or multi-line
static [char] GetFirstCharacter([string]$Text)
{
if ($Text.Length -eq 0) { throw 'String is empty' }
if ($Text.Contains("`n")) { throw 'String contains multiple lines' }
return $Text[0]
}

# get file extension in lower case
static [string] GetFileExtension([string]$Path)
{
return [Io.Path]::GetExtension($Path).ToLower()
}
}

“HelperStuff” 类定义了 “GetFirstCharacter“ 和 “GetFileExtension“ 两个静态方法。现在查找和使用这些工具函数非常方便:

1
2
3
4
5
6
7
8
9
10
PS> [HelperStuff]::GetFirstCharacter('Tobias')
T

PS> [HelperStuff]::GetFileExtension('c:\TEST.TxT')
.txt

PS> [HelperStuff]::GetFileExtension($profile)
.ps1

PS>

PowerShell 技能连载 - 读取注册表键值(临时解决办法)

在前一个技能中我们演示了 Get-ItemProperty 无法读取数据错误的注册表键值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> $key =  "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group  Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"


PS> Get-ItemProperty -Path $key
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyComma
nd


PS>

有一个变通办法,您可以使用 Get-Item 代替,来存取注册表键,这将使用它的 .NET 成员来读取所有值:

1
2
3
4
5
6
7
8
9
10
11
$key = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"

$key = Get-Item -Path $key

$hash = @{}
foreach ($prop in $key.Property)
{
$hash.$prop = $key.GetValue($prop)
}

$hash

结果看起来如下:

1
2
3
4
5
6
7
8
9
10
11
12
Name                           Value
---- -----
Extensions [{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{0F6B957E-509E-11D1-A7CC-0000F87571E3}]
Link Local
Options 0
GPOLink 1
Version 65537
GPOName Guidelines of the local group
lParam 0
DSPath LocalGPO
FileSysPath C:\WINDOWS\System32\GroupPolicy\Machine
DisplayName Guidelines of the local group

PowerShell 技能连载 - 读取注册表键值失败

有些时候,读取注册表键值可能会失败,提示奇怪的错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS> $key =  "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group  Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0"


PS> Get-ItemProperty -Path $key
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyComma
nd


PS>

当发生这种情况时,用 regedit.exe 检查注册表发现一个或多个键值已破坏。在我们的例子中,”lParam” 的值似乎在所有的 Windows 机器中都是错误的。Regedit.exe 报告“(invalid … value)”。

在这个例子中,Get-ItemProperty 指令并不会读出任何值。您无法也排除该值:

1
2
3
4
5
6
7
8
9
10
PS>  Get-ItemProperty -Path $key -Include * -Exclude lParam
Get-ItemProperty : Specified cast is not valid.
At line:1 char:1
+ Get-ItemProperty -Path $key -Include * -Exclude lParam
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ItemProperty], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand


PS>

可以采取的措施是只读取合法的键值:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS> Get-ItemProperty -Path $key -Name DSPath


DSPath : LocalGPO
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersi
on\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}\0
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersi
on\Group Policy\History\{35378EAC-683F-11D2-A89A-00C04FBBCFA2}
PSChildName : 0
PSProvider : Microsoft.PowerShell.Core\Registry


PS>

PowerShell 技能连载 - 每日问候(带语音)

在前一个技能中我们解释了如何在 PowerShell 配置文件中增加个人问候。这个问候信息也可以朗读出来,假设音量打开的情况下。这对所有的 PowerShell 宿主都有效,包括 VSCode。

这将把代码增加到您的配置文件脚本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# create profile if it does not yet exist
$exists = Test-Path -Path $Profile.CurrentUserAllHosts
if (!$exists)
{
$null = New-Item -Path $Profile.CurrentUserAllHosts -ItemType File -Force
}

# add code to profile
@'
$greetings =
'Hello there!',
'Glad to see you!',
'Happy coding!',
'Have a great day!',
'May the PowerShell be with you!'

$text = $greetings | Get-Random
$null = (New-Object -COM Sapi.SpVoice).Speak($text)
'@ | Add-Content -Path $Profile.CurrentUserAllHosts -Encoding Default

要编辑用户配置文件,请运行这段代码:

1
PS> notepad $profile.CurrentUserAllHosts

PowerShell 技能连载 - 每日问候

以下是一个在 PowerShell 中接受一个字符串数组并返回一个随机的字符串,可以用作自定义问候语的简单方法:

1
2
3
4
5
6
7
8
$greetings =
'Hello there!',
'Glad to see you!',
'Happy coding!',
'Have a great day!',
'May the PowerShell be with you!'

$greetings | Get-Random

您所需要做的只是将这段代码加到您的 profile 脚本,例如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# create profile if it does not yet exist
$exists = Test-Path -Path $Profile.CurrentUserAllHosts
if (!$exists)
{
$null = New-Item -Path $Profile.CurrentUserAllHosts -ItemType File -Force
}

# add code to profile
@'
$greetings =
'Hello there!',
'Glad to see you!',
'Happy coding!',
'Have a great day!',
'May the PowerShell be with you!'

$greetings | Get-Random
'@ | Add-Content -Path $Profile.CurrentUserAllHosts -Encoding Default

完成以后,PowerShell 将会使用自定义信息向您问候。

PowerShell 技能连载 - PowerShell 中 LINQ 的真实情况

不久前有一些关于 LINQ,一个 .NET 查询语言,在 PowerShell 中用来提升代码速度的报告。

直到 PowerShell 真正支持 Linq 之前,使用 Linq 是非常冗长的,并且需要使用强类型和没有文档的方法。另外,同样的事可以使用纯 PowerShell 方法来做,速度的提升很少——至少对 IPPro 相关的任务不明显。

以下是一个使用很简单的 Linq 语句对数字求和的测试用例。它接受 Windows 文件夹下的所有文件,然后对所有文件的长度求和:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$numbers = Get-ChildItem -Path $env:windir -File | Select-Object -ExpandProperty Length

(Measure-Command {
$sum1 = [Linq.Enumerable]::Sum([int[]]$numbers)
}).TotalMilliseconds

(Measure-Command {
$sum2 = ($numbers | Measure-Object -Sum).Sum
}).TotalMilliseconds

(Measure-Command {
$sum3 = 0
foreach ($number in $numbers) { $sum3+=$number }
}).TotalMilliseconds

当您运行它多次的时候,您会观察到执行时间的输出。Linq 的方法可以使用,但是对数据类型十分敏感。例如,您需要将数字数组转换为 integer 数组,否则 Linq 的 Sum() 方法将不起作用。

可以提炼出两条法则:

  1. 这时不值得使用 Linq,因为它尚未集成到 PowerShell 中,并且会产生难读的代码。它几乎相当于在 PowerShell 使用 C# 源代码。

  2. 如果您想提升速度,请在所有可能的地方避免使用管道。foreach 循环的执行速度比用管道将许多对象通过管道传到 ForEach-Object 快许多。

If Linq was better integrated into PowerShell in the future, it would indeed be highly interesting.

PowerShell 技能连载 - 神秘的 Windows 10 透明模式

When you open a native PowerShell console in Windows 10, you can hold down CTRL+SHIFT, then move your mouse wheel, to adjust console background color transparency, and let other windows shine through. The same works for cmd.exe as well, of course.
当您在 Windows 10 中打开一个原生的 PowerShell 控制台,按下 CTRL + SHIFT 键,然后滚动鼠标滚轮,就可以调节控制台背景色的透明度,并且让其它窗口的内容透射出来。当然这对 cmd.exe 也是有效的。

PowerShell 技能连载 - 清空 DNS 缓存

Windows 使用了 DNS 缓存技术,如果改变了 DNS 服务器,您需要刷新 DNS 缓存以使新的设置生效。PowerShell 对传统的控制台命令是有好的,所以只需要在 PowerShell 中运行这行代码:

1
PS> ipconfig /flushdns

PowerShell 技能连载 - 显示文件夹树

PowerShell 对旧的控制台命令是十分友好的,所以要显示文件夹的树形结构,使用旧的 “tree” 命令是十分简单的。它最好工作在一个原生的 PowerShell 控制台中,因为编辑器往往使用不同的字符集。请试试这个命令:

1
PS> Tree $home

请确保您是在一个原生的 PowerShell 控制台中或 VSCode 中运行这段代码。您还可以将结果通过管道输出到 clip.exe 并将它粘贴到一个文本文档中:

1
PS> Tree $home | clip.exe

PowerShell 技能连载 - 使用缓存的端口文件

在前一个技能中我们介绍了如何用 PowerShell 通过 IANA 下载端口分配信息。这个过程需要 Internet 连接并且需要一段时间。所以以下代码会查找缓存的 CSV 文件。如果缓存文件存在,端口信息会从离线文件中加载,否则将在线加载数据,并写入缓存文件。请特别注意如何使用 Tee-Object 命令创建缓存文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$url = 'https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.csv'
$CSVFile = "$env:temp\ports.csv"
$exists = Test-Path -Path $CSVFile

if (!$exists)
{
Write-Warning "Retrieving data online..."

$portinfo = Invoke-WebRequest -Uri $Url -UseBasicParsing | `
Select-Object -ExpandProperty Content | `
Tee-Object -FilePath $CSVFile | ConvertFrom-Csv
}
else
{
Write-Warning "Loading cached file..."
$portinfo = Import-Csv -Path $CSVFile
}

$portinfo | Out-GridView