如何按 PowerShell 中的所有属性分组?
How to group by all properties in PowerShell?
我打算删除 Windows 上的重复防火墙规则。我知道有一组防火墙 cmdlet,如 get-netfirewallrule
,但当我尝试通过防火墙规则以获取其他信息时,它太慢了,如 Get-NetFirewallRule -displayname xxx | Get-NetFirewallApplicationFilter
。所以我打算使用老派的方式,netsh advfirewall firewall
命令集。
现在我已经将 netsh advfirewall firewall
的输出解析为一个对象数组。
$output = netsh advfirewall firewall show rule name=all verbose | Out-String
$output = [regex]::split($output.trim(), "\r?\n\s*\r?\n");
$objects = @(foreach($section in $output) {
$obj = [PSCustomObject]@{}
foreach($line in $($section -split '\r?\n')) {
if($line -match '^\-+$') {
continue
}
$name, $value = $line -split ':\s*', 2
$name = $name -replace " ", ""
$obj | Add-Member -MemberType NoteProperty -Name $name -Value $value
}
$obj
})
但问题是数组中的对象具有不同的属性。例如,这是一个对象:
RuleName : HNS Container Networking - ICS DNS (TCP-In) - B15BF139-F18D-471C-A18C-92DFD33350F1 - 0
Description : HNS Container Networking - ICS DNS (TCP-In) - B15BF139-F18D-471C-A18C-92DFD33350F1 - 0
Enabled : Yes
Direction : In
Profiles : Domain,Private,Public
Grouping :
LocalIP : Any
RemoteIP : Any
Protocol : TCP
LocalPort : 53
RemotePort : Any
Edgetraversal : No
Program : C:\WINDOWS\system32\svchost.exe
Service : sharedaccess
InterfaceTypes : Any
Security : NotRequired
Rulesource : Local Setting
Action : Allow
这是另一个:
RuleName : HNS Container Networking - DNS (UDP-In) - 91EC1DEF-8CB8-4C2A-A6D4-91480448AE97 - 0
Description : HNS Container Networking - DNS (UDP-In) - 91EC1DEF-8CB8-4C2A-A6D4-91480448AE97 - 0
Enabled : Yes
Direction : In
Profiles : Domain,Private,Public
Grouping :
LocalIP : Any
RemoteIP : Any
Protocol : UDP
LocalPort : 53
RemotePort : Any
Edgetraversal : No
InterfaceTypes : Any
Security : NotRequired
Rulesource : Local Setting
Action : Allow
如您所见,第一个规则使用端口和程序来定义规则,第二个仅使用端口。如何对对象进行分组并找到重复的项目(以便我可以删除相应的防火墙规则)?我不能简单地使用 $objects | Group-Object -Property rulename,enabled,.....
,因为所有对象的属性都不相同。
顺便说一句,重复的防火墙规则是由我过去编写的一些不完善的脚本创建的。
您已经了解(或记住)在 PowerShell 出现之前解析命令输出的困难方法但是您已经完成了大部分工作,我们只需要对您尝试获取所有通用属性的尝试进行一些修改returned 个对象:
$output = ( netsh advfirewall firewall show rule name=all verbose |
Out-String ).Trim() -split '\r?\n\s*\r?\n'
$propertyNames = [System.Collections.Generic.List[string]]::new()
$objects = @( $(foreach($section in $output ) {
$obj = @{}
foreach( $line in ($section -split '\r?\n') ) {
if( $line -match '^\-+$' ) {
continue
}
$name, $value = $line -split ':\s*', 2
$name = $name -replace " ", ""
$obj.$name = $value
if( $propertyNames -notcontains $name ) {
$propertyNames.Add( $name )
}
}
$obj
}) | ForEach-Object {
foreach( $prop in $propertyNames ) {
if( $_.Keys -notcontains $prop ) {
$_.$prop = $null
}
}
[PSCustomObject]$_
})
我将解释我对您的代码所做的调整:
- 将
$output
设置为一行(为了便于阅读,此处设置为多行)。并非绝对必要。
- 为了与代码的其余部分保持一致,请使用
-split
运算符而不是 [regex]::split()
。
- 创建了一个名为
$propertyNames
的列表。这将用于跟踪从输出中读取的所有 属性 个名称。
- 子表达式
$()
您的第一个 foreach
循环以防止需要中间变量,让我们将 foreach
输出传递到管道。这在最后是必要的。它必须是一个子表达式,而不是仅仅被 ()
. 包围
- 将
$obj
设为 hashtable
而不是 PSCustomObject
。修改对象时更容易使用,最后可转换为 PSCustomObject
。
Add-Member
不再需要,因为我们有 hashtable
,所以 $obj.$name = $value
足以设置 属性 名称和值。
- 注:如果将这部分答案应用到其他领域,watch for pitfalls if the property name is anything other than a string。或者,如果这是一个问题,您可以使用传统的数组访问器语法:
$obj[$name] = $value
.
- 如果
$name
不在 $propertyList
中跟踪的已知 属性 名称中,请添加它。 foreach
部分循环完成后,我们将拥有所有可能的属性 return 通过您的 netsh
命令编辑。
- 现在我们可以 return
$obj
netsh
输出的每个部分。我们可能仍然需要更改它,所以暂时不要将其变成 PSCustomObject
。
- 将您的子表达式
foreach
循环的输出通过管道传输到 ForEach-Object
。同样,这避免了对中间变量的需要。我们需要遍历每个 $obj
因为....
- 在
ForEach-Object
中,遍历 $propertyList
并检查每个已知 属性 是否作为每个 $obj
上的键(表示为 $_
或 ScriptBlock
内的 $PSitem
。如果不存在,则添加值为 $null
。
- 最后,将
$_
(这将是第一个循环中的每个 $obj
return)转换为 PSCustomItem
,因为我们 return 它。
这是您的代码与我的更改之间的差异:
现在,每个 returned 对象都应该具有相同的属性,即使它们没有显示在该规则的原始输出中。
我打算删除 Windows 上的重复防火墙规则。我知道有一组防火墙 cmdlet,如 get-netfirewallrule
,但当我尝试通过防火墙规则以获取其他信息时,它太慢了,如 Get-NetFirewallRule -displayname xxx | Get-NetFirewallApplicationFilter
。所以我打算使用老派的方式,netsh advfirewall firewall
命令集。
现在我已经将 netsh advfirewall firewall
的输出解析为一个对象数组。
$output = netsh advfirewall firewall show rule name=all verbose | Out-String
$output = [regex]::split($output.trim(), "\r?\n\s*\r?\n");
$objects = @(foreach($section in $output) {
$obj = [PSCustomObject]@{}
foreach($line in $($section -split '\r?\n')) {
if($line -match '^\-+$') {
continue
}
$name, $value = $line -split ':\s*', 2
$name = $name -replace " ", ""
$obj | Add-Member -MemberType NoteProperty -Name $name -Value $value
}
$obj
})
但问题是数组中的对象具有不同的属性。例如,这是一个对象:
RuleName : HNS Container Networking - ICS DNS (TCP-In) - B15BF139-F18D-471C-A18C-92DFD33350F1 - 0
Description : HNS Container Networking - ICS DNS (TCP-In) - B15BF139-F18D-471C-A18C-92DFD33350F1 - 0
Enabled : Yes
Direction : In
Profiles : Domain,Private,Public
Grouping :
LocalIP : Any
RemoteIP : Any
Protocol : TCP
LocalPort : 53
RemotePort : Any
Edgetraversal : No
Program : C:\WINDOWS\system32\svchost.exe
Service : sharedaccess
InterfaceTypes : Any
Security : NotRequired
Rulesource : Local Setting
Action : Allow
这是另一个:
RuleName : HNS Container Networking - DNS (UDP-In) - 91EC1DEF-8CB8-4C2A-A6D4-91480448AE97 - 0
Description : HNS Container Networking - DNS (UDP-In) - 91EC1DEF-8CB8-4C2A-A6D4-91480448AE97 - 0
Enabled : Yes
Direction : In
Profiles : Domain,Private,Public
Grouping :
LocalIP : Any
RemoteIP : Any
Protocol : UDP
LocalPort : 53
RemotePort : Any
Edgetraversal : No
InterfaceTypes : Any
Security : NotRequired
Rulesource : Local Setting
Action : Allow
如您所见,第一个规则使用端口和程序来定义规则,第二个仅使用端口。如何对对象进行分组并找到重复的项目(以便我可以删除相应的防火墙规则)?我不能简单地使用 $objects | Group-Object -Property rulename,enabled,.....
,因为所有对象的属性都不相同。
顺便说一句,重复的防火墙规则是由我过去编写的一些不完善的脚本创建的。
您已经了解(或记住)在 PowerShell 出现之前解析命令输出的困难方法但是您已经完成了大部分工作,我们只需要对您尝试获取所有通用属性的尝试进行一些修改returned 个对象:
$output = ( netsh advfirewall firewall show rule name=all verbose |
Out-String ).Trim() -split '\r?\n\s*\r?\n'
$propertyNames = [System.Collections.Generic.List[string]]::new()
$objects = @( $(foreach($section in $output ) {
$obj = @{}
foreach( $line in ($section -split '\r?\n') ) {
if( $line -match '^\-+$' ) {
continue
}
$name, $value = $line -split ':\s*', 2
$name = $name -replace " ", ""
$obj.$name = $value
if( $propertyNames -notcontains $name ) {
$propertyNames.Add( $name )
}
}
$obj
}) | ForEach-Object {
foreach( $prop in $propertyNames ) {
if( $_.Keys -notcontains $prop ) {
$_.$prop = $null
}
}
[PSCustomObject]$_
})
我将解释我对您的代码所做的调整:
- 将
$output
设置为一行(为了便于阅读,此处设置为多行)。并非绝对必要。 - 为了与代码的其余部分保持一致,请使用
-split
运算符而不是[regex]::split()
。 - 创建了一个名为
$propertyNames
的列表。这将用于跟踪从输出中读取的所有 属性 个名称。 - 子表达式
$()
您的第一个foreach
循环以防止需要中间变量,让我们将foreach
输出传递到管道。这在最后是必要的。它必须是一个子表达式,而不是仅仅被()
. 包围
- 将
$obj
设为hashtable
而不是PSCustomObject
。修改对象时更容易使用,最后可转换为PSCustomObject
。 Add-Member
不再需要,因为我们有hashtable
,所以$obj.$name = $value
足以设置 属性 名称和值。- 注:如果将这部分答案应用到其他领域,watch for pitfalls if the property name is anything other than a string。或者,如果这是一个问题,您可以使用传统的数组访问器语法:
$obj[$name] = $value
.
- 注:如果将这部分答案应用到其他领域,watch for pitfalls if the property name is anything other than a string。或者,如果这是一个问题,您可以使用传统的数组访问器语法:
- 如果
$name
不在$propertyList
中跟踪的已知 属性 名称中,请添加它。foreach
部分循环完成后,我们将拥有所有可能的属性 return 通过您的netsh
命令编辑。 - 现在我们可以 return
$obj
netsh
输出的每个部分。我们可能仍然需要更改它,所以暂时不要将其变成PSCustomObject
。 - 将您的子表达式
foreach
循环的输出通过管道传输到ForEach-Object
。同样,这避免了对中间变量的需要。我们需要遍历每个$obj
因为.... - 在
ForEach-Object
中,遍历$propertyList
并检查每个已知 属性 是否作为每个$obj
上的键(表示为$_
或ScriptBlock
内的$PSitem
。如果不存在,则添加值为$null
。 - 最后,将
$_
(这将是第一个循环中的每个$obj
return)转换为PSCustomItem
,因为我们 return 它。
这是您的代码与我的更改之间的差异:
现在,每个 returned 对象都应该具有相同的属性,即使它们没有显示在该规则的原始输出中。