PowerShell 技能连载 - Where-Object 和 .Where()

从 PowerShell 4 开始,当您不想使用管道的时候,可以使用 Where()ForEach() 方法来代替 Where-ObjectForEach-Object

所以如果您已经将所有数据加载到一个变量中,那么非流式操作会更高效:

1
2
3
4
5
6
$Services = Get-Service

# streaming
$Services | Where-Object { $_.Status -eq 'Running' }
# non-streaming
$Services.Where{ $_.Status -eq 'Running' }

要节约资源,最有效地方法仍然是使用流式管道,而不是用变量:

1
Get-Service | Where-Object { $_.Status -eq 'Running' }

请注意 Where-Object.Where() 使用不同的数组类型,所以它们的输出技术上是不同的:

1
2
3
4
5
PS C:\> (1..19 |  Where-Object { $_ -gt 10 }).GetType().FullName
System.Object[]

PS C:\> ((1..19).Where{ $_ -gt 10 }).GetType().FullName
System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

PowerShell 技能连载 - 快速创建对象数组

以下是一个用内置的 CSV 处理器生成对象数组的代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$csv = @'
PC,Date
PC82012,2017-02-28
PC82038,2017-02-28
PC83073,2017-02-28
PC84004,2017-02-28
PC84009,2017-02-28
PC84015,2017-02-28
PC90435,2017-02-28
'@

$data = $csv | ConvertFrom-Csv

$data
$data | Out-GridView

如果一个脚本需要一个静态的服务器、连接数据或其他信息的列表,这种方式会很有用。

PowerShell 技能连载 - 探索类型加速器

PowerShell 使用了大量所谓类型加速器来简化过长的 .NET 类型名。例如 “System.DirectoryServices.DirectoryEntry” 可以简化为 “ADSI”。

当您需要查询一个类型的完整名称时,您可以获取到实际的完整 .NET 类型名:

1
2
3
4
PS C:\> [ADSI].FullName
System.DirectoryServices.DirectoryEntry

PS C:\>

以下代码在 PowerShell 中输出所有的内置 .NET 类型加速器:

