PowerShell 技能连载 - 隐藏返回结果的属性

默认情况下,PowerShell 会精简对象并且只显示最重要的属性:

1
2
3
4
5
6
7
8
PS C:\> Get-WmiObject -Class Win32_BIOS


SMBIOSBIOSVersion : 1.9.0
Manufacturer : Dell Inc.
Name : 1.9.0
SerialNumber : DLGQD72
Version : DELL - 1072009

要查看真实的信息,需要使用 Select-Object 并显示要求显示所有信息:

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
PS C:\> Get-WmiObject -Class Win32_BIOS | Select-Object -Property *


PSComputerName : DESKTOP-7AAMJLF
Status : OK
Name : 1.9.0
Caption : 1.9.0
SMBIOSPresent : True
__GENUS : 2
__CLASS : Win32_BIOS
__SUPERCLASS : CIM_BIOSElement
__DYNASTY : CIM_ManagedSystemElement
__RELPATH : Win32_BIOS.Name="1.9.0",SoftwareElementID="1.9.0",SoftwareElementState=3,TargetOperatingSystem=0,Version="DELL - 1072009"
__PROPERTY_COUNT : 31
__DERIVATION : {CIM_BIOSElement, CIM_SoftwareElement, CIM_LogicalElement, CIM_ManagedSystemElement}
__SERVER : DESKTOP-7AAMJLF
__NAMESPACE : root\cimv2
__PATH : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS.Name="1.9.0",SoftwareElementID="1.9.0",SoftwareElementState=3,TargetOperatingSystem=0,Version="D
ELL - 1072009"
BiosCharacteristics : {7, 9, 11, 12...}
BIOSVersion : {DELL - 1072009, 1.9.0, American Megatrends - 5000B}
BuildNumber :
CodeSet :

