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:\>

PowerShell 技能连载 - Power Shell 5 的类继承(第二部分)

以下是在 PowerShell 5 中使用新的类特性的另一个用例。在前一个例子中,我们演示了如何从 System.Diagnostics.Process 派生一个新类,从而获得代表进程的功能更强大的对象。

以下是一个从 WebClient 派生的类,WebClient 主要是用来连接网站。当您使用标准的 WebClient 对象是,它拒绝连接到证书错误的 HTTPS 网站。这是一件好事,但是有时候您仍需要连接这类网站。

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

class MyWebClient : System.Net.WebClient
{
MyWebClient() : base()
{
# with SSL certificate errors, connect anyway
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$proxy = [System.Net.WebRequest]::GetSystemWebProxy()
$proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
$this.Proxy = $proxy
$this.UseDefaultCredentials = $true
$this.Proxy.Credentials = $this.Credentials
}
}

$client = [mywebclient]::new()
$client.DownloadString('http://www.psconf.eu')

这样,”“MyWebClient”“ 类继承于 WebClient() 并改变了 ServerCertificateValidationCallBack 的行为。它只是返回 $true,所以所有的连接都是成功的,而且证书检验变得无关紧要。

PowerShell 技能连载 - Power Shell 5 的类继承(第一部分)

PowerShell 5 内置了类的支持。您可以使用这个新特性来增强已有的 .NET 类的功能。以下是一个例子:创建一个包含新功能的增强的进程类。

进程通常是由 System.Diagnostics.Process 对象代表。它们只有有限的功能,并且假设没有能直接使用的以友好方式关闭一个应用程序的方法。您可以杀除进程(会丢失未保存的数据),或关闭它(用户可以取消关闭)。

以下是一个新的 继承于 System.Diagnostics.Process 的名为 AppInstance 的类。所以它拥有 Process 类中所有已有的功能,您可以增加额外的属性和方法:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#requires -Version 5
class AppInstance : System.Diagnostics.Process
{
# Constructor, being called when you instantiate a new object of
# this class
AppInstance([string]$Name) : base()
{
# launch the process, get a regular process object, and then
# enhance it with additional functionality
$this.StartInfo.FileName = $Name
$this.Start()
$this.WaitForInputIdle()
}

# for example, rename an existing method
[void]Stop()
{
$this.Kill()
}

# or invent new functionality
# Close() closes the window gracefully. Unlike Kill(),
# the user gets the chance to save unsaved work for
# a specified number of seconds before the process
# is killed
[void]Close([Int]$Timeout = 0)
{
# send close message
$this.CloseMainWindow()
# wait for success
if ($Timeout -gt 0)
{
$null = $this.WaitForExit($Timeout * 1000)
}
# if process still runs (user aborted request), kill forcefully
if ($this.HasExited -eq $false)
{
$this.Stop()
}
}

# example of how to change a property like process priority
[void]SetPriority([System.Diagnostics.ProcessPriorityClass] $Priority)
{
$this.PriorityClass = $Priority
}

[System.Diagnostics.ProcessPriorityClass]GetPriority()
{
if ($this.HasExited -eq $false)
{
return $this.PriorityClass
}
else
{
Throw "Process PID $($this.Id) does not run anymore."
}
}

# add static methods, for example a way to list all processes
# variant A: no arguments
static [System.Diagnostics.Process[]] GetAllProcesses()
{
return [AppInstance]::GetAllProcesses($false)
}
# variant B: submit $false to see only processes that have a window
static [System.Diagnostics.Process[]] GetAllProcesses([bool]$All)
{
if ($All)
{
return Get-Process
}
else
{
return Get-Process | Where-Object { $_.MainWindowHandle -ne 0 }
}
}
}

# you can always run static methods
[AppInstance]::GetAllProcesses($true) | Out-GridView -Title 'All Processes'
[AppInstance]::GetAllProcesses($false) | Out-GridView -Title 'Processes with Window'


# this is how you instantiate a new process and get back
# a new enhanced process object
# classic way:
# $notepad = New-Object -TypeName AppInstance('notepad')
# new (and faster) way in PowerShell 5 to instantiate new objects:
$notepad = [AppInstance]::new('notepad')

# set a different process priority
$notepad.SetPriority('BelowNormal')

# add some text to the editor to see the close message
Start-Sleep -Seconds 5

# close the application and offer to save changes for a maximum
# of 10 seconds
$notepad.Close(10)

