PowerShell 技能连载 - 使用类(重载 - 第四部分)

类中的方法可以重载:您可以定义多个同名的方法,但是参数类型不同。它用起来和 cmdlet 中的参数集类似。请看:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#requires -Version 5.0
class StopWatch
{
# property is marked "hidden" because it is used internally only
# it is not shown by IntelliSense
hidden [DateTime]$LastDate = (Get-Date)

# when no parameter is specified, do not emit verbose info

[int] TimeElapsed()
{
return $this.TimeElapsedInternal($false)
}

# user can decide whether to emit verbose info or not
[int] TimeElapsed([bool]$Verbose)
{
return $this.TimeElapsedInternal($Verbose)
}

# this method is called by all public methods

hidden [int] TimeElapsedInternal([bool]$Verbose)
{
# get current date
$now = Get-Date
# and subtract last date, report back milliseconds
$milliseconds = ($now - $this.LastDate).TotalMilliseconds
# use $this to access internal properties and methods
# update the last date so that it now is the current date
$this.LastDate = $now
# output verbose information if requested
if ($Verbose) {
$VerbosePreference = 'Continue'
Write-Verbose "Last step took $milliseconds ms." }
# use "return" to define the return value
return $milliseconds
}

Reset()
{
$this.LastDate = Get-Date
}
}

# create instance
$stopWatch = [StopWatch]::new()

# do not output verbose info
$stopWatch.TimeElapsed()


Start-Sleep -Seconds 2
# output verbose info
$stopWatch.TimeElapsed($true)

$a = Get-Service
# output verbose info
$stopWatch.TimeElapsed($true)

结果类似如下:

1
2
3
4
5
0
VERBOSE: Last step took 2018.1879 ms.
2018
VERBOSE: Last step took 68.8883 ms.
69

PowerShell 技能连载 - 使用类(增加方法 - 第三部分)

相对于 [PSCustomObject],使用 class 的最大好处之一是它也可以定义方法(命令)。以下例子实现了秒表功能。秒表可以用来计算代码执行了多少时间:

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
#requires -Version 5.0
class StopWatch
{
# property is marked "hidden" because it is used internally only
# it is not shown by IntelliSense
hidden [DateTime]$LastDate = (Get-Date)

[int] TimeElapsed()
{
# get current date
$now = Get-Date
# and subtract last date, report back milliseconds
$milliseconds = ($now - $this.LastDate).TotalMilliseconds
# use $this to access internal properties and methods
# update the last date so that it now is the current date
$this.LastDate = $now
# use "return" to define the return value
return $milliseconds
}

Reset()
{
$this.LastDate = Get-Date
}
}

以下是秒表的使用方法:

1
2
3
4
5
6
7
8
9
10
# create instance
$stopWatch = [StopWatch]::new()

$stopWatch.TimeElapsed()

Start-Sleep -Seconds 2
$stopWatch.TimeElapsed()

$a = Get-Service
$stopWatch.TimeElapsed()

结果类似如下:

1
2
3
0
2018
69

当您在一个函数中定义方法时,要遵守一系列规则:

  • 如果一个方法有返回值,那么必须指定返回值的数据类型
  • 方法的返回值必须用关键字“return”来指定
  • 方法中不能使用未赋值的变量,也不能从父作用域中读取变量
  • 要引用这个类中的属性或方法,请在前面加上“$this.

PowerShell 技能连载 - 使用类(初始化属性 - 第二部分)

可以为类的属性手动指定一个数据类型和缺省值。当您从一个类实例化一个对象时,属性已经填充好并且只接受指定的数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#requires -Version 5.0
class Info
{
# strongly typed properties with default values
[String]
$Name = $env:USERNAME

[String]
$Computer = $env:COMPUTERNAME

[DateTime]
$Date = (Get-Date)
}

# create instance
$infoObj = [Info]::new()

# view default (initial) values
$infoObj

# change value
$infoObj.Name = 'test'
$infoObj

PowerShell 技能连载 - 使用类(创建对象 - 第一部分)

从 PowerShell 5.0 开始,引入了一个新的关键字 “class”。它能够创建新的类。您可以使用类作为新对象的模板。一下代码定义了一个名为 “Info” 的新类的模板,这个类有一系列属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#requires -Version 5.0
class Info
{
$Name
$Computer
$Date
}

# generic syntax to create a new object instance
$infoObj = New-Object -TypeName Info

# alternate syntax PS5 or better (shorter and faster)
$infoObj = [Info]::new()


$infoObj

$infoObj.Name = $env:COMPUTERNAME
$infoObj.Computer = $env:COMPUTERNAME
$infoObj.Date = Get-Date

