Home How to track path/key during recursion through hash of hashes in PowerShell?
Reply: 1

How to track path/key during recursion through hash of hashes in PowerShell?

Spacey
1#
Spacey Published in 2018-02-13 10:28:55Z

I've been trying to compare two hashes of hashes against each other. Unfortunately even with the great help from here I struggled to pull together what I was trying to do.

So, I've resorted to searching the internet again and I was lucky enough to find Edxi's code (full credit to him and Dbroeglin who looks to have written it originally)

https://gist.github.com/edxi/87cb8a550b43ec90e4a89d2e69324806

His compare-hash function does exactly what I needed in terms of its comparisons. However, I'd like to report of the full path of the hash in the final output. So I've tried to update the code to allow for this. My thinking being that I should be able to take the Key (aka path) while the code loops over itself but I'm clearly going about it in the wrong manner.

Am I going about this with the right approach, and if not, would someone suggest another method please? The biggest problem I've found so far is that if I change the hash, my code changes don't work e.g. adding 'More' = 'stuff' So it becomes $sailings.Arrivals.PDH083.More breaks the path in the way I've set it.

My version of the code:

$Sailings = @{
    'Arrivals'   = @{
        'PDH083' = @{
            'GoingTo'   = 'Port1'
            'Scheduled' = '09:05'
            'Expected'  = '10:11'
            'Status'    = 'Delayed'
        }
    }
    'Departures' = @{
        'PDH083' = @{
            'ArrivingFrom' = 'Port1'
            'Scheduled'    = '09:05'
            'Expected'     = '09:05'
            'Status'       = 'OnTime'
            'Other'        = @{'Data' = 'Test'}
        }
    }
}

$Flights = @{
    'Arrivals'   = @{
        'PDH083' = @{
            'GoingTo'   = 'Port'
            'Scheduled' = '09:05'
            'Expected'  = '10:20'
            'Status'    = 'Delayed'

        }
    }
    'Departures' = @{
        'PDH083' = @{
            'ArrivingFrom' = 'Port11'
            'Scheduled'    = '09:05'
            'Expected'     = '09:05'
            'Status'       = 'NotOnTime'
            'Other'        = @{'Data' = 'Test_Diff'}
        }
    }
}


function Compare-Hashtable {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Hashtable]$Left,

        [Parameter(Mandatory = $true)]
        [Hashtable]$Right,
        [string] $path,
        [boolean] $trackpath = $True
    )
    write-host "PAth received as: $path"
    function New-Result($Key, $LValue, $Side, $RValue) {
        New-Object -Type PSObject -Property @{
            key    = $Key
            lvalue = $LValue
            rvalue = $RValue
            side   = $Side
        }
    }




    [Object[]]$Results = $Left.Keys | ForEach-Object {

        if ($trackpath ) {
            write-host "Working on Path: " $path + $_
        }

        if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
            write-host "Right key not matched. Report path as: $path"
            New-Result $path $Left[$_] "<=" $Null
        }
        else {
            if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
                if ($path -ne $null -or $path -ne "/") {
                    $path = $path + "/" + $_
                    write-host "Sending Path to function as: $path"
                }
                Compare-Hashtable $Left[$_] $Right[$_] $path
            }
            else {
                $LValue, $RValue = $Left[$_], $Right[$_]
                if ($LValue -ne $RValue) {
                    $path = $path + "/" + $_
                    write-host "Not a hash so must be a value at path:$path"
                    New-Result $path $LValue "!=" $RValue
                }
                else {
                    Write-Host "Before changing path: $path "
                    if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) {
                        $path = $path.Substring(0, $path.lastIndexOf('/'))
                        Write-Host "After changing path: $path "
                    }
                }
            }

        }
       # if (($path.Substring(0, $path.lastIndexOf('/'))).length >0) { 
       # Tried to use this to stop error on substring being less than zero
       # but clearly doesnt work when you add more levels to the hash 
                   $path = $path.Substring(0, $path.lastIndexOf('/'))
       # } else { $path = $path.Substring(0, $path.lastIndexOf('/')) }

    }



    $Results += $Right.Keys | ForEach-Object {
        if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
            New-Result $_ $Null "=>" $Right[$_]

        }
    }


    if ($Results -ne $null) { $Results }
}
cls

Compare-Hashtable $Sailings $Flights

outputs
key                             lvalue side rvalue
---                             ------ ---- ------
/Arrivals/PDH083/Expected       10:11  !=   10:20
/Arrivals/GoingTo               Port1  !=   Port
/Departures/PDH083/ArrivingFrom Port1  !=   Port11
/Departures/PDH083/Status       OnTime !=   NotOnTime
/Departures/PDH083/Other/Data   Test   !=   Test_Diff
iRon
2#
iRon Reply to 2018-02-13 16:23:39Z

I won't do that much string manipulation on the $Path but threat $Path as an array and join it to a string at the moment you assign it as a property (key = $Path -Join "/") to the object:

function Compare-Hashtable {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Hashtable]$Left,

        [Parameter(Mandatory = $true)]
        [Hashtable]$Right,
        [string[]] $path = @(),
        [boolean] $trackpath = $True
    )
    write-host "Path received as: $path"
    function New-Result($Path, $LValue, $Side, $RValue) {
        New-Object -Type PSObject -Property @{
            key    = $Path -Join "/"
            lvalue = $LValue
            rvalue = $RValue
            side   = $Side
        }
    }

    $Left.Keys | ForEach-Object {
        $NewPath = $Path + $_
        if ($trackpath ) {
            write-host "Working on Path: " $NewPath
        }

        if ($Left.ContainsKey($_) -and !$Right.ContainsKey($_)) {
            write-host "Right key not matched. Report path as: $NewPath"
            New-Result $NewPath $Left[$_] "<=" $Null
        }
        else {
            if ($Left[$_] -is [hashtable] -and $Right[$_] -is [hashtable] ) {
                 Compare-Hashtable $Left[$_] $Right[$_] $NewPath
            }
            else {
                $LValue, $RValue = $Left[$_], $Right[$_]
                if ($LValue -ne $RValue) {
                    New-Result $NewPath $LValue "!=" $RValue
                }
             }

        }
    }
    $Right.Keys | ForEach-Object {
        $NewPath = $Path + $_
        if (!$Left.ContainsKey($_) -and $Right.ContainsKey($_)) {
            New-Result $NewPath $Null "=>" $Right[$_]

        }
    }
}
cls

Compare-Hashtable $Sailings $Flights

side-note: Write Single Records to the Pipeline (SC03), see: Strongly Encouraged Development Guidelines. In other words, don't do the $Results += ... but intermediately put any completed result in the pipeline for the next cmdlet to be picked up (besides, it unnecessarily code)...

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.364027 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO