PowerShell 技能连载 - 快速创建逗号分隔的字符串(第 2 部分)

在前一个技能中我们演示了如何用 PowerShell 命令模式方便地创建引号包围的字符串列表。这可以很方便地创建代码,节省很多打字工作。

以下是一个在日常 PowerShell 编码工作中有用的函数:

1
function s+ { "'$($args -join "','")'" | Set-ClipBoard }

下一次您在代码中需要一个引号包围的字符串列表时,只需要键入:

1
2
3
4
5
6
7
8
9
10
PS> s+ start stop pause end whatever

PS> 'start','stop','pause','end','whatever'
start
stop
pause
end
whatever

PS>

执行完之后,引号包围的字符串就会存在您的剪贴板中,接下来您可以将它们粘贴到任何需要的地方。

PowerShell 技能连载 - 快速创建逗号分隔的字符串(第 1 部分)

这是一个非常简单的创建引号包起来的字符串的列表的例子:

1
& { "'$($args -join "','")'" } hello this is a test

以下是执行结果:

1
'hello','this','is','a','test'

这个例子有效利用了 PowerShell 的“命令模式”,字面量被当作参数使用。您还可以将结果通过管道导出到 Set-Clipboard 指令执行,然后将结果贴回代码中。这比起手工为每个字符串添加引号方便多了。

1
2
3
4
PS> & { "'$($args -join "','")'" } hello this is a test  | Set-ClipBoard

PS> Get-ClipBoard
'hello','this','is','a','test'

PowerShell 技能连载 - 用网格视图窗口作为选择对话框(第二部分)

在前一个技能中我们介绍了如何使用哈希表来显示简单的选择对话框,而当用户选择了一个对象时,返回完整的对象。

哈希表基本上可以使用任何数据作为键。在前一个例子中,我们使用字符串作为键。它也可以是其它对象。这可以让您做选择对话框的时候十分灵活。

只需要使用 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
# create a hash table where the key is the selected properties to display,
# and the value is the original object
$hashTable = Get-WmiObject -Class Win32_NetworkAdapterConfiguration |
# sort the objects by a property of your choice
Sort-Object -Property Description |
# use an ordered hash table to keep sort order
# (requires PowerShell 3; for older PowerShell remove [Ordered])
ForEach-Object { $ht = [Ordered]@{}}{
# specify the properties that you would like to show in a grid view window
$key = $_ | Select-Object -Property Description, IPAddress, MacAddress
$ht.Add($key, $_)
}{$ht}
Group-Object -Property Description, Index -AsHashTable -AsString

# show the keys in the grid view window
$hashTable.Keys |
Out-GridView -Title "Select Network Card" -OutputMode Single |
ForEach-Object {
# and retrieve the original (full) object by using
# the selected item as key into your hash table
$selectedObject = $hashTable[$_]
$selectedObject | Select-Object -Property *
}

当您运行这段代码时,网格视图窗口显示一个网络适配器的列表,并且只显示选择的属性 (Description、IPAddress 和 MacAddress)。

当用户选择了一个元素,这段代码返回原始的(完整)对象。这样即便网格视图窗口显示的是对象的一部分,整个对象任然可以用。

PowerShell 技能连载 - 用网格视图窗口作为选择对话框(第一部分)

如何使用网格视图窗口作为一个简单的选择对话框呢?

当您将对象用管道输出到网格视图窗口中,所有属性都会显示出来。通常情况下这可以工作得很好,只需要这样一行代码:

1
Get-Service | Out-GridView -Title "Select Service" -OutputMode Single

有些时候,特别是一个对象有诸多属性时,可能会让用户看不过来:

1
2
Get-WmiObject -Class Win32_NetworkAdapterConfiguration |
Out-GridView -Title "Select Network Card" -OutputMode Single

要简化这个对话框,您可以使用我们之前在用户配置文件管理中的方法,使用一个哈希表。只需要选择一个属性作为键。这个属性必须是唯一的。接下来,试试这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
Get-WmiObject -Class Win32_NetworkAdapterConfiguration |
Out-GridView -Title "Select Network Card" -OutputMode Single

$hashTable = Get-WmiObject -Class Win32_NetworkAdapterConfiguration |
Group-Object -Property Description -AsHashTable -AsString

$hashTable.Keys |
Sort-Object |
Out-GridView -Title "Select Network Card" -OutputMode Single |
ForEach-Object {
$hashTable[$_]
}

如您所见,只有选择中的属性会在网格视图窗口中显示,当用户选择了一个元素,将获取到完整的对象。这和服务列表的工作方式很像:

1
2
3
4
5
6
7
8
9
$hashTable = Get-Service |
Group-Object -Property DisplayName -AsHashTable -AsString

$hashTable.Keys |
Sort-Object |
Out-GridView -Title "Select Service" -OutputMode Single |
ForEach-Object {
$hashTable[$_]
}

