HashTable/HashTable.ps1

#region Copyright & License

# Copyright © 2012 - 2020 François Chabot
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#endregion

Set-StrictMode -Version Latest

<#
.SYNOPSIS
    Compare two HashTables and returns an array of differences.
.DESCRIPTION
    The Compare-HashTable function computes differences between two HashTables. Results are returned as an array of
    objects with the properties: "Key" (the name of the key for which there is a difference), "SideIndicator" (one of
    "<=", "!=" or "=>"), "ReferenceValue" an "DifferenceValue" (resp. the Reference and Difference value associated
    with the Key).
.PARAMETER ReferenceHashTable
    The HashTable used as a reference for comparison.
.PARAMETER DifferenceHashTable
    The HashTable that is compared to the reference HashTable.
.EXAMPLE
    Compare-HashTable @{ a = 1 ; b = 2 ; c = 3 } @{ b = 2 ; c = 4 ; e = 5}
    Returns a difference for ("3 <="), c (3 "!=" 4) and e ("=>" 5).
.EXAMPLE
    $ReferenceHashTable = @{ a = 1 ; b = 2 ; c = 3 ; f = $null ; g = 6 }
    $DifferenceHashTable = @{ b = 2 ; c = 4 ; e = 5 ; f = $null ; g = $null }
    Compare-HashTable $ReferenceHashTable $DifferenceHashTable
    Returns a difference for a ("3 <="), c (3 "!=" 4), e ("=>" 5) and g (6 "<=").
.NOTES
    See https://gist.github.com/dbroeglin/c6ce3e4639979fa250cf
#>

function Compare-HashTable {
    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param (
        [Parameter(Mandatory = $true)]
        [HashTable]
        $ReferenceHashTable,

        [Parameter(Mandatory = $true)]
        [HashTable]
        $DifferenceHashTable,

        [Parameter(Mandatory = $false, DontShow)]
        [string]
        $Prefix = ''
    )
    Resolve-ActionPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    $ReferenceHashTable.Keys + $DifferenceHashTable.Keys | Sort-Object -Unique -PipelineVariable key | ForEach-Object -Process {
        $propertyName = if ($Prefix) { "$Prefix.$key" } else { $key }
        if ($ReferenceHashTable.ContainsKey($key) -and !$DifferenceHashTable.ContainsKey($key)) {
            [PSCustomObject]@{Key = $propertyName ; ReferenceValue = $ReferenceHashTable.$key ; SideIndicator = '<' ; DifferenceValue = $null } | Tee-Object -Variable difference
            Write-Verbose -Message $difference
        } elseif (!$ReferenceHashTable.ContainsKey($key) -and $DifferenceHashTable.ContainsKey($key)) {
            [PSCustomObject]@{Key = $propertyName ; ReferenceValue = $null ; SideIndicator = '>' ; DifferenceValue = $DifferenceHashTable.$key } | Tee-Object -Variable difference
            Write-Verbose -Message $difference
        } else {
            $referenceValue, $differenceValue = $ReferenceHashTable.$key, $DifferenceHashTable.$key
            if ($referenceValue -ne $differenceValue) {
                [PSCustomObject]@{Key = $propertyName ; ReferenceValue = $referenceValue ; SideIndicator = '<>' ; DifferenceValue = $differenceValue } | Tee-Object -Variable difference
                Write-Verbose -Message $difference
            }
        }
    }
}

<#
.SYNOPSIS
    Returns a new HashTable which is the merging of the input hash tables.
.DESCRIPTION
    Properties are not overwritten during the merge operation unless forced. Even when forced it is possible to
    provide a list of properties not to overwrite.
.EXAMPLE
.NOTES
    © 2020 be.stateless.
#>

function Merge-HashTable {
    [CmdletBinding()]
    [OutputType([HashTable])]
    param(
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
        [HashTable[]]
        $HashTable,

        [Parameter(Mandatory = $false)]
        [string[]]
        $Exclude = @(),

        [Parameter(Mandatory = $false)]
        [switch]
        $Force
    )
    begin {
        Resolve-ActionPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
        $result = @{ }
    }
    process {
        $HashTable | ForEach-Object -Process { $_ } -PipelineVariable currentHashTable | Select-Object -ExpandProperty Keys -PipelineVariable key | ForEach-Object -Process {
            $propertyExists = $result.ContainsKey($key)
            if (-not $propertyExists -or ($Force -and $key -notin $Exclude) ) {
                $result.$key = $currentHashTable.$key
                if ($propertyExists) {
                    Write-Verbose -Message "Property '$key' has been overwritten because it has been defined multiple times."
                }
            }
        }
    }
    end {
        $result
    }
}