如您在这个例子中所见,当您从这个类创建一个新实例时,它启动了一个新的进程,而且这些进程照常暴露出相同的属性和方法。而且,有一些新的例如 SetPriority()Close() 的新方法。

PowerShell 技能连载 - 显示或隐藏窗口

PowerShell 可以调用 Windows 内部的 API,在这个例子中,我们想向您展示如何改变一个应用程序窗口的显示状态。比如可以最大化、最小化、隐藏或显示窗口。

这个例子使用 PowerShell 5 最新的枚举特性对 showstate 数值赋予有意义的名字。在 PowerShell 的更早版本中,只需要移除枚举部分,并在代码中直接使用合适的 showstate 数字即可。

这里的学习要点是如何使用 Add-Type 来包装一个 C# 形式的 API 方法并在 PowerShell 代码中返回一个暴露这个方法的 type:

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
#requires -Version 5
# this enum works in PowerShell 5 only
# in earlier versions, simply remove the enum,
# and use the numbers for the desired window state
# directly

Enum ShowStates
{
Hide = 0
Normal = 1
Minimized = 2
Maximized = 3
ShowNoActivateRecentPosition = 4
Show = 5
MinimizeActivateNext = 6
MinimizeNoActivate = 7
ShowNoActivate = 8
Restore = 9
ShowDefault = 10
ForceMinimize = 11
}


# the C#-style signature of an API function (see also www.pinvoke.net)
$code = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'

# add signature as new type to PowerShell (for this session)
$type = Add-Type -MemberDefinition $code -Name myAPI -PassThru

# access a process
# (in this example, we are accessing the current PowerShell host
# with its process ID being present in $pid, but you can use
# any process ID instead)
$process = Get-Process -Id $PID

# get the process window handle
$hwnd = $process.MainWindowHandle

# apply a new window size to the handle, i.e. hide the window completely
$type::ShowWindowAsync($hwnd, [ShowStates]::Hide)

Start-Sleep -Seconds 2
# restore the window handle again
$type::ShowWindowAsync($hwnd, [ShowStates]::Show)

请注意这个例子将 PowerShell 窗口临时隐藏 2 秒钟。您可以对任何运行中的应用程序窗口做相同的事情。只需要用 Get-Process 来查找目标进程,并使用它的 “MainWindowHandle“ 属性来发送 showstate 改变请求。

一些应用程序有多个窗口。在这种情况下,您只能针对主窗口操作,否则需要先靠其它 API 来获取子窗口的句柄集合。

PowerShell 技能连载 - 用 Pester Tests 做测试

Pester 是一个随 Windows 10 和 Windows Server 2016 发布的开源模块,可以通过 PowerShell Gallery 免费下载(需要事先安装最新版本的 PowerShellGet):

1
PS C:\> Install-Module -Name Pester -Force -SkipPublisherCheck

Pester 是一个主要用来测试 PowerShell 代码的测试框架。您不仅可以用它来测试代码,而且可以用它来测试任何东西。以下是一个测试 PowerShell 版本号和一些设置的小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Describe 'PowerShell Basic Check' {

Context 'PS Versioning' {
It 'is current version' {
$host.Version.Major -ge 5 -and $host.Version.Minor -ge 1 | Should Be $true
}
}
Context 'PS Settings' {
It 'can execute scripts' {
(Get-ExecutionPolicy) | Should Not Be 'Restricted'
}
It 'does not use AllSigned' {
(Get-ExecutionPolicy) | Should Not Be 'AllSigned'
}
It 'does not have GPO restrictions' {
(Get-ExecutionPolicy -Scope MachinePolicy) | Should Be 'Undefined'
(Get-ExecutionPolicy -Scope UserPolicy) | Should Be 'Undefined'
}
}
}

当您运行它时(当然,前提是已经安装了 Pester 模块),这是得到的输出结果:

1
2
3
4
5
6
7
8
9
10
11
Describing PowerShell Basic Check

Context PS Versioning
[+] is current version 76ms

Context PS Settings
[+] can execute scripts 47ms
[+] does not use AllSigned 18ms
[+] does not have GPO restrictions 21ms

PS>

当然,这只是一个例子。您可以把它做详细并且将测试扩展到更多的其它设置或依赖条件。

PowerShell 技能连载 - 读取最新的环境变量

当您在 PowerShell 中读取环境变量时,您可能会使用 “env:“ 驱动器。例如这行代码使用 %USERNAME% 环境变量,告知您执行这段代码的用户名:

1
2
3
4
PS C:\> $env:USERNAME
tobwe