PowerShell 技能连载 - 通过对话框移除用户配置文件(第二部分)

在前一个技能中我们演示了如何用一个用一个网格视图窗口显示所有可用的用户配置文件,并且可以选中一条并删除:

1
2
3
4
5
6
7
8
9
#requires -RunAsAdministrator

Get-WmiObject -ClassName Win32_UserProfile -Filter "Special=False AND Loaded=False" |
Add-Member -MemberType ScriptProperty -Name UserName -Value { (New-Object System.Security.Principal.SecurityIdentifier($this.Sid)).Translate([System.Security.Principal.NTAccount]).Value } -PassThru |
Out-GridView -Title "Select User Profile" -OutputMode Single |
ForEach-Object {
# uncomment the line below to actually remove the selected user profile!
#$_.Delete()
}

它可以像预期中的那样工作,网格视图窗口显示许多不需要的信息。如果您想用它作为一个服务桌面程序,您肯定只希望显示其中的一部分信息。

您当然可以在将结果通过管道导出到网格视图窗口之前使用 Select-Object 来控制显示哪些信息。然而,这将会改变数据的类型,而且这将导致无法访问对象的一些成员,例如使用 Delete() 来删除用户配置文件。

所以这是一个更普遍的问题:

如何用网格视图窗口来显示自定义数据,而当用户选择一条记录时,返回原始的对象?

一个简单的方法是用 Group-Object 来创建一个哈希表:将原始的数据通过类似 “UserName” 等属性来分组。然后,在网格视图窗口中显示哈希表的键。当用户选择了一个对象时,将选中的作为键,来访问哈希表中的原始对象:

1
2
3
4
5
6
7
8
9
10
11
12
#requires -RunAsAdministrator

$hashTable = Get-WmiObject -ClassName Win32_UserProfile -Filter "Special=False AND Loaded=False" |
Add-Member -MemberType ScriptProperty -Name UserName -Value { (New-Object System.Security.Principal.SecurityIdentifier($this.Sid)).Translate([System.Security.Principal.NTAccount]).Value } -PassThru |
Group-Object -Property UserName -AsHashTable -AsString

