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 远程处理在所有机器上并行执行代码,我们也可以区分每台机器的执行结果。

PowerShell 技能连载 - 转换数字字符串

在 PowerShell 中转换一个包含数字的字符串非常简单:

1
2
3
4
PS C:\> [double]"77.234"
77,234

PS C:\>

不过,如果字符串包含的不只是纯数字,那么就比较有挑战性了。例如,您需要转换一个类似 “2763MB” 的字符串,PowerShell 无法自动将它转换为一个数字。这时候您需要一个类似这样的转换函数:

1
2
3
4
5
6
7
function Convert-MBToByte($MBString)
{
$number = $MBString.Substring(0, $MBString.Length-2)
1MB * $number
}

Convert-MBToByte -MBString '2433MB'

或者如果它是一个合法的 PowerShell 代码格式,您可以试着让 PowerShell 来做转换:

1
2
3
4
PS C:\> Invoke-Expression -Command '2615MB'
2742026240

PS C:\>

然而,不推荐使用 Invoke-Expression,因为它会带来安全风险。例如用户能够改变命令执行的表达式,类似 SQL 注入攻击。

PowerShell 技能连载 - 使用缺省参数值

您可能听说过 PowerShell 的缺省参数值和 $PSDefaultParameterValues。当您将一个哈希表赋给这个特殊的变量,则哈希表的键定义了命令和影响的参数,而值定对应新的缺省值。

请看这个例子:

1
2
3
$PSDefaultParameterValues = @{
'*:ComputerName' = 'testserver1'
}

这将会把所有命令 (*) 的 -ComputerName 设置为新的缺省值 “testserver1”。当您调用一个命令,且满足以下两个条件 (a) 有一个名为 ComputerName 的参数 (b) 没有显式地赋值给这个参数时,将会使用缺省值。

这对 PowerShell 函数也有效,然而只对 “Advanced Functions” 有效,对 “Simple Functions” 无效。

要验证区别,请按上述介绍定义 $PSDefaultParameterValues,然后运行这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function testSimple
{
param($Computername)

"Result Simple: $Computername"
}

function testAdvanced
{
[CmdletBinding()]
param($Computername)

"Result Advanced: $Computername"
}


testSimple

testAdvanced

如您所见,只有 testAdvanced 函数应用了缺省参数。”Advanced Functions” 至少要定义一个参数属性,例如 [CmdletBinding()][Parameter(Mandatory)]