2

I have to save some informations using command Get-NetFirewallRule, and save them in a .JSON file. This is how I have done

Get-NetFirewallRule  |
select-object -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 |
ConvertTo-Json | Out-File "C:\Users\Administrator\Desktop\firewall.txt"

and the output file is this (this is a little part of file)

        "Name":  "Microsoft-Windows-PeerDist-WSD-Out",
        "DisplayName":  "BranchCache Peer Discovery (WSD-Out)",
        "DisplayGroup":  "BranchCache - Peer Discovery (Uses WSD)",
        "Protocol":  "UDP",
        "LocalPort":  "Any",
        "RemotePort":  "3702",
        "RemoteAddress":  "LocalSubnet",
        "Enabled":  2,
        "Profile":  0,
        "Direction":  2,
        "Action":  2
    },
    {
        "Name":  "Microsoft-Windows-PeerDist-HostedServer-In",
        "DisplayName":  "BranchCache Hosted Cache Server (HTTP-In)",
        "DisplayGroup":  "BranchCache - Hosted Cache Server (Uses HTTPS)",
        "Protocol":  "TCP",
        "LocalPort":  {
                          "value":  [
                                        "80",
                                        "443"
                                    ],
                          "Count":  2
                      },
        "RemotePort":  "Any",
        "RemoteAddress":  "Any",
        "Enabled":  2,
        "Profile":  0,
        "Direction":  1,
        "Action":  2
    }

