Speed up powershell command when using calculated properties - powershell

So I wanted to get a powershell command to list the firewall rules and ports, and I had to basically use 2 commands: Get-NetFirewallRule and Get-NetFirewallPortFilter.
So I basically came up with this:
Get-NetFirewallPortFilter | where-object {$_.LocalPort -cmatch '[0-9]+'}|select-object -Property #{n='Name';e={($_ | Get-NetFirewallRule).Name}}, #{n='Description';e={($_ | Get-NetFirewallRule).description}}, Protocol, LocalPort, RemotePort, #{n='Direction';e={($_|Get-NetFirewallRule).Direction}}
I went with Get-NetFirewallPortFilter first because I wanted to only get numerical ports (so I didn't want to get 'Any' as a result).
Problem is, the more calculated properties I have, the slower this runs. It generates like 3 lines a second, which IMO is very slow.
I timed the command to see how long it takes (did an Out-GridView to also see the output, I don't think it adds THAT much more to the time, it seems to generate just as many lines a second):
PS C:\> measure-command { Get-NetFirewallPortFilter | where-object {$_.LocalPort -cmatch '[0-9]+'}|select-object -Property #{n='Name';e={($_ | Get-NetFirewallRule).Name}}, #{n='Description';e={($_ | Get-NetFirewallRule).description}}, Protocol, LocalPort, RemotePort, #{n='Direction';e={($_|Get-NetFirewallRule).Direction}} |out-gridview }
Days : 0
Hours : 0
Minutes : 1
Seconds : 59
Milliseconds : 143
Ticks : 1191434968
TotalDays : 0.00137897565740741
TotalHours : 0.0330954157777778
TotalMinutes : 1.98572494666667
TotalSeconds : 119.1434968
TotalMilliseconds : 119143.4968
It output 358 lines in ~119 seconds.
Are there any optimizations I can run to make this run faster?

You are using Get-NetFirewallRule multiple times in your code, where you could do this only once per PortFilter:
Get-NetFirewallPortFilter | Where-Object {$_.LocalPort -match '[0-9]+'} | ForEach-Object {
$rule = $_ | Get-NetFirewallRule
$_ | Select-Object #{Name = 'Name'; Expression = {$rule.Name}},
#{Name = 'Description'; Expression = {$rule.Description}},
Protocol, LocalPort, RemotePort,
#{Name = 'Direction'; Expression = {$rule.Direction}}
} | Out-GridView
It might be just a tad faster if you take out the ForEach-Object and use a foreach() instead:
$portFilters = Get-NetFirewallPortFilter | Where-Object {$_.LocalPort -match '[0-9]+'}
$result = foreach ($filter in $portFilters) {
$rule = $filter | Get-NetFirewallRule
$filter | Select-Object #{Name = 'Name'; Expression = {$rule.Name}},
#{Name = 'Description'; Expression = {$rule.Description}},
Protocol, LocalPort, RemotePort,
#{Name = 'Direction'; Expression = {$rule.Direction}}
}
$result | Out-GridView

Filtering left is always fastest, with get-netfirewallportfilter first. This takes about 18 seconds. I had trouble using -pipelinevariable (or -pv) with get-netfirewallportfilter. Powershell commands manipulate objects vs linux commands that manipulate text.
EDIT: Piping to get-netfirewallrule behaves oddly, like all the objects would be processed in one shot. I put it inside a foreach-object. I changed name to displayname and put description last.
# not work
Get-NetFirewallPortFilter -pipelinevariable port
Get-NetFirewallPortFilter : Cannot retrieve the dynamic parameters for the cmdlet. Object reference not set to an
instance of an object.
At line:1 char:1
+ Get-NetFirewallPortFilter -pipelinevariable port
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-NetFirewallPortFilter], ParameterBindingException
+ FullyQualifiedErrorId : GetDynamicParametersException,Get-NetFirewallPortFilter
Get-NetFirewallPortFilter |
where localPort -match \d -pipelinevariable port |
% { $_ | Get-NetFirewallRule } | select displayname,
#{n='Protocol'; e={$port.protocol}},
#{n='Localport'; e={$port.localport}},
#{n='Remoteport';e={$port.remoteport}},
direction, description | ft
DisplayName Protocol Localport Remoteport Direction description
----------- -------- --------- ---------- --------- -----------
SNMP Trap Service (UDP In) UDP 162 Any Inbound Inbound rule for the SNMP Trap Service to allow SNMP traps. [UDP 162]
SNMP Trap Service (UDP In) UDP 162 Any Inbound Inbound rule for the SNMP Trap Service to allow SNMP traps. [UDP 162]
Delivery Optimization (TCP-In) TCP 7680 Any Inbound Inbound rule to allow Delivery Optimization to connect to remote endpoints

Related

PowerShell find firewall rule by port

What I want to do
I want to list all firewall rules involving some ports and list their display name but the only way I found for now displays only the port part and has no idea what the display name is.
First the basics
If we call Show-NetFirewallRule without argument, it lists all rules and each is formatted like that (notice DisplayName that is on "root" and LocalPort that is under Get-NetFirewallPortFilter):
Name : {96022E5F-666B-4E9E-8BD4-040498CEF1F5}
DisplayName : Google Chrome (mDNS-In)
Description : Inbound rule for Google Chrome to allow mDNS traffic.
DisplayGroup : Google Chrome
Group : Google Chrome
Enabled : True
Profile : Any
Platform :
Direction : Inbound
Action : Allow
EdgeTraversalPolicy : Block
LooseSourceMapping : False
LocalOnlyMapping : False
Owner :
PrimaryStatus : OK
Status : The rule was parsed successfully from the store. (65536)
EnforcementStatus : NotApplicable
PolicyStoreSource : PersistentStore
PolicyStoreSourceType : Local
RemoteDynamicKeywordAddresses :
$_ | Get-NetFirewallAddressFilter
LocalAddress : Any
RemoteAddress : Any
$_ | Get-NetFirewallServiceFilter
Service : Any
$_ | Get-NetFirewallApplicationFilter
Program : C:\Program Files\Google\Chrome\Application\chrome.exe
Package :
$_ | Get-NetFirewallInterfaceFilter
InterfaceAlias : Any
$_ | Get-NetFirewallInterfaceTypeFilter
InterfaceType : Any
$_ | Get-NetFirewallPortFilter
Protocol : UDP
LocalPort : 5353
RemotePort : Any
IcmpType : Any
DynamicTarget : Any
$_ | Get-NetFirewallSecurityFilter
Authentication : NotRequired
Encryption : NotRequired
OverrideBlockRules : False
LocalUser : Any
RemoteUser : Any
RemoteMachine : Any
What I tried
The closer, I think, is
Show-NetFirewallRule | where {$_.LocalPort -eq "5353" -or $_.LocalPort -eq "5354"}
But it returns only Get-NetFirewallPortFilter part as said above:
$_ | Get-NetFirewallPortFilter
Protocol : UDP
LocalPort : 5353
RemotePort : Any
IcmpType : Any
DynamicTarget : Any
$_ | Get-NetFirewallPortFilter
Protocol : UDP
LocalPort : 5353
RemotePort : Any
IcmpType : Any
DynamicTarget : Any
$_ | Get-NetFirewallPortFilter
Protocol : UDP
LocalPort : 5353
RemotePort : Any
IcmpType : Any
DynamicTarget : Any
On same basis I tried:
Get-NetFirewallRule | where { $_.Get-NetFirewallPortFilter.LocalPort -Eq "5353" }
that displays a parse error, and all
Get-NetFirewallRule | where { $_.NetFirewallPortFilter.LocalPort -Eq "5353" }
Get-NetFirewallRule | where { ($_ | Get-NetFirewallAddressFilter).LocalPort -Eq "5353" }
Get-NetFirewallRule | where { ($_ | Get-NetFirewallAddressFilter).$_.LocalPort -Eq "5353" }
That gives no result.
Based on https://itluke.online/2018/11/27/how-to-display-firewall-rule-ports-with-powershell/, I tried:
Get-NetFirewallRule |
Format-Table -Property Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}} | where {$PSItem.LocalPort -eq "5353"}
But it seems doing nothing, and when I call it without the | where ... it is very slow, displaying approximatively 1 line per second. Note I tried also $_.LocalPort -eq "5353" and $_ -like "5353" in where.
I also tried
Get-NetFirewallRule | Get-NetFirewallPortFilter | Where-Object -Property { $_.LocalPort -Eq "5353" }
But is returns nothing (and is also very slow).
Workaround
For now I use a dirty "workaround", I call Show-NetFirewallRule > NetFirewallRule.txt and search manually in file, but I would like to have a script that does this automatically for me (and that is not very slow, since some commands that seems close to the answer are very slow).
The question
Anybody knows if/how can I achieve that ?
Thanks !
I believe you want to start with Get-NetFirewallPortFIlter, filter the results, and pass them to Get-NetFirewallRule. That should be much faster than looping on all results of Get-NetFirewallRule and testing each yourself.
Example (indented for readability, but can be a one-liner, of course):
Get-NetFirewallPortFilter |
Where-Object { $_.LocalPort -eq 5353 } |
Get-NetFirewallRule
Searched 717 rules and an equivalent 717 port filters in 1.2 seconds with 6 results.
If you'd like to show the port information alongside each rule, something like (this may or might not be optimal, but ...):
Get-NetFirewallPortFilter |
Where-Object { $_.LocalPort -eq 5353 } |
ForEach-Object {
"----"
"Rule"
"----"
$_ | Get-NetFirewallRule
"-----------"
"Port Filter"
"-----------"
$_
}
With the above, you'll still be looping over the filtered results rather than the entire set of rules.
This is what works for me:
Get-NetFirewallRule |Select-Object -First 20 -PipelineVariable Rule |
Get-NetFirewallPortFilter |Where-Object LocalPort -in 'RPCEPMap', 'Any' |
ForEach-Object { [pscustomobject]#{ name = $Rule.DisplayName; port = $_.LocalPort } }
But there are two tricky things here to deal with:
The common parameter -PipelineVariable is limited (see the information in the description along with the Note and the Caution) which I assume covers the reason why you can't remove the |Select-Object -First 20 part and place the -PipelineVariable Rule directly on the Get-NetFirewallRule cmdlet (but I don't fully understand the implication myself)
The Get-NetFirewallPortFilter cmdlet requires a CimInstance rather than e.g. a usual (PS)Object. This presumable explains why you can't replace the |Select-Object -First 20 with |Select-Object * know that former command actually places a reference to object output by Get-NetFirewallRule. See: Select-Object -First affects prior cmdlet in the pipeline
Update
as there appears to be a bug: "after some tests, it started to return always same name, of a "random" rule not involving port(s) I am searching for"
This is probably because the CimInstances run asynchronously and the refences in the -PipelineVariable are probably being overwritten (even with NetFirewallRule -ThrottleLimit 1 -OutBuffer 1). This means that this probably can't piped correctly.
Anyways, this (doing a filter on each specific rule instance) also appears to work for me:
Get-NetFirewallRule |ForEach-Object {
$Ports = $_ |Get-NetFirewallPortFilter |Where-Object LocalPort -in 'RPCEPMap', 'Any'
if ($Ports) { $_ |Select-Object DisplayName, #{n='LocalPort';e={$Ports.LocalPort} } }
}
Trying to parse something like "1000-2000" in localport. This fails with multiple false outcomes in the same rule.
$target = 8555
get-netfirewallportfilter |
? { $_.localport -match '-' } |
? { $_.localport | foreach { if ($_ -match '-') { $beg,$end = $_ -split '-';
[int]$beg -le $target -and [int]$end -ge $target }}} |
select instanceid,#{n='localport';e={"$($_.localport)"}}
instanceid localport
---------- ---------
NVS-FrameServer-In-TCP-NoScope 554 8554-8558
{E7D045E7-643F-4A91-94FF-63518836FA72} 5353 7200-17210 8889
{9823D038-1960-4767-9290-B36AA995FBB3} 5000 7000 7100 50000 7200-17210 8888
{123BF547-E50B-4A9D-A9E7-0FAE15D9C665} 7200-17210
IIS-WebServerRole-FTP-Passive-In-TCP 1024-65535
{25931D39-0705-4E63-A10C-E5F16BD17E0A} 7200-17210
{D6E50A26-B3A7-4683-A9FC-8485ED2B38BA} 5000 7000 7100 50000 7200-17210 8888
{B88115EB-F6B6-48D2-B194-723414C249B5} 5353 7200-17210 8889

Select-Object Cmdlet Returns Different Formatted Results for Get-NetIPConfiguration (different type)

I was trying to get the IP config details for my physical network adapter, while excluding all virtual network adapter. My script is as below:
$NetAdaptIndex = Get-NetAdapter -Physical | Select -Property InterfaceIndex -ExpandProperty InterfaceIndex
## Get Ip address, Gateway and DNS details as per configuration
$IPAddressConfig = foreach ($II in $NetAdaptIndex) {
Write-Host $II -ForegroundColor Yellow
Get-NetIPConfiguration |
Where-Object { $_.InterfaceIndex -eq $II} |
Select-Object InterfaceAlias, InterfaceDescription, InterfaceIndex, Ipv4Address, IPv4DefaultGateway, DNSServer
}
When I call the variable, I get the following results where the IPv4Address, IPv4DefaultGateway, and DNSServer are not returned in a string format.
PS C:\WINDOWS\system32> $IPAddressConfig
InterfaceAlias : WiFi 2
InterfaceDescription : D-Link DWA-171 Wireless AC Dual Band Adapter
InterfaceIndex : 46
IPv4Address : {192.168.0.103}
IPv4DefaultGateway : {MSFT_NetRoute (InstanceID = ":8:8:8:9:55?#55;C?8;#B8:8;55;")}
DNSServer : {MSFT_DNSClientServerAddress (Name = "46", CreationClassName = "", SystemCreationClassName = "", SystemName = "23"), MSFT_DNSClientServerAddress (Name = "46", CreationClassName = "", SystemCreationClassName = "", SystemName = "2")}
InterfaceAlias : Ethernet
InterfaceDescription : Broadcom NetLink (TM) Gigabit Ethernet
InterfaceIndex : 10
IPv4Address : {169.254.149.208}
IPv4DefaultGateway :
DNSServer : {MSFT_DNSClientServerAddress (Name = "10", CreationClassName = "", SystemCreationClassName = "", SystemName = "23"), MSFT_DNSClientServerAddress (Name = "10", CreationClassName = "", SystemCreationClassName = "", SystemName = "2")}
But if I were to manually remove the Select-Object code, it returns a correctly formatted results.
Get-NetIPConfiguration |
Where-Object { $_.InterfaceIndex -eq 46}
Observe how the IPv4Address, IPv4DefaultGateway, and DNSServer has returned the correctly formatted result.
InterfaceAlias : WiFi 2
InterfaceIndex : 46
InterfaceDescription : D-Link DWA-171 Wireless AC Dual Band Adapter
NetProfile.Name : <removed>
IPv6Address : <removed>
IPv4Address : 192.168.0.103
IPv6DefaultGateway : <removed>
IPv4DefaultGateway : 192.168.0.1
DNSServer : 2001:f40:0:3::12:68
2001:4860:4860::8888
1.1.1.1
8.8.8.8
My question is why is this happening? And is there any method that I can use to convert them to the correct format? The latter format are the one that I want.
Everything in the PowerShell is an object. The objects can get quite complex with multiple nested properties.
PS > (Get-NetIPConfiguration | Select-Object DNSServer)[0]
DNSServer
---------
{MSFT_DNSClientServerAddress (Name = "11", CreationClassName = "", SystemCreationClassName = "", SystemName = "23"), MSFT_DNSClientServerAddress (Name = "11", CreationClassName = "", Syste...
PS > (Get-NetIPConfiguration | Select-Object -ExpandProperty DNSServer)[0]
InterfaceAlias Interface Address ServerAddresses
Index Family
-------------- --------- ------- ---------------
LAN 11 IPv6 {}
To make your life easier, PowerShell formats objects when they're displayed on screen. Formatting rules are stored in the xml files: Formatting File Overview. For the NetTCPIP module, the file would be C:\Windows\System32\WindowsPowerShell\v1.0\Modules\NetTCPIP\Tcpip.Format.ps1xml. Here is the DNSServer item formatting code:
<ListItem>
<Label>DNSServer</Label>
<ItemSelectionCondition>
<ScriptBlock>
($_.DNSServer.Count -ne 0) -and
(($_.NetIPv4Interface.ConnectionState -eq "Connected") -or
($_.NetIPv6Interface.ConnectionState -eq "Connected"))
</ScriptBlock>
</ItemSelectionCondition>
<ScriptBlock>
$output = "";
foreach($Server in $_.DNSServer) {
foreach($Address in $Server.ServerAddresses) {
$output += $Address + "`n";
}
};
$output.Trim("`n");
</ScriptBlock>
</ListItem>
What formatting gets applied is controlled by the PSTypeNames property which exists on every object:
PS > (Get-NetIPConfiguration)[0].PSTypeNames
NetIPConfiguration
System.Object
When you use Select-Object, it modifies that property, so the resulting object loses its formatting:
PS > (Get-NetIPConfiguration | Select-Object DNSServer)[0].PSTypeNames
Selected.NetIPConfiguration
System.Management.Automation.PSCustomObject
System.Object
Alas, simply setting it back wouldn't work (object doesn't have correct structure for this), but you can emulate it by using calculated property:
Get-NetIPConfiguration | Select-Object InterfaceAlias, InterfaceDescription, InterfaceIndex, Ipv4Address, IPv4DefaultGateway, #{ n='DNSServer' ; e={$_.DNSServer.ServerAddresses -join "`n"}}
This will squash DNSServer to string, so you wouldn't be able to reuse it properly later, though. Perhaps, just outputting ServerAddresses collection would be more convenient and it would look the same:
Get-NetIPConfiguration | Select-Object InterfaceAlias, InterfaceDescription, InterfaceIndex, Ipv4Address, IPv4DefaultGateway, #{ n='DNSServer' ; e={#($_.DNSServer.ServerAddresses)}}
I've omitted to include that I'm piping the whole output to a ConvertTo-HTML cmdlet as not to make the question goes out of scope. But since I'm converting the output to HTML, it means that there is more than 1 way to skin the cat. I do not need to force the DNSServer object to be changed back to its previous formatting.
So, instead of joining the DNSServer with a ', ', I've joined it with a <br> tag instead.
Select-Object InterfaceAlias, InterfaceDescription, InterfaceIndex, #{n='IPv4 Address';e={$_.IPv4Address.IPAddress}}, #{n='IPv4 Default Gateway';e={$_.IPv4DefaultGateway.NextHop}}, #{n='DNS Server';e={$_.DNSServer.ServerAddresses -split " " -join "<br>"}}
Since ConvertTo-HTML translated the <> symbol to < and > respectively, I've added the following code to translate < and > back to <>.
$IPAddressConfigTemp = $IPAddressConfig | ConvertTo-Html -fragment -As List -PreContent "<h2>IP Address Configuration Details</h2>"
$IPAddressConfigHTML = $IPAddressConfigTemp -replace "<", "<" -replace ">", ">" -replace "'", "'"
The partial result of the code looks somewhat like this:
<table>
<tr><td>DNS Server:</td><td>2001:f40:0:3::12:68<br>2001:4860:4860::8888<br>1.1.1.1<br>8.8.8.8</td></tr>
</table>
It's not the most elegant solution, but it at least fits into what I would like to achieve. So, since I'm using calculated property to reach the final solution, I've selected #beatcracker's reply as the correct answer. It helps that his answer answered the whys and helped me to learn further.
Using Select-Object with the -Property parameter results in a new object with a new type (Selected.NetIPConfiguration, pipe to Get-Member to see this).
Formatting in PowerShell is based on types, so the formatting is seeing the new type and not applying the formatting from the old type.
You could conceivably apply a new typename (using the PSTypeNames property), and copy the formatting instructions from the old typename (NetIPConfiguration) to the new one.
Note that the difference in formatting doesn't imply any difference in the property values, it's purely cosmetic.

PowerShell property expression increases execution time by 4-5 times

Scroll down for TL;DR
I need to get the following properties for every process as quickly as possible, ideally 5 seconds, maximum 10 seconds: ID, Name, Description, Path, Company, Username, Session ID, StartTime, Memory, CPU (percentage, not time)
To get this data, I put together the following snippet which (I think) is functionally perfect:
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
The issue is that its taking 18-22 seconds to execute, caused by this line (which adds about 16 seconds):
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
}
TotalSeconds : 19.061206
When I remove the slow property expression noted above and keep the WMI query, execution takes about 4.5 seconds:
Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}
TotalSeconds : 4.5202906
I thought that by getting all of the required data in a single query and referring back to the $ProcessCPU array would be fast - but I appreciate I'm iterating through each of the 250 arrays stored in $Processes.
TL;DR:
Is there a more performant method of joining two objects on a common property rather than using iteration as I have above? I.E. $ProcessCPU.IDProcess on $Processes.Id?
I tried the following block to test $Output = $ProcessCPU + $Processes | Group-Object -Property Id, it executed in just 3 seconds, but the output wasn't acceptable:
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object #{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
$Output = $ProcessCPU + $Processes | Group-Object -Property Id
}
TotalSeconds : 2.9656969
Use CIM to build up a hashtable that maps process IDs (PIDs) to their CPU percentages first.
Then make the calculated property passed to Select-Object consult that hashtable for efficient lookups:
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
ForEach-Object -Begin { $htCpuPctg=#{} } `
-Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime } #`
Get-Process -IncludeUserName |
Select-Object Id,
Name,
Description,
Path,
Company,
UserName,
SessionId,
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}
Note:
Get-CimInstance rather than Get-WimObject is used, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
There is usually no need to use calculated properties such as #{Name='Id';Expression={[int]$_.Id}} to simply extract a property as-is - just use the property's name - Id - as a Select-Object -Property argument (but you've since clarified that you're using calculated properties because you want explicit control over the property's data type for sending data to an IoT Gateway via JSON).
Note that CIM reports PIDs as [uint32]-typed values, whereas Get-Process uses [int] values - hence the need to cast to [uint32] in the hashtable lookup.

