Home > Mobile >  matching data across two arrays and combining with additional data in array
matching data across two arrays and combining with additional data in array

Time:05-05

The Goal

See if $SP.ip is in $NLIP.IpRanges and if it is, add $NLIP.IpRanges and $NLIP.DisplayName to the $SP array or all into a new array.

The Arrays

Array 1 is $SP, it's a CSV import and has the properties 'name' and 'ip', it looks like this:

name: bob
ip: 1.9.8.2

Array 2 is $NLIP and has the relevant properties 'IpRanges' and 'DisplayName'. It's fetched from: $NLIP = Get-AzureADMSNamedLocationPolicy | where-object {$_.OdataType -eq "#microsoft.graph.ipNamedLocation"}, it looks like this:

DisplayName : Named Location 1
IpRanges : {class IpRange {
CidrAddress: 16.29.28.9/28 #fictitious CIDR
}
, class IpRange {
CidrAddress: 1.9.8.3/28 #fictitious CIDR
    }
}

The Code / the problem

I'm using IPInRange.ps1 function from https://github.com/omniomi/PSMailTools to find if the IP is in the range. It works like so:

> IPInRange 1.9.8.2 1.9.8.3/28
True

I also worked out that $NLTP.IpRanges.split() | Where-Object ($_ -like "*/*"} can return all the ranges, but $NLIP | Where-Object {$_.IpRanges.split() -like "*/*"} doesn't. I would naturally use the second to keep the variable in the pipe to return the DisplayName. So I'm struggling on how to pull the individual ranges out in such a way that I can then add the 'IpRange' and 'DisplayName' to an array. Also, maybe it's because I haven't worked out the above issue, but I'm struggling to think how I would iterate through both arrays and combine them into one. I know I would probably enter into a foreach ($item in $SP) and create a temporary array, but after that it's getting hazy.

The result

What I'm hoping to have in the end is:

name: bob
ip: 1.9.8.2
IpRange: 1.9.8.3/28 #fictitious CIDR
DisplayName: Named Location 1

thanks in advance.

CodePudding user response:

Let's to shed some lights in the hazy darkness by first creating a Minimal, Reproducible Example (mcve):

$SP = ConvertFrom-Csv @'
IP,          Name
1.9.8.2,     BOB
10.10.10.10, Apple
16.29.28.27, Pear
16.30.29.28, Banana
'@

$NLIP = ConvertFrom-Csv @'
IPRange,       SubNet
16.29.28.9/28, NetA
1.9.8.3/28,    NetB
'@

To tackle this, you need two loops where the second loop is inside the first loop. For the outer loop you might use the ForEach-Object cmdlet which lets you stream each object and with that actually use less memory (assuming that you import the data from a file and eventually export it to a new file). Within the inner loop you might than cross link each IP address with the IPRange using the function you refer to and in case the condition is true create a new PSCustomObject:

$SP |ForEach-Object { # | Import-Csv .\SP.csv |ForEach-Object { ...
    ForEach($SubNet in $NLIP) {
        if (IPInRange $_.IP $SubNet.IPRange) {
            [PSCustomObject]@{
                IP      = $_.IP
                Name    = $_.Name
                IPRange = $SubNet.IPRange
                SubNet  = $SubNet.SubNet
            }
        }
    }
} # | Export-Csv .\Output.csv

Which results in:

IP          Name   IPRange      SubNet
--          ----   -------      ------
1.9.8.2     BOB    1.9.8.3/28   NetB
16.29.28.27 Pear   16.29.28.9/8 NetA
16.30.29.28 Banana 16.29.28.9/8 NetA

But as you are considering 3rd party scripts anyways, you might as well use this Join-Object script/Join-Object Module (see also: In Powershell, what's the best way to join two tables into one?):

$SP |Join $NLIP -Using { IPInRange $Left.IP $Right.IPRange }

Which gives the same results.

CodePudding user response:

I believe this will work for you if I understood the NLIP construct correctly.

We will loop through all the SP objects and see if we can find any NLIP that match the IP range using the IPinRange function you linked. We will then add the 2 properties you want to the SP object if matched and finally pass thru to the pipeline or you can append | export-csv -path YourPath to the end if you would like to send to a csv file

$SP | ForEach-Object {
    $target = $_
    $matched = $NLIP | ForEach-Object {
        $item = $_
        # Using where to single out matching range using IPinRange function
        $_.IpRanges.Where({ IPInRange -IPAddress $target.ip -Range $_.CidrAddress }) |
        ForEach-Object {
            # for matching range output custom object containing the displayname and iprange
            [PSCustomObject]@{
                DisplayName = $item.DisplayName
                IpRange     = $_.CidrAddress
            }
        }
    }
    # add the 2 properties (DisplayName and IpRange) from the match to the original $SP
    # object and then pass thru
    $target | Add-Member -NotePropertyName DisplayName -NotePropertyValue $matched.DisplayName
    $target | Add-Member -NotePropertyName IpRange -NotePropertyValue $matched.IpRange -PassThru

}

By the way, this is how I envisioned the NLIP objects and what I tested with

$NLIP = @(
    [pscustomobject]@{
        DisplayName = 'Named location 1'
        IpRanges    = @(
            [pscustomobject]@{
                CidrAddress = '16.29.28.9/28'
            },
            [pscustomobject]@{
                CidrAddress = '1.9.8.3/28'
            }
        )
    },
    [pscustomobject]@{
        DisplayName = 'Named location 2'
        IpRanges    = @(
            [pscustomobject]@{
                CidrAddress = '16.29.28.25/28'
            },
            [pscustomobject]@{
                CidrAddress = '1.9.8.25/28'
            }
        )
    }
)
  • Related