PowerShell 技能连载 - 查找 PowerShell 类

从 PowerShell 5 开始,您可以定义 PowerShell 类。它们是动态定义的,并且存在于内存中。那么要如何知道这些类的名字?

我们首先定义一个简单的,没有任何内容的类:

1
2
3
4
class TestClass
{

}

如何确认内存中确实存在一个名为 “TestClass” 的类?以下是一个名为 Get-PSClass 的工具函数:

1
2
3
4
5
6
7
8
9
10
11
function Get-PSClass($Name = '*')
{
[AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GetCustomAttributes($false) |
Where-Object { $_ -is [System.Management.Automation.DynamicClassImplementationAssemblyAttribute]} } |
ForEach-Object { $_.GetTypes() |
Where-Object IsPublic |
Where-Object { $_.Name -like $Name } |
Select-Object -ExpandProperty Name
}
}

执行这个函数后,它会返回当前内存中所有定义的 PowerShell 类(在我们的 PowerShell 例子中,在前几个技能实验中有好几个 PowerShell 类):

1
2
3
4
5
6
PS> Get-PSClass
HelperStuff
Employee
TestClass

PS>

您也可以显示地测试一个类名:

1
2
3
4
5
6
7
8
PS> Get-PSClass -Name TestClass
TestClass

PS> (Get-PSClass -Name TestClass) -ne $null
True

PS> (Get-PSClass -Name TestClassNotExisting) -ne $null
False

您也可以使用通配符。一下代码将返回所有以 “A” 至 “H” 字母开头的类:

1
2
3
PS> Get-PSClass -Name '[A-H]*'
HelperStuff
Employee

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

从 PowerShell 5 开始,您可以定义 PowerShell 类。您可以使用类来创建新对象,并通过创建一个或多个构造函数,您可以方便地初始化新创建的对象。

让我们看看效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Employee
{
[int]$Id
[string]$Name

Employee([int]$Id, [string]$Name)
{
$this.Id = $Id
$this.Name = $Name
}
Employee ([string]$Name)
{
$this.Id = -1
$this.Name = $Name
}
Employee ()
{
$this.Id = -1
$this.Name = 'Undefined'
}
}

这段代码运行后,将创建一个包含三个构造函数的 “Employee” 新类。以下是如何使用新类的方法:

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
PS> [Employee]::new()

Id Name
-- ----
-1 Undefined



PS> [Employee]::new('Tobias')

Id Name
-- ----
-1 Tobias



PS> [Employee]::new(999, 'Tobias')

Id Name
-- ----
999 Tobias



PS>

每次调用都使用一个新的构造函数,并且该类根据需要创建相应的对象。

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