Why doesn't Get-NetFirewallRule show all information of the firewall rule?

I'm trying to find if a firewall rule already existing, with the same name, same configuration, like: localport.
So I use Get-NetFirewallRule to list all rules, but the rules returned do not contain the information of port, also some other information are missing. where can I find all the config of a rule. Below is the attributess returned:
Name
DisplayName
Description
DisplayGroup
Group
Enabled
Profile
Platform
Direction
Action
EdgeTraversalPolicy
LooseSourceMapping
LocalOnlyMapping
Owner
PrimaryStatus
Status
EnforcementStatus
PolicyStoreSource
PolicyStoreSourceType
What I don't think is understood by many, including me recently, is that the Get-NetFirewall*Filter commands provide a speedy shortcut to searching the firewall rules, like the -filter option does in other commands. If I were to do this, it would take a very long time:
Get-NetFirewallRule | Get-NetFirewallPortFilter |
Where LocalPort -eq 3389
While this is almost instant:
Get-NetFirewallPortFilter | Where LocalPort -eq 3389
And Get-NetFirewallPortFilter actually returns the name of the firewall rule in the InstanceID property, which isn't shown by default. That's why you can pipe Get-NetFirewallPortFilter back into Get-NetFirewallRule.
Get-NetFirewallPortFilter | Where LocalPort -eq 3389 |
Get-NetFirewallRule
Here's a function that gives netsh-like verbose output, with the ports, addresses, and applications:
function mynetsh {
param($displayname)
$rule = get-netfirewallrule -displayname $displayname
$address = $rule | Get-NetFirewallAddressFilter
$port = $rule | Get-NetFirewallPortFilter
$application = $rule | Get-NetFirewallApplicationFilter
[pscustomobject]#{
DisplayName = $rule.DisplayName
Description = $rule.Description
Enabled = $rule.Enabled
Direction = $rule.Direction
Profile = $rule.Profile
DisplayGroup = $rule.DisplayGroup
LocalAddress = $address.LocalAddress
RemoteAddress = $address.RemoteAddress
Protocol = $port.Protocol
LocalPort = $port.LocalPort
RemotePort = $port.RemotePort
EdgeTraversalPolicy = $rule.EdgeTraversalPolicy
Program = $application.Program
Action = $rule.Action
}
}
mynetsh 'Remote Desktop - User Mode (TCP-In)'
DisplayName : Remote Desktop - User Mode (TCP-In)
Description : Inbound rule for the Remote Desktop service to allow RDP traffic. [TCP 3389]
Enabled : False
Direction : Inbound
Profile : Any
DisplayGroup : Remote Desktop
LocalAddress : Any
RemoteAddress : Any
Protocol : TCP
LocalPort : 3389
RemotePort : Any
EdgeTraversalPolicy : Block
Program : %SystemRoot%\system32\svchost.exe
Action : Allow
In order to find the port numbers that are already in the firewall rules, you can use a different cmdlet Get-NetFirewallPortFilter.
(Info)
Use Get-NetFirewallRule to filter which subset of rules you want to look at and pipe it to the above cmdlet. eg.:
Get-NetFirewallRule -DisplayName "SQL Broker Service" | Get-NetFirewallPortFilter
Sounds like the property you are after is localport.
Use the below command to list all.
Get-NetFirewallRule| Where { $_.Enabled -eq $True } |
Format-Table -Property Name,
DisplayName,
DisplayGroup,
#{Name='Protocol';Expression={($PSItem | Get-NetFirewallPortFilter).Protocol}},
#{Name='LocalPort';Expression={($PSItem | Get-NetFirewallPortFilter).LocalPort}},
#{Name='RemotePort';Expression={($PSItem | Get-NetFirewallPortFilter).RemotePort}},
#{Name='RemoteAddress';Expression={($PSItem | Get-NetFirewallAddressFilter).RemoteAddress}},
Enabled,
Profile,
Direction,
Action
The output is shown below
Use the Select-Object Cmdlet to Display all Properties
This will Display only the First one so you dont get flooded with text, feel free to delete the "-First 1" as needed
Get-NetFirewallRule | select -First 1 -Property *
However investigating that it does not seem like there is Information about the port, looking further into it - you would probably need to use Get-NetFirewallPortFilter and match them up by instanceid. If you need help with that I'll need a little more Information on what you are trying to accomplish.
If you use only the firewall cmdlets to get a list of object that include ports, programs etc., it's not an easy task, and it's very slow! Why not try the old school way, the netsh advfirewall firewall command suite. Below is my trying to get a list of objects that include all rule information.
$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( $prop in $propertyNames ) {
if( $_.Keys -notcontains $prop ) {
$_.$prop = $null
}
}
[PSCustomObject]$_
})