$infoObj
$infoObj.GetType().Name

您可以使用 New-Object 来创建这个类的任意多个新实例。每个实例代表有三个属性的 “Info” 类型的一个新对象。

1
2
3
4
5
Name            Computer        Date
---- -------- ----

DESKTOP-7AAMJLF DESKTOP-7AAMJLF 1/2/2017 2:00:02 PM
Info

这是一个非常简单(但很有用)的例子,演示了如何使用类来生产对象。如果您只希望在新对象中存储一些零碎信息,您也可以使用 PowerShell 3.0 引入的 [PSCustomObject] 语法:

1
2
3
4
5
6
7
8
9
#requires -Version 3.0
$infoObj = [PSCustomObject]@{
Name = $env:COMPUTERNAME
Computer = $env:COMPUTERNAME
Date = Get-Date
}

$infoObj
$infoObj.GetType().Name

这种做法没有使用一个蓝本(类),而是根据哈希表创建独立的新对象:

1
2
3
4
Name            Computer        Date
---- -------- ----
DESKTOP-7AAMJLF DESKTOP-7AAMJLF 1/2/2017 2:02:39 PM
PSCustomObject

所以新创建的对象类型永远是 “PSCustomObject“而在前一个例子中,对象的类型是通过类名定义的。

PowerShell 技能连载 - 使用命名空间

使用 .NET 的类型名称很麻烦,因为这些名字很长。以下是一个例子:

1
2
3
4
5
#requires -Version 2.0

Add-Type -AssemblyName System.Speech
$speak = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$speak.Speak('Hello I am PowerShell!')

在 PowerShell 5.0 以上版本,您可以定义希望使用的 .NET 命名空间。这些 “using namespace“ 语句必须放在脚本的开头。此时代码的可读性变得更好,并且 using 语句明确了该脚本使用了哪些 .NET 命名空间:

1
2
3
4
5
6
7
8
#requires -Version 5.0

using namespace System.Speech.Synthesis

Add-Type -AssemblyName System.Speech

$speak = New-Object -TypeName SpeechSynthesizer
$speak.Speak('Hello I am PowerShell!')

以下是另一个例子:System.IO.Path .NET 命名空间包含一系列有用的路径工具方法。以下是一些例子:

1
2
[System.IO.Path]::ChangeExtension('test.txt', 'bat')
[System.IO.Path]::GetExtension('test.txt')

现在可以不必重复地使用 [System.IO.Path] 来访问这些方法。添加一句 “using namespace System.IO“ 语句之后就可以直接访问 [Path] 类型:

1
2
3
4
5
6
#requires -Version 5.0

using namespace System.IO

[Path]::ChangeExtension('test.txt', 'bat')
[Path]::GetExtension('test.txt')

PowerShell 技能连载 - 确定个人年龄

您是如何基于生日确定一个人的年龄?您可以将 Get-Date 命令返回的当前时间减去生日事件,但是结果并不包含年数:

1
2
3
4
5
6
7
#requires -Version 1.0

$birthday = Get-Date -Date '1978-12-09'
$today = Get-Date
$timedifference = $today - $birthday

$timedifference

以下是结果:

1
2
3
4
5
6
7
8
9
10
11
Days              : 13905
Hours : 16
Minutes : 34
Seconds : 58
Milliseconds : 575
Ticks : 12014516985758198
TotalDays : 13905.6909557387
TotalHours : 333736.582937728
TotalMinutes : 20024194.9762637
TotalSeconds : 1201451698.57582
TotalMilliseconds : 1201451698575.82

要计算年数,请取 “ticks” 的数值(衡量时间最小单位),并且转换为 datetime 类型,然后取年数并减一:

1
2
3
4
5
6
7
8
#requires -Version 1.0
$birthdayString = '1978-12-09'
$birthday = Get-Date -Date $birthdayString
$today = Get-Date
$timedifference = $today - $birthday
$ticks = $timedifference.Ticks
$age = (New-Object DateTime -ArgumentList $ticks).Year -1
"Born on $birthdayString = $age Years old (at time of printing)"

这是计算结果的样子:

1
Born on 1978-12-09 = 38 Years old (at time of printing)

PowerShell 技能连载 - 加速 New-Object Synthesizer

New-Object 创建新的对象实例,在之前的“语音之周”中,您已经见到了如何创建一个语音合成器对象,并且将文本转换为语音:

1
2
3
Add-Type -AssemblyName System.Speech
$speak = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$speak.Speak('Hello I am PowerShell!')