PS C:\>

env: 驱动器总是存取环境变量的操作集合。所以大多数情况所有环境变量(例如 “UserName”)都定义在这个集合之中。基本上,环境变量的操作集合是当一个应用程序启动时所有环境变量的“快照”,加上一些额外的信息(例如 “UserName”)。

要从系统或用户集合中读取最新的环境变量,请使用类似如下的代码:

1
2
3
4
5
$name = 'temp'
$scope = [EnvironmentVariableTarget]::Machine

$content = [Environment]::GetEnvironmentVariable($name, $scope)
"Content: $content"

例如您可以使用这个技术在两个进程间通信。实践方法是,打开两个 PowerShell 控制台。现在在第一个控制台中键入以下信息:

1
[Environment]::SetEnvironmentVariable("PS_Info", "Hello", "user")

在第二个 PowerShell 控制台中,键入这行代码来接收信息:

1
[Environment]::GetEnvironmentVariable("PS_Info", "user")

要清除环境变量,请在任意一个控制台中输入这行代码:

1
[Environment]::SetEnvironmentVariable("PS_Info", "", "user")

PowerShell 技能连载 - 设置环境变量

当通过 PowerShell 的 “env:“ 驱动器来设置环境变量,您只需要操作其中的数据即可。它会应用到当前的 PowerShell 实例,和所有由它启动的应用程序。不过改变并不会保存。

要设置环境变量并使之持久化,请用这段代码代替:

1
2
3
4
5
$name = 'Test'
$value = 'hello'
$scope = [EnvironmentVariableTarget]::User

[Environment]::SetEnvironmentVariable($name, $value, $scope)

这个例子设置了一个名为 “test”,值为 “hello” 的用户级别新环境变量。请注意这个改变只会影响设置这个变量之后启动的应用程序。

要彻底删除一个环境变量,请将 $value 设置成一个空字符串。

PowerShell 技能连载 - 检测宿主

在过去,Microsoft 发布了两个 PowerShell 宿主 (host):一个时基本的 PowerShell 控制台,以及更复杂的 PowerShell ISE。一些用户使用类似以下的代码来分辨脚本时运行在控制台中还是运行在 PowerShell ISE 中:

1
2
3
$inISE = $psISE -ne $null

"Running in ISE: $inISE"

然而,现在有越来越多的宿主。Visual Studio 可以作为 PowerShell 的宿主,Visual Studio Code 也可以。而且还有许多商业编辑器。所以您需要确定一个脚本是否在一个特定的环境中运行,请使用宿主标识符来代替:

1
2
3
4
$name = $host.Name
$inISE = $name -eq 'Windows PowerShell ISE Host'

"Running in ISE: $inISE"

Each host emits its own host name, so this approach can be adjusted to any host. When you run a script inside Visual Studio Code, for example, the host name is “Visual Studio Code Host”.
每个宿主会提供它的宿主名称,所以这种方法可以适用于任何宿主。例如当您在 Visual Studio Code 中运行一个脚本,宿主名会变为 “Visual Studio Code Host”。

PowerShell 技能连载 - 接触 PowerShell 6.0

PowerShell 现在是开源的,而且 PowerShell 下一个主要的 release 是在开放的环境中开发。如果您希望看一眼预览版,只需要打开源项目的发布发布页面,并且下载合适的 release:

https://github.com/PowerShell/PowerShell/releases

而且现在 PowerShell 6.0 是跨平台的。您可以同时找到适合 Linux 或 OS X 和 Windows 操作系统的版本。

当您下载某个了 Windows 平台的 ZIP 格式的 release,请先对文件解锁(右键单击文件,选择“属性”,然后解锁)。下一步,解压压缩包。在压缩包里,找到 powershell.exe,可以双击它启动一个新的 PowerShell 6.0 控制台。它是一个完全独立发行的 PowerShell,可以和现有的 PowerShell 版本同时运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.

PS C:\Users\tobwe\Downloads\PowerShell_6.0.0-alpha.15-win10-win2k16-x64> $PSVersionTable

Name Value
---- -----
PSVersion 6.0.0-alpha
CLRVersion
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
GitCommitId v6.0.0-alpha.15
BuildVersion 3.0.0.0
PSEdition Core


PS C:\Users\tobwe\Downloads\PowerShell_6.0.0-alpha.15-win10-win2k16-x64>

如果您是一个开发者,请查看 GitHub 工程:您可以查看所有源代码,甚至可以加入这个版本的开发者社区。