问题
I need to enumerate all firewall rules on windows 10 using PowerShell. I switched to PowerShell from netsh
because some built-in rules were getting funny names like @{microsoft.windows.shellexperiencehost_10.0.17134.1_neutral_neutral_cw5n1h2txyewy?ms-resource://microsoft.windows.shellexperiencehost/resources/pkgdisplayname}
which I was unable to manage with netsh
. Switching to PowerShell shows that the real name was a UID and fixed my problem but the code is really slow to run:
PS C:\Users\vagrant\Desktop> Measure-Command {.\ps-slow.ps1 show}
Seconds : 48
...
In contrast to Measure-Command {show-netfirewallrule}
:
...
Milliseconds : 644
And netsh
:
PS C:\Users\vagrant\Desktop> Measure-Command { netsh advfirewall firewall show rule all verbose}
...
TotalSeconds : 1.0588127
By commenting out the Get-NetFirewall*Filter
part of the script it runs at full speed but of course is missing all the data I want. The idea is to collect detailed info on all firewall rules and then output the lot as JSON.
Does anyone have an idea how to optimize this? I'm a PowerShell noob so I'm hoping I missed something obvious. The complete script is:
Show-NetFirewallRule | `
Where-Object { $_.cimclass.toString() -eq "root/standardcimv2:MSFT_NetFirewallRule" } | `
ForEach-Object { `
$af = $_ | Get-NetFirewallAddressFilter | Select-Object -First 1; # Assumes only one filter
$appf = $_ | Get-NetFirewallApplicationFilter | Select-Object -First 1; # Assumes only one filter
$pf = $_ | Get-NetFirewallPortFilter | Select-Object -First 1; # Assumes only one filter
$if = $_ | Get-NetFirewallInterfaceTypeFilter | Select-Object -First 1; # Assumes only one filter
New-Object -Type PSCustomObject -Property @{
Name = $_.Name
DisplayName = $_.DisplayName
Description = $_.Description
Enabled = $_.Enabled.toString()
Action = $_.Action.toString()
Direction = $_.Direction.toString()
EdgeTraversalPolicy = $_.EdgeTraversalPolicy.toString()
Profile = $_.Profile.toString()
DisplayGroup = $_.DisplayGroup
# Address Filter
LocalAddress = $af.LocalAddress
RemoteAddress = $af.RemoteAddress
LocalIp = $af.LocalIp
RemoteIp = $af.RemoteIp
# Port Filter
LocalPort = $pf.LocalPort
RemotePort = $pf.RemotePort
Protocol = $pf.Protocol
IcmpType = $pf.IcmpType
# Application Filter
Program = $appf.Program
# Interface Filter
InterfaceType = $if.InterfaceType.toString()
}
} | Convertto-json
回答1:
This approach is faster, so, maybe a different approach for you to get the same information.
param
(
[switch]$Local,
[switch]$GPO
)
# If no switches are set the script will default to local firewall rules
if (!($Local) -and !($Gpo))
{ $Local = $true }
$RegistryKeys = @()
if ($Local) {$RegistryKeys += 'Registry::HKLM\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules'}
if ($GPO) {$RegistryKeys += 'Registry::HKLM\Software\Policies\Microsoft\WindowsFirewall\FirewallRules'}
Foreach ($Key in $RegistryKeys)
{
if (Test-Path -Path $Key)
{
(Get-ItemProperty -Path $Key).PSObject.Members |
Where-Object {
(@('PSPath','PSParentPath','PSChildName') -notcontains $_.Name) -and
($_.MemberType -eq 'NoteProperty') -and
($_.TypeNameOfValue -eq 'System.String')} |
ForEach-Object {
# Prepare hashtable
$HashProps = @{
NameOfRule = $_.Name
RuleVersion = ($_.Value -split '\|')[0]
Action = $null
Active = $null
Dir = $null
Protocol = $null
LPort = $null
App = $null
Name = $null
Desc = $null
EmbedCtxt = $null
Profile = $null
RA4 = $null
RA6 = $null
Svc = $null
RPort = $null
ICMP6 = $null
Edge = $null
LA4 = $null
LA6 = $null
ICMP4 = $null
LPort2_10 = $null
RPort2_10 = $null
}
# Determine if this is a local or a group policy rule and display this in the hashtable
if ($Key -match 'HKLM\\System\\CurrentControlSet')
{ $HashProps.RuleType = 'Local' }
else
{ $HashProps.RuleType = 'GPO' }
# Iterate through the value of the registry key and fill PSObject with the relevant data
ForEach ($FireWallRule in ($_.Value -split '\|'))
{
switch (($FireWallRule -split '=')[0])
{
'Action' {$HashProps.Action = ($FireWallRule -split '=')[1]}
'Active' {$HashProps.Active = ($FireWallRule -split '=')[1]}
'Dir' {$HashProps.Dir = ($FireWallRule -split '=')[1]}
'Protocol' {$HashProps.Protocol = ($FireWallRule -split '=')[1]}
'LPort' {$HashProps.LPort = ($FireWallRule -split '=')[1]}
'App' {$HashProps.App = ($FireWallRule -split '=')[1]}
'Name' {$HashProps.Name = ($FireWallRule -split '=')[1]}
'Desc' {$HashProps.Desc = ($FireWallRule -split '=')[1]}
'EmbedCtxt' {$HashProps.EmbedCtxt = ($FireWallRule -split '=')[1]}
'Profile' {$HashProps.Profile = ($FireWallRule -split '=')[1]}
'RA4' {[array]$HashProps.RA4 += ($FireWallRule -split '=')[1]}
'RA6' {[array]$HashProps.RA6 += ($FireWallRule -split '=')[1]}
'Svc' {$HashProps.Svc = ($FireWallRule -split '=')[1]}
'RPort' {$HashProps.RPort = ($FireWallRule -split '=')[1]}
'ICMP6' {$HashProps.ICMP6 = ($FireWallRule -split '=')[1]}
'Edge' {$HashProps.Edge = ($FireWallRule -split '=')[1]}
'LA4' {[array]$HashProps.LA4 += ($FireWallRule -split '=')[1]}
'LA6' {[array]$HashProps.LA6 += ($FireWallRule -split '=')[1]}
'ICMP4' {$HashProps.ICMP4 = ($FireWallRule -split '=')[1]}
'LPort2_10' {$HashProps.LPort2_10 = ($FireWallRule -split '=')[1]}
'RPort2_10' {$HashProps.RPort2_10 = ($FireWallRule -split '=')[1]}
Default {}
}
}
# Create and output object using the properties defined in the hashtable
New-Object -TypeName 'PSCustomObject' -Property $HashProps
}
}
}
# Partial results
Action : Allow
LPort2_10 :
RuleType : Local
LPort : 135
Edge :
LA6 :
Dir : In
Desc : @icsvc.dll,-710
ICMP4 :
RA4 :
Name : @icsvc.dll,-709
LA4 :
App : %SystemRoot%\system32\svchost.exe
ICMP6 :
Protocol : 6
RuleVersion : v2.0
NameOfRule : vm-monitoring-dcom
RPort :
Svc : RpcSs
RA6 :
Profile :
EmbedCtxt : @icsvc.dll,-700
RPort2_10 :
Active : FALSE
回答2:
Does Get-NetFirewallRule
give you what you want?
$MyRules = Get-NetFirewallRule
foreach ($rule in $MyRules) {
[the rest of your code]
}
回答3:
Ok thanks heaps for everyone who posted. The original problem was that netsh
leaves unresolved names like:
@{Microsoft.Todos_1.41.12842.0_x64__8wekyb3d8bbwe?ms-resource://Microsoft.Todos/Resources/app_name_ms_todo}
@{Microsoft.Todos_1.41.12842.0_x64__8wekyb3d8bbwe?ms-resource://Microsoft.Todos/Resources/app_name_ms_todo}
In its output that can only be resolved by PowerShell, using the original script. The problem with this approach is that this is very slow (minutes).
Suggestions on this thread and from colleagues were:
- Use PS batching (made things slower - suggesting WMI bottleneck)
- Read the registry directly (mostly works, but data is left unresolved in different ways and would require slow registry scans to resolve - eg
@FirewallAPI.dll,-25427
to whatever resource that references) - Use the COM API
New-Object -ComObject HNetCfg.FwPolicy
(has the same output problems asnetsh
) - Adjust use of pipelines/object creation (no measurable impact)
In summary, the optimization I want is impossible without sacrificing the data that I want. I'm going to attempt to use the faster COM API/netsh most of the time but switch to using the powershell API when there is no choice (when unresolved names force us to)
回答4:
I don't think this is well understood, but the get-netfirewall*filter commands are meant to do things faster. Just compare the first pipeline to the second pipeline. It's like using the -filter option in other commands like get-childitem or get-wmiobject. I don't think even the writers of the online help understand this. It's like powershell is only 80% documented.
Get-NetFirewallRule |
Get-NetFirewallPortFilter |
Where LocalPort -eq 3389 | Get-NetFirewallRule |
Set-NetFirewallRule -RemoteAddress 192.168.1.1 -WhatIf
Get-NetFirewallPortFilter |
Where LocalPort -eq 3389 | Get-NetFirewallRule |
Set-NetFirewallRule -RemoteAddress 192.168.1.1 -WhatIf
来源:https://stackoverflow.com/questions/53146884/how-can-i-speed-up-powershell-to-get-firewall-rules-on-windows-10