创建对象的方法是类似的,所以如果换成一个不同的类,例如 System.Net.NetworkInformation.Ping,就可以 ping 某个 IP 地址或主机名:

1
2
3
4
5
$ping = New-Object -TypeName System.Net.NetworkInformation.Ping
$timeout = 1000
$result = $ping.Send('powershellmagazine.com', $timeout)

$result

在 PowerShell 5.0 或以上版本,有另一种方法来代替 New-Object,而且用起来更快:使用任意类型暴露的 New() 静态方法。您可以像这样重写以上的例子:

1
2
3
Add-Type -AssemblyName System.Speech
$speak = [System.Speech.Synthesis.SpeechSynthesizer]::New()
$speak.Speak('Hello I am PowerShell!')

类似地:

1
2
3
4
5
$ping = [System.Net.NetworkInformation.Ping]::New()
$timeout = 1000
$result = $ping.Send('powershellmagazine.com', $timeout)

$result

或者,可以精简:

1
[System.Net.NetworkInformation.Ping]::New().Send('powershellmagazine.com', 1000)

请注意:一旦您使用 New() 来代替 New-Object,您的代码需要 PowerShell 5.0 以上版本。

PowerShell 技能连载 - 语音之周:使用语音合成器高级选项

.NET 语音引擎不止可以接受文本输入。如果您使用 SpeakSsm(),您可以使用 XML 来切换语言、速度,以及其它文本到语音转换的参数。

以下例子需要同时安装了英语和德语的语音。如果您没有安装德语语音,请使当地修改脚本中的语言 ID。以下是查找系统中可用的语言 ID 的方法:

1
2
3
4
5
6
7
8
PS C:\> Add-Type -AssemblyName System.Speech

PS C:\> $speak.GetInstalledVoices() | Select-Object -ExpandProperty VoiceInfo | Select-Object -ExpandProperty Culture | Sort-Object -Unique

LCID Name DisplayName
---- ---- -----------
1031 de-DE German (Germany)
1033 en-US English (United States)

以下是完整的例子:

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
#requires -Version 2.0
Add-Type -AssemblyName System.Speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$ssml = '
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis"
xml:lang="en-US">
<voice xml:lang="en-US">
<prosody rate="1">
<p>I can speak English!</p>
</prosody>
</voice>
<voice xml:lang="de-DE">
<prosody rate="1">
<p>und ich kann auch deutsch sprechen!</p>
</prosody>
</voice>
<voice xml:lang="en-US">
<prosody rate="0">
<p>...and sometimes I get really tired.</p>
</prosody>
</voice>
</speak>
'

$speak.SpeakSsml($ssml)

PowerShell 技能连载 - 语音之周:记录语音到文件合成器

内置的 Microsoft 文本到语音引擎可以将音频文件保存到文件。通过这种方式,您可以自动生成 WAV 文件。以下是一个例子:它在您的桌面上创建一个新的 “clickme.wav” 文件,当您打开这个文件时,将会听到语音文本:

1
2
3
4
5
6
7
8
9
10
#requires -Version 2.0
$Path = "$home\Desktop\clickme.wav"

Add-Type -AssemblyName System.Speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.SetOutputToWaveFile($Path)
$speak.Speak('Hello I am PowerShell!')
$speak.SetOutputToDefaultAudioDevice()

Invoke-Item -Path $Path

PowerShell 技能连载 - 语音之周:更改讲述人的语音

在前一个技能中我们演示了如何使用语音转换器来念出文本。以下是查找您系统中安装的语言的方法:

1
2
3
4
5
6
#requires -Version 2.0
Add-Type -AssemblyName System.Speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.GetInstalledVoices() |
Select-Object -ExpandProperty VoiceInfo |
Select-Object -Property Culture, Name, Gender, Age

结果类似如下:

1
2
3
4
5
Culture Name                    Gender   Age
------- ---- ------ ---
en-US Microsoft Zira Desktop Female Adult
en-US Microsoft David Desktop Male Adult
de-DE Microsoft Hedda Desktop Female Adult

用这行代码可以返回缺省的语音:

1
$speak.Voice

假设您的系统安装了多个语音,以下是选择一个不同语音的方法。只需要传入您想使用的语音名字。这个例子在德文 Windows 10 系统上使用德语语音引擎:

1
2
3
4
5
#requires -Version 2.0
Add-Type -AssemblyName System.speech
$speak = New-Object System.Speech.Synthesis.SpeechSynthesizer
$speak.SelectVoice('Microsoft Hedda Desktop')
$speak.Speak('Jetzt spreche ich deutsch.')