如何按 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 对象都应该具有相同的属性,即使它们没有显示在该规则的原始输出中。