$hashTable.Keys |
Sort-Object |
Out-GridView -Title "Select User Profile" -OutputMode Single |
ForEach-Object {
# uncomment the line below to actually remove the selected user profile!
# $hashTable[$_].Delete()

现在这个工具使用起来方便多了:网格视图窗口只显示用户名,而当您选择了一项后,可以获取到原始对象,进而做删除操作。

PowerShell 技能连载 - 通过对话框移除用户配置文件

我们收到了许多关于处理用户配置文件的技能的反馈,所以我们决定增加一系列额外的技能文章。

在前一个技能中我们演示了如何用 WMI 删除用户配置文件。有一些用户推荐使用 Remove-WmiObject 来替代 WMI 内部的 Delete() 方法。然而,Remove-WmiObject 无法删除用户配置文件实例。

以下代码汇总了我们在之前的技能中提到的所有细节。它列出所有用户配置文件,除了当前加载的和系统账户的。您可以选择一个,PowerShell 会帮您移除这个用户配置文件。

请注意以下代码并不会删除任何内容。要防止丢失数据,我们注释掉了删除操作的代码。当您去掉注释,真正地删除用户配置文件之前,请确保您知道会发生什么!

1
2
3
4
5
6
7
8
9
#requires -RunAsAdministrator

Get-WmiObject -ClassName Win32_UserProfile -Filter "Special=False AND Loaded=False" |
Add-Member -MemberType ScriptProperty -Name UserName -Value { (New-Object System.Security.Principal.SecurityIdentifier($this.Sid)).Translate([System.Security.Principal.NTAccount]).Value } -PassThru |
Out-GridView -Title "Select User Profile" -OutputMode Single |
ForEach-Object {
# uncomment the line below to actually remove the selected user profile!
#$_.Delete()
}

Are you an experienced professional PowerShell user? Then learning from default course work isn’t your thing. Consider learning the tricks of the trade from one another! Meet the most creative and sophisticated fellow PowerShellers, along with Microsoft PowerShell team members and PowerShell inventor Jeffrey Snover. Attend this years’ PowerShell Conference EU, taking place April 17-20 in Hanover, Germany, for the leading edge. 35 international top speakers, 80 sessions, and security workshops are waiting for you, including two exciting evening events. The conference is limited to 300 delegates. More details at www.psconf.eu.

PowerShell 技能连载 - 查找用户配置文件

我们收到了许多关于处理用户配置文件的技能的反馈,所以我们决定增加一系列额外的技能文章。

通常,每次当用户登录一个系统时,无论是本地登录还是远程登录,将会创建一个用户配置文件。所以随着时间的增长,可能会存在许多孤岛的用户配置文件。如果您想管理用户配置文件(包括删除不需要的),请确保排除系统使用的用户配置文件。它们可以通过 “Special” 属性来识别。

以下是一段在网格视图窗口中显示显示所有普通的用户配置文件,并且允许您选择一条记录的代码:

1
2
3
4
5
$selected = Get-CimInstance -ClassName Win32_UserProfile -Filter "Special=False" |
Add-Member -MemberType ScriptProperty -Name UserName -Value { (New-Object System.Security.Principal.SecurityIdentifier($this.Sid)).Translate([System.Security.Principal.NTAccount]).Value } -PassThru |
Out-GridView -Title "Select User Profile" -OutputMode Single

$selected

PowerShell 技能连载 - 列出用户配置文件

我们收到很多反馈,关于如何处理用户配置文件的技能,所以我们决定增加几个额外的技能。

WMI 可以方便地枚举出系统中所有用户的配置文件,但是只列出了 SID (security identifier),而不是明文的用户名。

1
2
Get-CimInstance -ClassName Win32_UserProfile |
Out-GridView

要改进这个结果,以下是一小段将 SID 转换为用户名的示例代码:

1
2
$sid = "S-1-5-32-544"
(New-Object System.Security.Principal.SecurityIdentifier($sid)).Translate([System.Security.Principal.NTAccount]).Value

要向 Get-CimInstance 指令的输出结果添加明文的用户名,您可以使用 Add-Member 指令和 ScriptProperty 属性:

1
2
3
Get-CimInstance -ClassName Win32_UserProfile |
Add-Member -MemberType ScriptProperty -Name UserName -Value { (New-Object System.Security.Principal.SecurityIdentifier($this.Sid)).Translate([System.Security.Principal.NTAccount]).Value } -PassThru |
Out-GridView

网格视图显示了一个名为 UserName 的额外列,其中包括指定用户配置文件的明文用户名。

PowerShell 技能连载 - 理解和避免双跃点问题

当一个脚本在远程执行时,您可能会遇到“拒绝访问”的问题,这通常和双跃点 (double-hop) 问题有关。以下是一个例子,并且我们将演示如何解决它:

1
2
3
4
5
6
7
8
$target = 'serverA'

$code = {
# access data from another server with transparent authentication
Get-WmiObject -Class Win32_BIOS -ComputerName serverB
}

Invoke-Command -ScriptBlock $code -ComputerName $target

以上脚本在 ServerA 服务器上执行 PowerShell 代码。远程执行的代码试图连接 ServerB 来获取 BIOS 信息。请不要介意这是否有现实意义,有关系的是远程执行的代码无法透明地登录 ServerB,即便执行这段代码的用户可以直接访问 ServerB。

双跃点问题发生在您的认证信息没有从一个远程计算机传递给另一台远程计算机时。对于任何非域控制计算机,双跃点缺省都是禁止的。

如果您想使用上述代码,您需要使用 CredSSP 来验证(远程桌面也使用了这项技术)。这需要一次性设置您和您直接访问的计算机(在这个例子中是 ServerA)之间的信任关系:

1
2
3
4
5
6
7
8
9
10
11
#requires -RunAsAdministrator

$TargetServer = 'ServerA'

# configure the computer you directly connect to
Invoke-Command -ScriptBlock {
Enable-WSManCredSSP -Role Server -Force | Out-String
} -ComputerName $TargetServer

# establish CredSSP trust
Enable-WSManCredSSP -Role Client -DelegateComputer $TargetServer -Force

当这个信任存在时,您可以使用 CredSSP 规避双跃点问题。以下是如何在 CredSSP 启用的情况下如何远程运行代码的方法:

1
2
Invoke-Command -ScriptBlock $code -ComputerName $target
-Authentication Credssp -Credential mydomain\myUser

当您使用 CredSSP 时,您不再能使用透明的登录。取而代之的是,您必须使用 -Credential 来指定用户账户。

PowerShell 技能连载 - 用 Group-Object 区分远程处理结果

当您需要通过 PowerShell 远程处理来获取多台计算机的信息时,您可以单独查询每台机器。更快的方法是同时查询多台机器,这也产生一个问题,如何区分处理结果呢?

以下是一个例子(假设您的环境中启用了 PowerShell 远程操作):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$serverList = 'TRAIN11','TRAIN12'


$code = {
Get-Service
}

# get results from all machines in parallel...
$results = Invoke-Command -ScriptBlock $code -ComputerName $serverList |
# and separate results by PSComputerName
Group-Object -Property PSComputerName -AsHashTable -AsString


$results['TRAIN11']

$results['TRAIN12']

如您所见,Invoke-Command 命令从两台机器返回了所请求的服务信息。接下来 Group-Object 命令将数据分离到两个组里,并且使用 PSComputerName 属性来标识它。PSComputerName 是一个自动属性,通过 PowerShell 远程处理接收到的数据总是带有这个属性。

这样,即便 PowerShell 远程处理在所有机器上并行执行代码,我们也可以区分每台机器的执行结果。