ClassPath : \\DESKTOP-7AAMJLF\root\cimv2:Win32_BIOS
Properties : {BiosCharacteristics, BIOSVersion, BuildNumber, Caption...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers : {dynamic, Locale, provider, UUID}
Site :
Container :

如何在自己的 PowerShell 函数中实现相同的内容并且返回自己的对象?

只需要告诉 PowerShell 缺省情况下需要可见的最重要的属性。以下是一个示例。Get-Info 函数创建一个有五个属性的自定义对象。在函数返回这个对象之前,它使用一些 PowerShell 的魔法对这个对象进行标记并且列出缺省的属性:

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
function Get-Info
{

# prepare the object returned by the function
$result = [PSCustomObject]@{
Name = $env:username
Date = Get-Date
BIOS = Get-WmiObject -Class Win32_BIOS | Select-Object -ExpandProperty SMBIOSBIOSVersion
Computername = $env:COMPUTERNAME
Random = Get-Date
}

#region Define the VISIBLE properties
# this is the list of properties visible by default
[string[]]$visible = 'Name','BIOS','Random'
$typ = 'DefaultDisplayPropertySet'
[Management.Automation.PSMemberInfo[]]$info =
New-Object System.Management.Automation.PSPropertySet($typ,$visible)

# add the information about the visible properties to the return value
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $info -InputObject $result
#endregion


# return the result object
return $result
}

以下是执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\> Get-Info

Name BIOS Random
---- ---- ------
tobwe 1.9.0 01.04.2019 19:32:44



PS C:\> Get-Info | Select-Object -Property *


Name : tobwe
Date : 01.04.2019 19:32:50
BIOS : 1.9.0
Computername : DESKTOP-7AAMJLF
Random : 01.04.2019 19:32:50

PowerShell 技能连载 - 锁定工作站

PowerShell 可以通过 C# 形式的代码操作底层 API。通过这种方法,可以在内存中编译 API 函数并添加新类型。以下例子使用一个 API 函数来锁定工作站:

1
2
3
4
5
6
7
Function Lock-WorkStation
{
$signature = '[DllImport("user32.dll",SetLastError=true)]
public static extern bool LockWorkStation();'
$t = Add-Type -memberDefinition $signature -name api -namespace stuff -passthru
$null = $t::LockWorkStation()
}

要锁定当前用户,请运行以下代码:

1
PS C:\> Lock-WorkStation

PowerShell 技能连载 - 命令发现机制揭秘(第 2 部分)

当您在 PowerShell 中键入一条命令,引擎将触发三个事件来发现您想执行的命令。这为您提供了许多机会来拦截并改变命令的发现机制。让我们教 PowerShell 当在命令中加入 >> 时将命令输出结果发送到 Out-GridView

一下是代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ExecutionContext.InvokeCommand.PreCommandLookupAction = {
param
(
[string]
$Command,

[Management.Automation.CommandLookupEventArgs]
$Obj
)

# when the command ends with ">>"...
if ($Command.EndsWith('>>'))
{
# ...remove the ">>" from the command...
$RealCommand = $Command.Substring(0, $Command.Length-2)
# ...run the original command with its original arguments,
# and pipe the results to a grid view window
$obj.CommandScriptBlock = {
& $RealCommand @args | Out-GridView
# use a new "closure" to make the $RealCommand variable available
# inside the script block when it is later called
}.GetNewClosure()
}
}

接下来,输入两条命令:

1
2
PS C:\> Get-Process -Id $PID
PS C:\> Get-Process>> -Id $PID

第一条命令只是输出当前进程。第二条命令自动将执行结果输出到 Out-GridView

如果您希望取消这种行为,请重新启动 PowerShell(或将一个空脚本块赋值给该事件)。如果您希望使该行为永久生效,请将以上代码加入到您的 profile 脚本中。

PowerShell 技能连载 - 命令发现机制揭秘(第 1 部分)

当您在 PowerShell 键入一个命令,将触发一系列事件来指定命令所在的位置。这从 PreCommandLookupAction 开始,您可以用它来记录日志。请看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ExecutionContext.InvokeCommand.PreCommandLookupAction = {
param
(
[string]
$Command,

[Management.Automation.CommandLookupEventArgs]
$Obj
)
$whitelist = @(
'prompt',
'out-default',
'psconsolehostreadline',
'Microsoft.PowerShell.Core\Set-StrictMode'
)

if ($Command -notin $whitelist -and $Obj.CommandOrigin -eq 'Runspace')
{
$host.UI.WriteLine('Yellow','White',$Command)
}
}

当您运行这段代码,所有键入的命令都将回显到控制台中——除了白名单中列出命令。这演示了 PreCommandLookupAction 的工作方式:每当您键入一条命令时,将自动触发它,而且您也可以将命令写入一个日志文件。

PowerShell 技能连载 - 向字符串添加数字(第 2 部分)

在前一个技能中我们演示了一系列安全地将变量加入到字符串中的方法。将变量变量添加到双引号包围的文本中会导致

# this is the desired output:
# PowerShell Version is 5.1.17763.316

# this DOES NOT WORK:
"PowerShell Version is $PSVersionTable.PSVersion"

当您运行这段代码,输出结果并不是大多数人想象的那样。语法着色已经暗示了错误的地方:双引号括起来的字符串只会解析变量。他们不关心后续的任何信息。所以由于 $PSVersionTable 是一个哈希表对象,PowerShell 输出的是对象类型名称,然后在后面加上 “.PSVersion”:

1
2
PS> "PowerShell Version is $PSVersionTable.PSVersion"
PowerShell Version is System.Collections.Hashtable.PSVersion

以下是四种有效的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
# use a subexpression
"PowerShell Version is $($PSVersionTable.PSVersion)"

# use the format (-f) operator
'PowerShell Version is {0}' -f $PSVersionTable.PSVersion


# concatenate (provided the first element is a string)
'PowerShell Version is ' + $PSVersionTable.PSVersion

# use simple variables
$PSVersion = $PSVersionTable.PSVersion
"PowerShell Version is $PSVersion"

PowerShell 技能连载 - 向字符串添加数字(第 1 部分)

双引号括起来的字符串可以方便地扩展变量,但是这个概念并不是万无一失的:

1
2
3
4
5
6
$id = 123

# this is the desired output:
# Number is 123:
# this DOES NOT WORK:
"Number is $id:"

如您所见的上述例子中,当您在双引号中放置变量时,PowerShell 自动判断变量的起止位置。而 : 被当成变量的一部分。要修复这个问题,您需要某种方法来明确地标记变量的起止位置。以下是一些修复这类问题的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = 123

# PowerShell escape character ends the variable
"Number is $id`:"
# braces "embrace" the variable name
"Number is ${id}:"
# subexpressions execute the code in the parenthesis
"Number is $($id):"
# the format operator inserts the array on the right into the
# placeholders in the template on the left
'Number is {0}:' -f $id
# which is essentially this:
'Number is ' + @($id)[0] + ':'

# careful with "addition": this requires the first
# element to be a string. So this works:
'Number is ' + $id + ':'
# this won't:
$id + " is the number"
# whereas this will again:
'' + $id + " is the number"

PowerShell 技能连载 - Get-PSCallStack 和调试

在前一个技能中我们使用 Get-PSCallStack 来确定代码的“调用深度”。今天我们来看看如何使用这个 cmdlet 来帮助调试。要演示这个功能,请将以下代码保存为一个脚本文件。将它保存为一个 ps1 文件十分重要。请在 PowerShell ISE 中执行它。

1
2
3
4
5
6
7
8
9
10
11
12
13
function test1
{
test2
}

function test2
{
Wait-Debugger
Get-Process

}

test1

test1 调用了 test2,并且在 test2 中,有一个 Wait-Debugger 调用。这个 cmdlet 是从 PowerShell 5 开始引入的。它会导致代码暂停并调用调试器。如果您使用的是一个早版本的 PowerShell,那么可以通过 F9 键设置一个断点。当您运行这段代码时,调试器会在 Get-Process 正要执行之前暂停,并且该行代码会以黄色高亮(如果没有效果,请检查是否已将代码保存为文件?)。

在交互式的 PowerShell 控制台中,您现在可以键入 Get-PSCallStack 来检查在代码块中停在哪里:

[DBG]: PS C:\>> Get-PSCallStack

Command Arguments Location
------- --------- --------
test2   {}        a1.ps1: Line 11
test1   {}        a1.ps1: Line 5
a1.ps1  {}        a1.ps1: Line 14

输出结果显示您当前位于函数 test2 中,它是被 test1 调用,而 test1 是被 a1.ps1 调用。

您还可以通过 InvocationInfo 属性看到更多的信息:

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
[DBG]: PS C:\>> Get-PSCallStack | Select-Object -ExpandProperty InvocationInfo


MyCommand : test2
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 5
OffsetInLine : 5
HistoryId : 17
ScriptName : C:\Users\tobwe\Documents\PowerShell\a1.ps1
Line : test2

PositionMessage : In C:\Users\tobwe\Documents\PowerShell\a1.ps1:5 Line:5
+ test2
+ ~~~~~
PSScriptRoot : C:\Users\tobwe\Documents\PowerShell
PSCommandPath : C:\Users\tobwe\Documents\PowerShell\a1.ps1
InvocationName : test2
PipelineLength : 1
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Internal
DisplayScriptPosition :

MyCommand : test1
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 14
OffsetInLine : 1
HistoryId : 17
ScriptName : C:\Users\tobwe\Documents\PowerShell\a1.ps1
Line : test1

PositionMessage : In C:\Users\tobwe\Documents\PowerShell\a1.ps1:14 Line:1
+ test1
+ ~~~~~
PSScriptRoot : C:\Users\tobwe\Documents\PowerShell
PSCommandPath : C:\Users\tobwe\Documents\PowerShell\a1.ps1
InvocationName : test1
PipelineLength : 1
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Internal
DisplayScriptPosition :

MyCommand : a1.ps1
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 0
OffsetInLine : 0
HistoryId : 17
ScriptName :
Line :
PositionMessage :
PSScriptRoot :
PSCommandPath :
InvocationName : C:\Users\tobwe\Documents\PowerShell\a1.ps1
PipelineLength : 2
PipelinePosition : 1
ExpectingInput : False
CommandOrigin : Internal
DisplayScriptPosition :

PowerShell 技能连载 - 发现嵌套层数

Get-PSCallStack 返回所谓的“调用堆栈”——它最基本的功能是告诉您代码的嵌套深度:每次进入一个脚本块,就将向堆栈加入一个新的对象。让我们看一看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function test1
{
$callstack = Get-PSCallStack
$nestLevel = $callstack.Count - 1
"TEST1: Nest Level: $nestLevel"
test2
}

function test2
{
$callstack = Get-PSCallStack
$nestLevel = $callstack.Count - 1
"TEST2: Nest Level: $nestLevel"
}

# calls test1 which in turn calls test2
test1
# calls test2 directly
test2

在这个例子中,您会看到两个函数。它们使用 Get-PSCallStack 来确定它们的“嵌套深度”。当运行 test1 时,它内部调用 test2,所以 test2 的嵌套深度为 2。而当您直接调用 test2,它的嵌套深度为 1:

TEST1: Nest Level: 1
TEST2: Nest Level: 2
TEST2: Nest Level: 1

还有一个使用相同技术的,更有用的示例:一个递归的函数调用,当嵌套深度为 10 层时停止递归:

1
2
3
4
5
6
7
8
9
10
11
12
13
function testRecursion
{
$callstack = Get-PSCallStack
$nestLevel = $callstack.Count - 1
"TEST3: Nest Level: $nestLevel"

# function calls itself if nest level is below 10
if ($nestLevel -lt 10) { testRecursion }

}

# call the function
testRecursion

以下是执行结果:

TEST3: Nest Level: 1
TEST3: Nest Level: 2
TEST3: Nest Level: 3
TEST3: Nest Level: 4
TEST3: Nest Level: 5
TEST3: Nest Level: 6
TEST3: Nest Level: 7
TEST3: Nest Level: 8
TEST3: Nest Level: 9
TEST3: Nest Level: 10

PowerShell 技能连载 - 超级简单的密码生成器

以下是一个超级简单的生成随机密码的方法。这个方法确保不会使用有歧义的字符,但不会关心其它规则,例如指定字符的最小个数。

1
2
3
4
5
6
$Length = 12
$characters = 'abcdefghkmnprstuvwxyz23456789§$%&?*+#'
$password = -join ($characters.ToCharArray() |
Get-Random -Count $Length)

$password

PowerShell 技能连载 - 优先使用 WLAN 连接

当您同时连接到 LAN 和 WLAN,并且希望指定一个优先的连接,那么您可以调整网络跃点。网络跃点值越小,网卡的优先级越高。

要列出当前首选项,请使用 Get-NetIPInterface。例如要将所有 WLAN 网卡的跃点值设成 10,请使用这行代码:

1
2
Get-NetIPInterface -InterfaceAlias WLAN |
Set-NetIPInterface -InterfaceMetric 10

改变网络跃点值需要管理员权限。相关 cmdlet 在 Windows 10 和 Windows Server 2016/2019 中可用。