# SIG # Begin signature block
# MIII0QYJKoZIhvcNAQcCoIIIwjCCCL4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU3w16bg2XaK03AZFpCcUs7cMq
# 4f2gggVMMIIFSDCCAzCgAwIBAgIJAJkr3mJdTBkUMA0GCSqGSIb3DQEBCwUAMEEx
# PzA9BgNVBAMeNgBpAGMAcgBhAGYAdABzAG8AZgB0AHcAYQByAGUAQABzAHQAYQB0
# AGUAbABlAHMAcwAuAGIAZTAeFw0yMTA2MjUxNDEyMjNaFw00MTA2MjAxNDEyMjNa
# MEExPzA9BgNVBAMeNgBpAGMAcgBhAGYAdABzAG8AZgB0AHcAYQByAGUAQABzAHQA
# YQB0AGUAbABlAHMAcwAuAGIAZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAOeqdUHBv7sxSeX3aj6yPKj7PAvs8izpVXjyEBl5aR8mQneVcXuF53AH7EW1
# 6E5p4+Az5pJPGUD5c3tXhiGMF7vgLhQjO6hlaVBRIqiIYHikNLwMNy6YBMc/QQYM
# rPhqHEFsZ53dkBIIj3M8e3kFcTFA09n25yDtTPDab4nd9yUhc9Qc8+nfpIzfYsoP
# 1pZ3nCzhw6hN2/44v1dkQrG3dRYwt+px65p6NPNZWEJpt4VCJjIFh+lBYJdxm9d4
# X/rAnlHIkbv7liOavWDzgHVabS3hdAWtcDmynm+7+FcZDFqPWNCl3e4SS7xe4s/R
# CKFKA0IsfKkSk9YJlLgeSQIEXUOOWXJAGaLqnRD8xWLZsc4Oi9GZg7XV1mv/S88c
# oztXnwtAN3OOlRKBh2QbomMgxeMO0GvsLE/cq5Q/YKAoz+KGr/7LcZq9jzQ8IPus
# ZvWLeDXmxPiwJjpZc1koLgfGIEX2NStQTT3QmacWr9thrWcKvI+4uBmI4exS9B4a
# R3nV91w5EY+2RoYsHqej9LWwNamO96+jMX9pxprTX+EkLUuMAikw/po8sBC9MUUn
# 5pMWmUv7DCtQOLGGBDDMMMkn4ZcjpCEEdPGHRKfqNnD27ssGtDjiNzfQrsm67toU
# bBwUF+gyJq/YckWquYJhA9ZOFWEADuIwGnsOzsoRvuQyY+p9AgMBAAGjQzBBMA4G
# A1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzAXBgNVHREEEDAO
# ggxzdGF0ZWxlc3MuYmUwDQYJKoZIhvcNAQELBQADggIBACithYM3qckZRc9+Xbfu
# a6gWr3HwjrW+FHKgjfrcOm8ZnLVapb9xFqsqrRQqd3RXWQDINEGrtI2rSfrzyfoK
# UiTgldIfQNP1ZcGY229d++90t3hdo2mlt05hjYlbMENloJHpsEP0vQZmwOcEimCT
# ex1pymYM+P9pj3j8UD1PT1eIZot6or8fBRl63UybyDSrM7L4UOkkAOniKxWy5pW6
# 6duS8SR+SZpr3Bv44NyXPj0Nv+MIpLmsLrd7XPBFmnGxzY01ZO9vzi9KEhM2wT5i
# jPqHDNOvfPiADtAa+EyUBzdJiqy9heCz/TMZQgMWGwtfqJNxWZmsHcha2anW4Qt+
# mzrLO4GojWoVog9uVSAq+l0a+YQsd1u1kUmm4vgZCFyUA+lEp4LkI7ca2VBHkLPD
# w+u2DoDMRiqFPZjO7BCKjGc0jj9B/qGR3JVt+tqDdB621xXf2YGF2oFvxZQ/keGt
# 0ujfJ+JwN3nCulDAA4773q6KUnfykyrvAgITNbRJL6TngeRKtw9VIJBPxzqMzLpV
# 5ggXNituwLaD1CCBJ1oo9DZHpL9gplXp1wGrelJOTiJhh+pdNsPtRH7CrranWa5h
# LFLuigqin0eewQ5giJ1VaiBVEseOmiZog+27UpFIv40aDzgGL3YxB/Mu0ojwrQtp
# WLmqJCmWnR5qxOm0yK+zNWe0MYIC7zCCAusCAQEwTjBBMT8wPQYDVQQDHjYAaQBj
# AHIAYQBmAHQAcwBvAGYAdAB3AGEAcgBlAEAAcwB0AGEAdABlAGwAZQBzAHMALgBi
# AGUCCQCZK95iXUwZFDAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAA
# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
# DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUOsiC8NPK29idrLD2tG4/lWSJ
# 5EgwDQYJKoZIhvcNAQEBBQAEggIAcDMTEvEVfKvteM8FC23bvPnX1dr8D663DK2Z
# W9rjEcduSu++buQUqeEVJntHSvu9KyV/o1SDZgDNzxkWQEZ8W8SHuLrg+YMJQl3w
# 3ViBOrQ0XCUVzqAPcTofwGfcqmo0MK7erFqw7OZocz+U+YWhlUl+eR8A6YE4BE5S
# LbTQb6nJOrLReEgjcmLf3EfZ+1PLRHQ9xYbY58jN3mq6WAjPhIgCXCc79quX8+Ho
# k9YHyuc4kdIbsjFCFFJe63Ql5iGoz3aF/SKt1cTCx03sXrg1/FynGpP2R/x0kLFY
# lLWRfxIs0DkXMRPfLvYmpEodcTotZVCmliRdxyAn7dDjeE9XskpgPSpKVsT4H2pL
# ySnex1Pfb9xkjLljqjF02OK7XZu7sGG96khJYvW/r7TTLYwdZ1KN8n1oufin6Xqt
# 8AIl0MNdYerxWhzC0lY5BEYiSDRfioAMM0bESeweZe8fXMG0CwvYw3uQ7RpN4EoA
# go3yWdumSFoERfSwps9jWBYUxkON15M3YlUJD/wN1h6toLSyAzXqjpnTxax1oy9r
# I+MzlXPaemo2NxcdkclKURWmzsvWyu2q/VwgUHJQq4Ov/0g1QdJALz0F8/egumuh
# komtqt+PfMZQx+dAQAT5R/WZ3jtAKq5mtvxpksV77TxfMtBNRdML7dK+2m4hK7X5
# iI1crcQ=
# SIG # End signature block