Powershell - Bytes sent/received

I need to create a script that samples the network traffic every 30 seconds and stores the bytes sent/received. This data is then later used to draw graphs. I wrote one that works perfectly on Windows 2012 but i realised some of the cmdlets were not available in previous versions like 2008 so i am seeking alternatives.
For windows 2012 i used get-netadapterstatistics to get the received/sent bytes but this won't work on pre 2012 so i thought i could use netstat -e but the problem is both are giving me completely different results and i was hoping someone can tell me why? The script below was written to see the different between data.
function getNic{
$nic = Get-NetRoute | ? DestinationPrefix -eq '0.0.0.0/0' | Get-NetIPInterface | Where ConnectionState -eq "Connected" | Select -ExpandProperty InterfaceAlias
return $nic
}
function getBR{
$b = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
$a = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$a - $script:startbr
$b - $script:startbr2
$script:startbr = $a
$script:Startbr2 = $b
}
$nic_name = getNic
$startbr = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$startbr2 = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
for(1..1000){
getBR
Start-Sleep 5
}
The results are as below
0
0
4577
18308
6695
26780
9055
36220
Ideally i am only interested in capturing traffic on the external interface.
While i can´t offer you an explanation for the difference between your methods i could offer you an alternative that should work on pre 2012 as well as on 2012 upwards:
$ifIndex = Get-WmiObject -Class win32_ip4routetable | where {$_.destination -eq "0.0.0.0"} | select -ExpandProperty InterfaceIndex
$ifIndex = "InterfaceIndex=" + $ifIndex
$nic_name = Get-WmiObject -Class win32_networkadapterconfiguration -Filter $ifIndex | select -ExpandProperty Description
$nic = [System.Net.NetworkInformation.Networkinterface]::GetAllNetworkInterfaces() | where {($_.description -eq $nic_name) -and ($_.operationalstatus -eq "up")}
$stats = $nic.GetIPv4Statistics()
$bytesSent = $stats.BytesSent
$bytesReceived = $stats.BytesReceived
This gives results consistent with the Get-NetAdapterStatistics Cmdlet on my system
After thinking about it maybe netstat shows statistics for multiple network adapters (maybe including loopback) combined since there is no differentiation by nic? Just guessing but this might explain the increased bytecount. Sadly there´s no details to be found in the docs