As you can see, powershell save the LocalPort in 2 different ways: the first with 1 value, and the second with 2 value and Count; in my code (C#) I wrote this to read my JSON file

string file = File.ReadAllText(MainWindow.path + @"\..\..\misc\json\FirewallRules.json");
List<GetSetFRules> rulesList = JsonConvert.DeserializeObject<List<GetSetFRules>>(file);

but there is a problem; the class GetSetFRules cannot save the content of JSON, because the format in JSON isn't the same for each rules

 class GetSetFRules
    {
        public string Name { get; set; }
        public string DisplayName { get; set; }
        public string DisplayGroup { get; set; }
        public string Protocol { get; set; }
        public Localport LocalPort { get; set; }
        public string RemotePort { get; set; }
        public string RemoteAddress { get; set; }
        public int Enabled { get; set; }
        public int Profile { get; set; }
        public int Direction { get; set; }
        public int Action { get; set; }
    }

    public class Localport
    {
        public string[] value { get; set; }
        public int Count { get; set; }
    }

So, the question is, Is there a way to save each rules with empty value like this?

[...]"LocalPort":  "Any",[...]

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇

[...]"LocalPort":  {
                          "value":  [
                                        "Any",
                                    ],
                          "Count":  1
                      }[...]

or this

[...]"LocalPort":  {
                          "value":  [
                                        "",
                                    ],
                          "Count":  0
                      }[...]
Teo230
  • 95
  • 2
  • 3
  • 13
  • 1
    Teo230, my suggestion is to use a JSON parser that supports a different parsing mechanism instead of (de)serialization. I would propably use one that supports [JSONPath](https://www.baeldung.com/guide-to-jayway-jsonpath), like [JSON.NET](https://www.newtonsoft.com/json). In such case you'll have the complete control on how to build the instances loaded with the data you need. – gsscoder Dec 23 '19 at 13:25
  • can you show an example? – Teo230 Dec 23 '19 at 13:35
  • yes, I think (and hope) that this could help you: https://www.newtonsoft.com/json/help/html/QueryJsonSelectTokenJsonPath.htm. – gsscoder Dec 23 '19 at 13:43

3 Answers3

1

You need to ensure that the output from ($PSItem | Get-NetFirewallPortFilter).LocalPort in your calculated LocalPort property is array-typed, which you can ensure as follows:

@{
  Name='LocalPort'
  Expression={ , [array] ($PSItem | Get-NetFirewallPortFilter).LocalPort }
}
  • Cast [array] ensures that the command output is treated as an array, even if only a single value such as Any is output.

  • The unary form of ,, the array-constructor ("comma") operator, wraps the resulting array in an aux. temporary array that prevents a single-element array from getting unwrapped when the script block's ({ ... }) output is assigned to the Expression hashtable entry.

As an aside: As Theo points out in his answer, along with an optimization, calling Get-NetFireWallPortFilter multiple times for the same input object, across multiple calculated properties, is inefficient and slows your command down.


The above will give you the desired JSON representation consistently, but there is a caveat:

The JSON representation created by ConvertTo-Json for arrays via an object, containing a value property with the array elements, and a count property with the element count, should be considered a bug, as arrays should simply be represented as JSON arrays:

That is, something like:

, (80, 443) | ConvertTo-Json

should yield:

# OK: This is how it works in PowerShell v6+
[
  80,
  443
]

and not:

# BUG in Windows PowerShell v5.1
{
    "value":  [
                  80,
                  443
              ],
    "Count":  2
}

This answer discusses the details, but note that running the following once in a given Windows PowerShell session can be used to fix the problem:

# Run this once per session, before calling ConvertTo-Json, to 
# fix the JSON array serialization problem:
Remove-TypeData System.Array -ErrorAction Ignore
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

I think I find the solution

Replace class GetSetFRules with class dynamic

List<dynamic> rulesList = JsonConvert.DeserializeObject<List<dynamic>>(file);
Teo230
  • 95
  • 2
  • 3
  • 13
  • With this method the programm won't give you error, but the output will be the same in json file – Teo230 Dec 23 '19 at 14:02
  • 1
    I'm not sure what you mean with _the output will be the same in json file_. Does that mean you still do not get the desired output? – Theo Dec 23 '19 at 14:32
  • Glad to hear my answer worked for you, @Teo230. Given that this answer isn't really effective, please consider amending or removing it, along with your other answer that is really more of a clarification than an answer. – mklement0 Jan 07 '20 at 08:32
0

On my Win10 machine, using your Powershell code also shows array output for RemotePort and Protocol. I believe it is better not to try and adapt all items to have that kind of output, but list all rule objects as-is, so they all have a single value for these properties.

Also, you could speed this up a little by not performing Get-NetFirewallPortFilter multiple times:

# Retrieve every firewall rule object as single object.
# All of these objects will have properties 'LocalPort', RemotePort, 'Protocol'
# listed as single string values; not as array values in the output
Get-NetFirewallRule | ForEach-Object {
    $portFilter = $_ | Get-NetFirewallPortFilter
    $_ | Select-Object -Property Name,
    DisplayName,
    DisplayGroup,
    @{Name='Protocol';Expression={$portFilter.Protocol}},
    @{Name='LocalPort';Expression={$portFilter.LocalPort}},
    @{Name='RemotePort';Expression={$portFilter.RemotePort}},
    @{Name='RemoteAddress';Expression={($_ | Get-NetFirewallAddressFilter).RemoteAddress}},
    Enabled,
    Profile,
    Direction,
    Action 
} | Sort-Object DisplayName | ConvertTo-Json | Out-File "C:\Users\Administrator\Desktop\firewall.txt"

Partial output:

[
    {
        "Name":  "{56BC7333-582E-45E8-9249-F88002B598E9}",
        "DisplayName":  "Microsoft Management Console",
        "DisplayGroup":  null,
        "Protocol":  "TCP",
        "LocalPort":  "Any",
        "RemotePort":  "Any",
        "RemoteAddress":  "Any",
        "Enabled":  1,
        "Profile":  4,
        "Direction":  1,
        "Action":  4
    },
    {
        "Name":  "{C3F4E91A-65F5-4805-8F6B-86C6ECA5F4EE}",
        "DisplayName":  "Microsoft Management Console",
        "DisplayGroup":  null,
        "Protocol":  "UDP",
        "LocalPort":  "Any",
        "RemotePort":  "Any",
        "RemoteAddress":  "Any",
        "Enabled":  1,
        "Profile":  4,
        "Direction":  1,
        "Action":  4
    },
    {
        "Name":  "UDP Query User{A8997866-770C-417E-AE45-2436717836E8}C:\\windows\\system32\\mmc.exe",
        "DisplayName":  "Microsoft Management Console",
        "DisplayGroup":  null,
        "Protocol":  "UDP",
        "LocalPort":  "Any",
        "RemotePort":  "Any",
        "RemoteAddress":  "Any",
        "Enabled":  1,
        "Profile":  2,
        "Direction":  1,
        "Action":  2
    },
    {
        "Name":  "TCP Query User{C99F0EF2-8891-4B2D-B389-4A3F8B66638A}C:\\windows\\system32\\mmc.exe",
        "DisplayName":  "Microsoft Management Console",
        "DisplayGroup":  null,
        "Protocol":  "TCP",
        "LocalPort":  "Any",
        "RemotePort":  "Any",
        "RemoteAddress":  "Any",
        "Enabled":  1,
        "Profile":  2,
        "Direction":  1,
        "Action":  2
    }
]
Theo
  • 57,719
  • 8
  • 24
  • 41
  • @Teo230 Yes, every firewall rule is its own object in the result array because of the `ForEach-Object` – Theo Dec 23 '19 at 15:12