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
# MIIJEgYJKoZIhvcNAQcCoIIJAzCCCP8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU3w16bg2XaK03AZFpCcUs7cMq
# 4f2gggWhMIIFnTCCA1GgAwIBAgIQKBOAjgMDO55A7UJ/k/g5nTBBBgkqhkiG9w0B
# AQowNKAPMA0GCWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQC
# AQUAogMCASAwJjEkMCIGA1UEAwwbaWNyYWZ0c29mdHdhcmVAc3RhdGVsZXNzLmJl
# MB4XDTIwMDYyMzExNDM1NloXDTIxMDYyMzEyMDM1NlowJjEkMCIGA1UEAwwbaWNy
# YWZ0c29mdHdhcmVAc3RhdGVsZXNzLmJlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
# MIICCgKCAgEAmQcb0GwlBHBHBJZ9vNM8EewN7T+nhsWVU0WBoWnIw6UAT99Rw9x5
# RcfOQU2hxqKmR1k+iI6B+qddpTC3VLSChA/mh1P4pCDDsZeyR/0nn/r/DezhDe8x
# 5jckjR88KSRcgDoh0kLjgfrToDpx9EvBcwXmNJKDwBIWu5SBvk04beU4XO7OHjBo
# g0kMaHxCZc9HcWfdzBefP+fbVzu6f1j1WgEqZn9sr1ML2ulHRdu26+56xGq9RZGJ
# vXyY1mY+K5mqBcET+1bV2pZnBrM3Gc/hlmvTkwrC0ZGBALLZWZqqpLVrDCY5eoHP
# w2C0kA4JzK4Q1o218s+wXbuDcjYRIZqBSwI8fizR/4DS+6dEjfa3kzs2z/MrkJOk
# hJ06tiMSRr55tX1DR8NwVLdiNqZYvs4zP2ZNRMMI8uFCjkP/Wn1hfBr+GSPlgdLq
# 2TFishY2pj5O1WlE/tCz+B0YLhPWdfbVEp8kB3fGBsVf7uw4STK/wDA1MYRIHikt
# w+K9gtdf0eIR9dYX9CMwoDN2TNLK6vnCWMrzWFe5EOU3/oljUBkyQT838a5A6wMu
# cGeu7Cwjdigylt7ULaTglL7ORIyaRbzkltxd+1oaQ21kjl4ef0ZD2gWLj7bwrZR+
# KWCfmaHFoZlVRKNPtScuyOnilPGGZ6T7SNuwVxSXFRtbp+cQea4UxxUCAwEAAaNf
# MF0wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMBcGA1UdEQQQ
# MA6CDHN0YXRlbGVzcy5iZTAdBgNVHQ4EFgQUq4sCoE2IqN4K4uwNuibjqd5yNNQw
# QQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDAN
# BglghkgBZQMEAgEFAKIDAgEgA4ICAQBR98amLpANKFlc7mPlkaV4ZtS2uTmbJ6dO
# qzyWKJ2yTmv7U9yq8PdEH9mPJlxYvGyNgxqHoocKv1SdjgYh27SM8pDnsfU2NpER
# 6K/3sICy6Orh9vhC+U18Bp93WoLEezolaBcF0co3/o+HazOvs/2zBFONFHMkef9/
# 3Bipm0sd95teHo53vLKViHbjSmoGxYsvJJiYITB4Zeo6xgUAmwcUpL1To62Lb3RP
# CDLKZQ5h8Ir07nncV4HLq+0qF3+G9Y0IXHJv6Qcr/XTTLo0J877HRqS37WJcgF8+
# 2nbZbqO9NVvp14A4nTqpeDFmzewDU33hiZvzuLHBj//OgLgGZ9lJPxCu0tVxfFWZ
# INHg1YHp3lMaAw00Q3tb/vhc5kE6Kl7FnXnUTsu4j+vUoaFMWhYezoyn9m4rD+xN
# RITrbLPZdWAZvVOJ8ehmswRhfiMZ1npwbrk7KU1UTsmMS7PHREWSyUM28WlMFf2i
# ut8TlY/MV/adUGr2GpqBWhxp5DRgfl1uamKm2wFlCra3/kReVlQgC/Bbod2JOgJW
# t8zCbO4nJx+fJYwM9RG70h/TmuqzP8uChsHtKcgs2YtXmSm12JZakXY4IflInI7p
# ddDEs9UOfsWXDsqpvmFQZbwgGeNeEsPk3Fdm1MzDtS9PBXMk4jGGXNzEsVUgwf42
# 2HuDWeX/4jGCAtswggLXAgEBMDowJjEkMCIGA1UEAwwbaWNyYWZ0c29mdHdhcmVA
# c3RhdGVsZXNzLmJlAhAoE4COAwM7nkDtQn+T+DmdMAkGBSsOAwIaBQCgeDAYBgor
# BgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEE
# MBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQ6
# yILw08rb2J2ssPa0bj+VZInkSDANBgkqhkiG9w0BAQEFAASCAgAy1YmmiJ5RCljK
# Bv0UG6hGwWrYypnf/Y1PfUWW49aatsnGPyCzxSBHrRJ4lq2SwcxhMeog92SL+6qT
# /v54UUI6N9qBly9Jw29zMolo9ZnLBA8sT/LeO45sSGksp4Y2JVoUdd3ruNPFP2LS
# hPLLPcyV4EVqBjVq+9BT+12ITDxC6lWxq+z/M+MatigG6ljNt/nATwsopzngcaKT
# wKwKIkPTbIgMJK+qLKrEYU5JN3dHwcOQBysTy2EqtcLX8qsK3XJTt8rGqoOUfn1M
# 8K5o/PVX7AZi4+64pGTvGw8cQ/nondLvj7sMXAS+xhkjqMDts25z4x+22cIt/7mN
# eiJFEOy6e61BmsnWadtyss8+cWQCVPokhkIbIET69h7HvnC/amlXUtp2YofZRay1
# WnNT/7plvblSvG/WqZ5YYpAQRvPMwP3D9r46DFHiGYZurksaeRK77vU0KXZnIxGP
# xStcDjXMDwkfB5VJdGue32AR4Csi/CyhsQsoGku4S/LOsqrOB9Bl3FBpGhgun8wE
# n2zw6db//CrczZcXGUnJRdq0zRr3+73ad3NkLe7+b5pgbAfnMnOnvolpq/KYR/VW
# bNlVZk6qzKYv9EaAETvvszjDUl/NBgwKhQv/H0fAzbCtL9lD9XbydcU3Wc1fX9sA
# WKce6icMjhtFgSHvum1IDnSHW2L9OA==
# SIG # End signature block