1
2
[PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::get |
Out-GridView

除了显式的类型加速器之外,还有一个 PowerShell 内置的规则:在 System 命名空间中的类型加速器可以省略命名空间。所以以下的表达完全一致:

1
2
3
4
5
6
7
8
PS C:\> [int].FullName
System.Int32

PS C:\> [System.Int32].FullName
System.Int32

PS C:\> [Int32].FullName
System.Int32

PowerShell 技能连载 - 危险的临时文件!

内部的系统功能往往十分有用,但请确保真正了解它们的功能。

一个特别常见的系统方法叫做 GetTempFileName() ,能够创建临时文件名。而当您进一步观察的时候,您会发现它不仅创建临时文件名,而且还创建了临时文件:

1
2
$file = [System.IO.Path]::GetTempFileName()
Test-Path -Path $file

所以如果在脚本中只是使用这个方法来创建临时文件名的话,会留下一大堆孤立的文件。

PowerShell 技能连载 - 探索对象

在 PowerShell 中,一切都是用对象描述。以下是一个检查任意对象并将它的成员以文本的方式复制到剪贴板的单行代码:

1
2
3
4
5
"Hello" |
Get-Member |
Format-Table -AutoSize -Wrap |
Out-String -Width 150 |
clip.exe

只需要将 “Hello” 替换成任何变量或命令,然后看看复制了什么到剪贴板中。您可以将信息粘贴到文本编辑器或文字处理器中,并将它打印出来或转成 PDF 备用。

PowerShell 技能连载 - 管理比特标志位(第四部分)

在 PowerShell 5 中,对枚举的新支持特性使得处理比特位比您在前面的 PowerShell 技能中看到的简单得多。现在设置或清除比特位不再需要冗长的逻辑操作符。

我们先定义一个枚举类型,这样更好管理十进制数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#requires -Version 5

[Flags()]
enum GardenPartyItems
{
Chair = 0
Table = 1
Barbecue = 2
Fridge = 4
Candle = 8
Knife = 16
}

$decimal = 11
[GardenPartyItems]$flags = $decimal
$flags

现在,十进制数的比特位可以很容易地转化为 GardenPartyItem 的列表:

1
2
3
4
PS C:\>  [GardenPartyItems]11
Table, Barbecue, Candle

PS C:\>

注意:将十进制数转换为枚举型时,请确保枚举型中定义了所有的比特。如果十进制数太大,包含枚举型之外的比特时,转换会失败。

要增加一个新的标志位,请试试以下的代码:

1
2
3
4
5
6
7
8
9
PS C:\> $flags
Table, Barbecue, Candle

PS C:\> $flags += [GardenPartyItems]::Knife

PS C:\> $flags
Table, Barbecue, Candle, Knife

PS C:\>

要移除一个标志位,请试试以下代码:

1
2
3
4
5
6
7
8
9
PS C:\> $flags
Table, Barbecue, Candle, Knife

PS C:\> $flags -= [GardenPartyItems]::Candle

PS C:\> $flags
Table, Barbecue, Knife

PS C:\>

然而,实际上并没有看起来这么简单。当移除一个已有的标志位,没有问题。但移除一个没有置位的标志位,会把比特值搞乱:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> $flags
Table, Barbecue, Candle

PS C:\> $flags -= [GardenPartyItems]::Candle

PS C:\> $flags
Table, Barbecue

PS C:\> $flags -= [GardenPartyItems]::Candle

PS C:\> $flags
-5

PS C:\>

所以 PowerShell 在自动处理二进制算法方面明显还不够智能。要安全地使用该功能,您还是要用二进制操作符。要移除标志位,请使用 -band-bnot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> $flags
Table, Barbecue, Candle

PS C:\> $flags = $flags -band -bnot [GardenPartyItems]::Candle

PS C:\> $flags
Table, Barbecue

PS C:\> $flags = $flags -band -bnot [GardenPartyItems]::Candle

PS C:\> $flags
Table, Barbecue

PS C:\>

要设置标志位,请使用 -bor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PS C:\> $flags
Table, Barbecue, Candle

PS C:\> $flags = $flags -bor [GardenPartyItems]::Knife

PS C:\> $flags
Table, Barbecue, Candle, Knife

PS C:\> $flags = $flags -bor [GardenPartyItems]::Knife

PS C:\> $flags
Table, Barbecue, Candle, Knife

PS C:\>

在所有这些操作中,实际上是在操作一个十进制数:

1
2
PS C:\> [Int]$flags
19

相当棒,对吧?

PowerShell 技能连载 - 管理比特标志位(第三部分)

对十进制数设置比特标志位不是很难,但是不够直观。以下是一个快速的新方法,演示如何设置或取消一个数字中特定的比特:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$decimal = 6254
[Convert]::ToString($decimal, 2)

# set bit 4
$bit = 4
$decimal = $decimal -bor [Math]::Pow(2, $bit)
[Convert]::ToString($decimal, 2)

# set bit 0
$bit = 0
$decimal = $decimal -bor [Math]::Pow(2, $bit)
[Convert]::ToString($decimal, 2)

# clear bit 1
$bit = 1
$decimal = $decimal -band -bnot [Math]::Pow(2, $bit)
[Convert]::ToString($decimal, 2)

结果演示了代码做了什么。ToString() 从右到左显示比特,所以第 0 比特是在最右边。在第二行和第三行,设置了两个独立的比特位,而并不影响其它位。在最后一行中,清除了一个比特位。

1
2
3
4
1100001101110
1100001111110
1100001111111
1100001111101

PowerShell 技能连载 - 管理比特标志位(第二部分)

在前一个技能中我们演示了如何使用 PowerShell 5 新的枚举特性来解析bite标志位,甚至可以独立地检测每个标志位。

如果您无法使用 PowerShell 5,在早期的 PowerShell 版本中,仍然可以使用这个技术只需要通过 C# 代码来定义枚举即可:

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
# this is the decimal we want to decipher
$rawflags = 56823


# define an enum with the friendly names for the flags
# don't forget [Flags]
# IMPORTANT: you cannot change your type inside a PowerShell session!
# if you made changes to the enum, close PowerShell and open a new
# PowerShell!
$enum = '
using System;
[Flags]
public enum BitFlags
{
None = 0,
Option1 = 1,
Option2 = 2,
Option3 = 4,
Option4 = 8,
Option5 = 16,
Option6 = 32,
Option7 = 64,
Option8 = 128,
Option9 = 256,
Option10= 512,
Option11= 1024,
Option12= 2048,
Option13= 4096,
Option14= 8192,
Option15= 16384,
Option16= 32768,
Option17= 65536
}
'
Add-Type -TypeDefinition $enum

# convert the decimal to the new enum
[BitFlags]$flags = $rawflags
$flags

# test individual flags
$flags.HasFlag([BitFlags]::Option1)
$flags.HasFlag([BitFlags]::Option2)

如您所见,从十进制数转换到新的枚举类型使用正常而且非常简单:

1
2
3
4
PS C:\> [BitFlags]6625
Option1, Option6, Option7, Option8, Option9, Option12, Option13

PS C:\>

PowerShell 技能连载 - 管理比特标志位(第一部分)

有时候您会需要处理比特标志位值。一个数字中的每个比特代表一个特定的设置,并且您的代码可能需要决定一个标志位是否置位,而不能影响别的比特。

这常常需要一系列位操作。然而在 PowerShell 5 中,有一个简单得多的办法——标志位枚举。

假设有一个值 56823,并且希望知道哪个比特是置位的。您需要将该数字转换成可视化的比特:

1
2
PS C:\> [Convert]::ToString(56823, 2)
1101110111110111

如果您了解每个比特的意义,那么一个更强大的方法是定义一个枚举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#requires -Version 5

[flags()]
enum CustomBitFlags
{
None = 0
Option1 = 1
Option2 = 2
Option3 = 4
Option4 = 8
Option5 = 16
Option6 = 32
Option7 = 64
Option8 = 128
Option9 = 256
Option10= 512
Option11= 1024
Option12= 2048
Option13= 4096
Option14= 8192
Option15= 16384
Option16= 32768
Option17= 65536
}

对每个比特提供一个友好的名字,并且记得添加属性 [Flags](这将允许设置多个值)。

现在要解析这个十进制值非常简单——只需要将它转换成新定义的枚举类型:

1
2
$rawflags = 56823
[CustomBitFlags]$flags = $rawflags

这时得到的结果:

1
2
PS C:\> $flags
Option1, Option2, Option3, Option5, Option6, Option7, Option8, Option9, Option11, Option12, Option13, Option15, Option16

如果您只希望检测某个标志位是否置位,请使用 HasFlag() 方法:

1
2
3
4
5
6
7
PS C:\> $flags.HasFlag([CustomBitFlags]::Option1)

True

PS C:\> $flags.HasFlag([CustomBitFlags]::Option4)

False

PowerShell 技能连载 - 使用泛型

泛型可以作为实际类型的占位符,您可能会好奇为什么它会有意思。

有许多不同的数据类型没有 NULL 值。例如 Integer 和 Boolean 型,没有办法指出一个值是非法的还是未设置。您可以通过将一个 0(或者 -1)指定为某个 integer 变量的 “undefined” 值。但如果所有的数字都是合法的值呢?对于 Boolean,情况也是一样:虽然您可以定义 $false 值为 “undefined” 值,但许多情况下的确需要三种值:$true$flaseundefined

泛型是解决的办法,您可以使用 Nullable 类型根据任何合法的类型来创建自己的可空值类型。

1
2
3
4
5
[Nullable[int]]$number =  $null
[Nullable[bool]]$flag = $null

$number
$flag

用常规数据类型来做数据转换:

1
2
3
4
5
6
7
PS C:\> [int]$null
0

PS C:\> [bool]$null
False

PS C:\>