PSSharedGoods.psm1

function Write-Color { 
    <#
    .SYNOPSIS
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    .DESCRIPTION
    Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options.
 
    It provides:
    - Easy manipulation of colors,
    - Logging output to file (log)
    - Nice formatting options out of the box.
    - Ability to use aliases for parameters
 
    .PARAMETER Text
    Text to display on screen and write to log file if specified.
    Accepts an array of strings.
 
    .PARAMETER Color
    Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER BackGroundColor
    Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string.
    If there are more strings than colors it will start from the beginning.
    Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
 
    .PARAMETER StartTab
    Number of tabs to add before text. Default is 0.
 
    .PARAMETER LinesBefore
    Number of empty lines before text. Default is 0.
 
    .PARAMETER LinesAfter
    Number of empty lines after text. Default is 0.
 
    .PARAMETER StartSpaces
    Number of spaces to add before text. Default is 0.
 
    .PARAMETER LogFile
    Path to log file. If not specified no log file will be created.
 
    .PARAMETER DateTimeFormat
    Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss
 
    .PARAMETER LogTime
    If set to $true it will add time to log file. Default is $true.
 
    .PARAMETER LogRetry
    Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2.
 
    .PARAMETER Encoding
    Encoding of the log file. Default is Unicode.
 
    .PARAMETER ShowTime
    Switch to add time to console output. Default is not set.
 
    .PARAMETER NoNewLine
    Switch to not add new line at the end of the output. Default is not set.
 
    .PARAMETER NoConsoleOutput
    Switch to not output to console. Default all output goes to console.
 
    .EXAMPLE
    Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan
 
    .EXAMPLE
    Write-Color -Text "This is text in Green ",
                      "followed by red ",
                      "and then we have Magenta... ",
                      "isn't it fun? ",
                      "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1
 
    .EXAMPLE
    Write-Color "1. ", "Option 1" -Color Yellow, Green
    Write-Color "2. ", "Option 2" -Color Yellow, Green
    Write-Color "3. ", "Option 3" -Color Yellow, Green
    Write-Color "4. ", "Option 4" -Color Yellow, Green
    Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1
 
    .EXAMPLE
    Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss"
    Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." `
                -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt"
 
    .EXAMPLE
    Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow
    Write-Color -t "my text" -c yellow -b green
    Write-Color -text "my text" -c red
 
    .EXAMPLE
    Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput
 
    .NOTES
    Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings
    Project support: https://github.com/EvotecIT/PSWriteColor
    Original idea: Josh (https://stackoverflow.com/users/81769/josh)
 
    #>

    [alias('Write-Colour')]
    [CmdletBinding()]
    param (
        [alias ('T')] [String[]]$Text,
        [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White,
        [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null,
        [alias ('Indent')][int] $StartTab = 0,
        [int] $LinesBefore = 0,
        [int] $LinesAfter = 0,
        [int] $StartSpaces = 0,
        [alias ('L')] [string] $LogFile = '',
        [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss',
        [alias ('LogTimeStamp')][bool] $LogTime = $true,
        [int] $LogRetry = 2,
        [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode',
        [switch] $ShowTime,
        [switch] $NoNewLine,
        [alias('HideConsole')][switch] $NoConsoleOutput
    )
    if (-not $NoConsoleOutput) {
        $DefaultColor = $Color[0]
        if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) {
            Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated."
            return
        }
        if ($LinesBefore -ne 0) {
            for ($i = 0; $i -lt $LinesBefore; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        } 
        if ($StartTab -ne 0) {
            for ($i = 0; $i -lt $StartTab; $i++) {
                Write-Host -Object "`t" -NoNewline 
            } 
        }  
        if ($StartSpaces -ne 0) {
            for ($i = 0; $i -lt $StartSpaces; $i++) {
                Write-Host -Object ' ' -NoNewline 
            } 
        }  
        if ($ShowTime) {
            Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline 
        } 
        if ($Text.Count -ne 0) {
            if ($Color.Count -ge $Text.Count) {

                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                }
            }
            else {
                if ($null -eq $BackGroundColor) {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline 
                    }
                }
                else {
                    for ($i = 0; $i -lt $Color.Length ; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline 
                    }
                    for ($i = $Color.Length; $i -lt $Text.Length; $i++) {
                        Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline 
                    }
                }
            }
        }
        if ($NoNewLine -eq $true) {
            Write-Host -NoNewline 
        }
        else {
            Write-Host 
        } 
        if ($LinesAfter -ne 0) {
            for ($i = 0; $i -lt $LinesAfter; $i++) {
                Write-Host -Object "`n" -NoNewline 
            } 
        }  
    }
    if ($Text.Count -and $LogFile) {

        $TextToFile = ""
        for ($i = 0; $i -lt $Text.Length; $i++) {
            $TextToFile += $Text[$i]
        }
        $Saved = $false
        $Retry = 0
        Do {
            $Retry++
            try {
                if ($LogTime) {
                    "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                else {
                    "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false
                }
                $Saved = $true
            }
            catch {
                if ($Saved -eq $false -and $Retry -eq $LogRetry) {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))"
                }
                else {
                    Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)"
                }
            }
        } Until ($Saved -eq $true -or $Retry -ge $LogRetry)
    }
}
function Get-PSRegistryDictionaries {
    <#
    .SYNOPSIS
    Retrieves a set of registry dictionaries for common registry hives and keys.
 
    .DESCRIPTION
    This function retrieves a set of registry dictionaries that provide mappings for common registry hives and keys. These dictionaries can be used to easily reference different registry locations in PowerShell scripts.
 
    .EXAMPLE
    Get-PSRegistryDictionaries
 
    Description:
    Retrieves all the registry dictionaries.
 
    #>

    [cmdletBinding()]
    param()
    if ($Script:Dictionary) {
        return
    }
    $Script:Dictionary = @{

        'HKUAD:'   = 'HKEY_ALL_USERS_DEFAULT' 
        'HKUA:'    = 'HKEY_ALL_USERS' 
        'HKUD:'    = 'HKEY_DEFAULT_USER' 
        'HKUDUD:'  = 'HKEY_ALL_DOMAIN_USERS_DEFAULT' 

        'HKUDU:'   = 'HKEY_ALL_DOMAIN_USERS'

        'HKUDUO:'  = 'HKEY_ALL_DOMAIN_USERS_OTHER'

        'HKUDUDO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT'

        'HKCR:'    = 'HKEY_CLASSES_ROOT'
        'HKCU:'    = 'HKEY_CURRENT_USER'
        'HKLM:'    = 'HKEY_LOCAL_MACHINE'
        'HKU:'     = 'HKEY_USERS'
        'HKCC:'    = 'HKEY_CURRENT_CONFIG'
        'HKDD:'    = 'HKEY_DYN_DATA'
        'HKPD:'    = 'HKEY_PERFORMANCE_DATA'
    }

    $Script:HiveDictionary = [ordered] @{

        'HKEY_ALL_USERS_DEFAULT'              = 'All+Default'
        'HKUAD'                               = 'All+Default'
        'HKEY_ALL_USERS'                      = 'All'
        'HKUA'                                = 'All'
        'HKEY_ALL_DOMAIN_USERS_DEFAULT'       = 'AllDomain+Default'
        'HKUDUD'                              = 'AllDomain+Default'
        'HKEY_ALL_DOMAIN_USERS'               = 'AllDomain'
        'HKUDU'                               = 'AllDomain'
        'HKEY_DEFAULT_USER'                   = 'Default'
        'HKUD'                                = 'Default'
        'HKEY_ALL_DOMAIN_USERS_OTHER'         = 'AllDomain+Other'
        'HKUDUO'                              = 'AllDomain+Other'
        'HKUDUDO'                             = 'AllDomain+Other+Default'
        'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' = 'AllDomain+Other+Default'

        'HKEY_CLASSES_ROOT'                   = 'ClassesRoot'
        'HKCR'                                = 'ClassesRoot'
        'ClassesRoot'                         = 'ClassesRoot'
        'HKCU'                                = 'CurrentUser'
        'HKEY_CURRENT_USER'                   = 'CurrentUser'
        'CurrentUser'                         = 'CurrentUser'
        'HKLM'                                = 'LocalMachine'
        'HKEY_LOCAL_MACHINE'                  = 'LocalMachine'
        'LocalMachine'                        = 'LocalMachine'
        'HKU'                                 = 'Users'
        'HKEY_USERS'                          = 'Users'
        'Users'                               = 'Users'
        'HKCC'                                = 'CurrentConfig'
        'HKEY_CURRENT_CONFIG'                 = 'CurrentConfig'
        'CurrentConfig'                       = 'CurrentConfig'
        'HKDD'                                = 'DynData'
        'HKEY_DYN_DATA'                       = 'DynData'
        'DynData'                             = 'DynData'
        'HKPD'                                = 'PerformanceData'
        'HKEY_PERFORMANCE_DATA '              = 'PerformanceData'
        'PerformanceData'                     = 'PerformanceData'
    }

    $Script:ReverseTypesDictionary = [ordered] @{
        'REG_SZ'        = 'string'
        'REG_NONE'      = 'none'
        'REG_EXPAND_SZ' = 'expandstring'
        'REG_BINARY'    = 'binary'
        'REG_DWORD'     = 'dword'
        'REG_MULTI_SZ'  = 'multistring'
        'REG_QWORD'     = 'qword'
        'string'        = 'string'
        'expandstring'  = 'expandstring'
        'binary'        = 'binary'
        'dword'         = 'dword'
        'multistring'   = 'multistring'
        'qword'         = 'qword'
        'none'          = 'none'
    }
}
$Script:RGBColors = [ordered] @{
    None                   = $null
    AirForceBlue           = 93, 138, 168
    Akaroa                 = 195, 176, 145
    AlbescentWhite         = 227, 218, 201
    AliceBlue              = 240, 248, 255
    Alizarin               = 227, 38, 54
    Allports               = 18, 97, 128
    Almond                 = 239, 222, 205
    AlmondFrost            = 159, 129, 112
    Amaranth               = 229, 43, 80
    Amazon                 = 59, 122, 87
    Amber                  = 255, 191, 0
    Amethyst               = 153, 102, 204
    AmethystSmoke          = 156, 138, 164
    AntiqueWhite           = 250, 235, 215
    Apple                  = 102, 180, 71
    AppleBlossom           = 176, 92, 82
    Apricot                = 251, 206, 177
    Aqua                   = 0, 255, 255
    Aquamarine             = 127, 255, 212
    Armygreen              = 75, 83, 32
    Arsenic                = 59, 68, 75
    Astral                 = 54, 117, 136
    Atlantis               = 164, 198, 57
    Atomic                 = 65, 74, 76
    AtomicTangerine        = 255, 153, 102
    Axolotl                = 99, 119, 91
    Azure                  = 240, 255, 255
    Bahia                  = 176, 191, 26
    BakersChocolate        = 93, 58, 26
    BaliHai                = 124, 152, 171
    BananaMania            = 250, 231, 181
    BattleshipGrey         = 85, 93, 80
    BayOfMany              = 35, 48, 103
    Beige                  = 245, 245, 220
    Bermuda                = 136, 216, 192
    Bilbao                 = 42, 128, 0
    BilobaFlower           = 181, 126, 220
    Bismark                = 83, 104, 114
    Bisque                 = 255, 228, 196
    Bistre                 = 61, 43, 31
    Bittersweet            = 254, 111, 94
    Black                  = 0, 0, 0
    BlackPearl             = 31, 38, 42
    BlackRose              = 85, 31, 47
    BlackRussian           = 23, 24, 43
    BlanchedAlmond         = 255, 235, 205
    BlizzardBlue           = 172, 229, 238
    Blue                   = 0, 0, 255
    BlueDiamond            = 77, 26, 127
    BlueMarguerite         = 115, 102, 189
    BlueSmoke              = 115, 130, 118
    BlueViolet             = 138, 43, 226
    Blush                  = 169, 92, 104
    BokaraGrey             = 22, 17, 13
    Bole                   = 121, 68, 59
    BondiBlue              = 0, 147, 175
    Bordeaux               = 88, 17, 26
    Bossanova              = 86, 60, 92
    Boulder                = 114, 116, 114
    Bouquet                = 183, 132, 167
    Bourbon                = 170, 108, 57
    Brass                  = 181, 166, 66
    BrickRed               = 199, 44, 72
    BrightGreen            = 102, 255, 0
    BrightRed              = 146, 43, 62
    BrightTurquoise        = 8, 232, 222
    BrilliantRose          = 243, 100, 162
    BrinkPink              = 250, 110, 121
    BritishRacingGreen     = 0, 66, 37
    Bronze                 = 205, 127, 50
    Brown                  = 165, 42, 42
    BrownPod               = 57, 24, 2
    BuddhaGold             = 202, 169, 6
    Buff                   = 240, 220, 130
    Burgundy               = 128, 0, 32
    BurlyWood              = 222, 184, 135
    BurntOrange            = 255, 117, 56
    BurntSienna            = 233, 116, 81
    BurntUmber             = 138, 51, 36
    ButteredRum            = 156, 124, 56
    CadetBlue              = 95, 158, 160
    California             = 224, 141, 60
    CamouflageGreen        = 120, 134, 107
    Canary                 = 255, 255, 153
    CanCan                 = 217, 134, 149
    CannonPink             = 145, 78, 117
    CaputMortuum           = 89, 39, 32
    Caramel                = 255, 213, 154
    Cararra                = 237, 230, 214
    Cardinal               = 179, 33, 52
    CardinGreen            = 18, 53, 36
    CareysPink             = 217, 152, 160
    CaribbeanGreen         = 0, 222, 164
    Carmine                = 175, 0, 42
    CarnationPink          = 255, 166, 201
    CarrotOrange           = 242, 142, 28
    Cascade                = 141, 163, 153
    CatskillWhite          = 226, 229, 222
    Cedar                  = 67, 48, 46
    Celadon                = 172, 225, 175
    Celeste                = 207, 207, 196
    Cello                  = 55, 79, 107
    Cement                 = 138, 121, 93
    Cerise                 = 222, 49, 99
    Cerulean               = 0, 123, 167
    CeruleanBlue           = 42, 82, 190
    Chantilly              = 239, 187, 204
    Chardonnay             = 255, 200, 124
    Charlotte              = 167, 216, 222
    Charm                  = 208, 116, 139
    Chartreuse             = 127, 255, 0
    ChartreuseYellow       = 223, 255, 0
    ChelseaCucumber        = 135, 169, 107
    Cherub                 = 246, 214, 222
    Chestnut               = 185, 78, 72
    ChileanFire            = 226, 88, 34
    Chinook                = 150, 200, 162
    Chocolate              = 210, 105, 30
    Christi                = 125, 183, 0
    Christine              = 181, 101, 30
    Cinnabar               = 235, 76, 66
    Citron                 = 159, 169, 31
    Citrus                 = 141, 182, 0
    Claret                 = 95, 25, 51
    ClassicRose            = 251, 204, 231
    ClayCreek              = 145, 129, 81
    Clinker                = 75, 54, 33
    Clover                 = 74, 93, 35
    Cobalt                 = 0, 71, 171
    CocoaBrown             = 44, 22, 8
    Cola                   = 60, 48, 36
    ColumbiaBlue           = 166, 231, 255
    CongoBrown             = 103, 76, 71
    Conifer                = 178, 236, 93
    Copper                 = 218, 138, 103
    CopperRose             = 153, 102, 102
    Coral                  = 255, 127, 80
    CoralRed               = 255, 64, 64
    CoralTree              = 173, 111, 105
    Coriander              = 188, 184, 138
    Corn                   = 251, 236, 93
    CornField              = 250, 240, 190
    Cornflower             = 147, 204, 234
    CornflowerBlue         = 100, 149, 237
    Cornsilk               = 255, 248, 220
    Cosmic                 = 132, 63, 91
    Cosmos                 = 255, 204, 203
    CostaDelSol            = 102, 93, 30
    CottonCandy            = 255, 188, 217
    Crail                  = 164, 90, 82
    Cranberry              = 205, 96, 126
    Cream                  = 255, 255, 204
    CreamCan               = 242, 198, 73
    Crimson                = 220, 20, 60
    Crusta                 = 232, 142, 90
    Cumulus                = 255, 255, 191
    Cupid                  = 246, 173, 198
    CuriousBlue            = 40, 135, 200
    Cyan                   = 0, 255, 255
    Cyprus                 = 6, 78, 64
    DaisyBush              = 85, 53, 146
    Dandelion              = 250, 218, 94
    Danube                 = 96, 130, 182
    DarkBlue               = 0, 0, 139
    DarkBrown              = 101, 67, 33
    DarkCerulean           = 8, 69, 126
    DarkChestnut           = 152, 105, 96
    DarkCoral              = 201, 90, 73
    DarkCyan               = 0, 139, 139
    DarkGoldenrod          = 184, 134, 11
    DarkGray               = 169, 169, 169
    DarkGreen              = 0, 100, 0
    DarkGreenCopper        = 73, 121, 107
    DarkGrey               = 169, 169, 169
    DarkKhaki              = 189, 183, 107
    DarkMagenta            = 139, 0, 139
    DarkOliveGreen         = 85, 107, 47
    DarkOrange             = 255, 140, 0
    DarkOrchid             = 153, 50, 204
    DarkPastelGreen        = 3, 192, 60
    DarkPink               = 222, 93, 131
    DarkPurple             = 150, 61, 127
    DarkRed                = 139, 0, 0
    DarkSalmon             = 233, 150, 122
    DarkSeaGreen           = 143, 188, 143
    DarkSlateBlue          = 72, 61, 139
    DarkSlateGray          = 47, 79, 79
    DarkSlateGrey          = 47, 79, 79
    DarkSpringGreen        = 23, 114, 69
    DarkTangerine          = 255, 170, 29
    DarkTurquoise          = 0, 206, 209
    DarkViolet             = 148, 0, 211
    DarkWood               = 130, 102, 68
    DeepBlush              = 245, 105, 145
    DeepCerise             = 224, 33, 138
    DeepKoamaru            = 51, 51, 102
    DeepLilac              = 153, 85, 187
    DeepMagenta            = 204, 0, 204
    DeepPink               = 255, 20, 147
    DeepSea                = 14, 124, 97
    DeepSkyBlue            = 0, 191, 255
    DeepTeal               = 24, 69, 59
    Denim                  = 36, 107, 206
    DesertSand             = 237, 201, 175
    DimGray                = 105, 105, 105
    DimGrey                = 105, 105, 105
    DodgerBlue             = 30, 144, 255
    Dolly                  = 242, 242, 122
    Downy                  = 95, 201, 191
    DutchWhite             = 239, 223, 187
    EastBay                = 76, 81, 109
    EastSide               = 178, 132, 190
    EchoBlue               = 169, 178, 195
    Ecru                   = 194, 178, 128
    Eggplant               = 162, 0, 109
    EgyptianBlue           = 16, 52, 166
    ElectricBlue           = 125, 249, 255
    ElectricIndigo         = 111, 0, 255
    ElectricLime           = 208, 255, 20
    ElectricPurple         = 191, 0, 255
    Elm                    = 47, 132, 124
    Emerald                = 80, 200, 120
    Eminence               = 108, 48, 130
    Endeavour              = 46, 88, 148
    EnergyYellow           = 245, 224, 80
    Espresso               = 74, 44, 42
    Eucalyptus             = 26, 162, 96
    Falcon                 = 126, 94, 96
    Fallow                 = 204, 153, 102
    FaluRed                = 128, 24, 24
    Feldgrau               = 77, 93, 83
    Feldspar               = 205, 149, 117
    Fern                   = 113, 188, 120
    FernGreen              = 79, 121, 66
    Festival               = 236, 213, 64
    Finn                   = 97, 64, 81
    FireBrick              = 178, 34, 34
    FireBush               = 222, 143, 78
    FireEngineRed          = 211, 33, 45
    Flamingo               = 233, 92, 75
    Flax                   = 238, 220, 130
    FloralWhite            = 255, 250, 240
    ForestGreen            = 34, 139, 34
    Frangipani             = 250, 214, 165
    FreeSpeechAquamarine   = 0, 168, 119
    FreeSpeechRed          = 204, 0, 0
    FrenchLilac            = 230, 168, 215
    FrenchRose             = 232, 83, 149
    FriarGrey              = 135, 134, 129
    Froly                  = 228, 113, 122
    Fuchsia                = 255, 0, 255
    FuchsiaPink            = 255, 119, 255
    Gainsboro              = 220, 220, 220
    Gallery                = 219, 215, 210
    Galliano               = 204, 160, 29
    Gamboge                = 204, 153, 0
    Ghost                  = 196, 195, 208
    GhostWhite             = 248, 248, 255
    Gin                    = 216, 228, 188
    GinFizz                = 247, 231, 206
    Givry                  = 230, 208, 171
    Glacier                = 115, 169, 194
    Gold                   = 255, 215, 0
    GoldDrop               = 213, 108, 43
    GoldenBrown            = 150, 113, 23
    GoldenFizz             = 240, 225, 48
    GoldenGlow             = 248, 222, 126
    GoldenPoppy            = 252, 194, 0
    Goldenrod              = 218, 165, 32
    GoldenSand             = 233, 214, 107
    GoldenYellow           = 253, 238, 0
    GoldTips               = 225, 189, 39
    GordonsGreen           = 37, 53, 41
    Gorse                  = 255, 225, 53
    Gossamer               = 49, 145, 119
    GrannySmithApple       = 168, 228, 160
    Gray                   = 128, 128, 128
    GrayAsparagus          = 70, 89, 69
    Green                  = 0, 128, 0
    GreenLeaf              = 76, 114, 29
    GreenVogue             = 38, 67, 72
    GreenYellow            = 173, 255, 47
    Grey                   = 128, 128, 128
    GreyAsparagus          = 70, 89, 69
    GuardsmanRed           = 157, 41, 51
    GumLeaf                = 178, 190, 181
    Gunmetal               = 42, 52, 57
    Hacienda               = 155, 135, 12
    HalfAndHalf            = 232, 228, 201
    HalfBaked              = 95, 138, 139
    HalfColonialWhite      = 246, 234, 190
    HalfPearlLusta         = 240, 234, 214
    HanPurple              = 63, 0, 255
    Harlequin              = 74, 255, 0
    HarleyDavidsonOrange   = 194, 59, 34
    Heather                = 174, 198, 207
    Heliotrope             = 223, 115, 255
    Hemp                   = 161, 122, 116
    Highball               = 134, 126, 54
    HippiePink             = 171, 75, 82
    Hoki                   = 110, 127, 128
    HollywoodCerise        = 244, 0, 161
    Honeydew               = 240, 255, 240
    Hopbush                = 207, 113, 175
    HorsesNeck             = 108, 84, 30
    HotPink                = 255, 105, 180
    HummingBird            = 201, 255, 229
    HunterGreen            = 53, 94, 59
    Illusion               = 244, 152, 173
    InchWorm               = 202, 224, 13
    IndianRed              = 205, 92, 92
    Indigo                 = 75, 0, 130
    InternationalKleinBlue = 0, 24, 168
    InternationalOrange    = 255, 79, 0
    IrisBlue               = 28, 169, 201
    IrishCoffee            = 102, 66, 40
    IronsideGrey           = 113, 112, 110
    IslamicGreen           = 0, 144, 0
    Ivory                  = 255, 255, 240
    Jacarta                = 61, 50, 93
    JackoBean              = 65, 54, 40
    JacksonsPurple         = 46, 45, 136
    Jade                   = 0, 171, 102
    JapaneseLaurel         = 47, 117, 50
    Jazz                   = 93, 43, 44
    JazzberryJam           = 165, 11, 94
    JellyBean              = 68, 121, 142
    JetStream              = 187, 208, 201
    Jewel                  = 0, 107, 60
    Jon                    = 79, 58, 60
    JordyBlue              = 124, 185, 232
    Jumbo                  = 132, 132, 130
    JungleGreen            = 41, 171, 135
    KaitokeGreen           = 30, 77, 43
    Karry                  = 255, 221, 202
    KellyGreen             = 70, 203, 24
    Keppel                 = 93, 164, 147
    Khaki                  = 240, 230, 140
    Killarney              = 77, 140, 87
    KingfisherDaisy        = 85, 27, 140
    Kobi                   = 230, 143, 172
    LaPalma                = 60, 141, 13
    LaserLemon             = 252, 247, 94
    Laurel                 = 103, 146, 103
    Lavender               = 230, 230, 250
    LavenderBlue           = 204, 204, 255
    LavenderBlush          = 255, 240, 245
    LavenderPink           = 251, 174, 210
    LavenderRose           = 251, 160, 227
    LawnGreen              = 124, 252, 0
    LemonChiffon           = 255, 250, 205
    LightBlue              = 173, 216, 230
    LightCoral             = 240, 128, 128
    LightCyan              = 224, 255, 255
    LightGoldenrodYellow   = 250, 250, 210
    LightGray              = 211, 211, 211
    LightGreen             = 144, 238, 144
    LightGrey              = 211, 211, 211
    LightPink              = 255, 182, 193
    LightSalmon            = 255, 160, 122
    LightSeaGreen          = 32, 178, 170
    LightSkyBlue           = 135, 206, 250
    LightSlateGray         = 119, 136, 153
    LightSlateGrey         = 119, 136, 153
    LightSteelBlue         = 176, 196, 222
    LightYellow            = 255, 255, 224
    Lilac                  = 204, 153, 204
    Lime                   = 0, 255, 0
    LimeGreen              = 50, 205, 50
    Limerick               = 139, 190, 27
    Linen                  = 250, 240, 230
    Lipstick               = 159, 43, 104
    Liver                  = 83, 75, 79
    Lochinvar              = 86, 136, 125
    Lochmara               = 38, 97, 156
    Lola                   = 179, 158, 181
    LondonHue              = 170, 152, 169
    Lotus                  = 124, 72, 72
    LuckyPoint             = 29, 41, 81
    MacaroniAndCheese      = 255, 189, 136
    Madang                 = 193, 249, 162
    Madras                 = 81, 65, 0
    Magenta                = 255, 0, 255
    MagicMint              = 170, 240, 209
    Magnolia               = 248, 244, 255
    Mahogany               = 215, 59, 62
    Maire                  = 27, 24, 17
    Maize                  = 230, 190, 138
    Malachite              = 11, 218, 81
    Malibu                 = 93, 173, 236
    Malta                  = 169, 154, 134
    Manatee                = 140, 146, 172
    Mandalay               = 176, 121, 57
    MandarianOrange        = 146, 39, 36
    Mandy                  = 191, 79, 81
    Manhattan              = 229, 170, 112
    Mantis                 = 125, 194, 66
    Manz                   = 217, 230, 80
    MardiGras              = 48, 25, 52
    Mariner                = 57, 86, 156
    Maroon                 = 128, 0, 0
    Matterhorn             = 85, 85, 85
    Mauve                  = 244, 187, 255
    Mauvelous              = 255, 145, 175
    MauveTaupe             = 143, 89, 115
    MayaBlue               = 119, 181, 254
    McKenzie               = 129, 97, 60
    MediumAquamarine       = 102, 205, 170
    MediumBlue             = 0, 0, 205
    MediumCarmine          = 175, 64, 53
    MediumOrchid           = 186, 85, 211
    MediumPurple           = 147, 112, 219
    MediumRedViolet        = 189, 51, 164
    MediumSeaGreen         = 60, 179, 113
    MediumSlateBlue        = 123, 104, 238
    MediumSpringGreen      = 0, 250, 154
    MediumTurquoise        = 72, 209, 204
    MediumVioletRed        = 199, 21, 133
    MediumWood             = 166, 123, 91
    Melon                  = 253, 188, 180
    Merlot                 = 112, 54, 66
    MetallicGold           = 211, 175, 55
    Meteor                 = 184, 115, 51
    MidnightBlue           = 25, 25, 112
    MidnightExpress        = 0, 20, 64
    Mikado                 = 60, 52, 31
    MilanoRed              = 168, 55, 49
    Ming                   = 54, 116, 125
    MintCream              = 245, 255, 250
    MintGreen              = 152, 255, 152
    Mischka                = 168, 169, 173
    MistyRose              = 255, 228, 225
    Moccasin               = 255, 228, 181
    Mojo                   = 149, 69, 53
    MonaLisa               = 255, 153, 153
    Mongoose               = 179, 139, 109
    Montana                = 53, 56, 57
    MoodyBlue              = 116, 108, 192
    MoonYellow             = 245, 199, 26
    MossGreen              = 173, 223, 173
    MountainMeadow         = 28, 172, 120
    MountainMist           = 161, 157, 148
    MountbattenPink        = 153, 122, 141
    Mulberry               = 211, 65, 157
    Mustard                = 255, 219, 88
    Myrtle                 = 25, 89, 5
    MySin                  = 255, 179, 71
    NavajoWhite            = 255, 222, 173
    Navy                   = 0, 0, 128
    NavyBlue               = 2, 71, 254
    NeonCarrot             = 255, 153, 51
    NeonPink               = 255, 92, 205
    Nepal                  = 145, 163, 176
    Nero                   = 20, 20, 20
    NewMidnightBlue        = 0, 0, 156
    Niagara                = 58, 176, 158
    NightRider             = 59, 47, 47
    Nobel                  = 152, 152, 152
    Norway                 = 169, 186, 157
    Nugget                 = 183, 135, 39
    OceanGreen             = 95, 167, 120
    Ochre                  = 202, 115, 9
    OldCopper              = 111, 78, 55
    OldGold                = 207, 181, 59
    OldLace                = 253, 245, 230
    OldLavender            = 121, 104, 120
    OldRose                = 195, 33, 72
    Olive                  = 128, 128, 0
    OliveDrab              = 107, 142, 35
    OliveGreen             = 181, 179, 92
    Olivetone              = 110, 110, 48
    Olivine                = 154, 185, 115
    Onahau                 = 196, 216, 226
    Opal                   = 168, 195, 188
    Orange                 = 255, 165, 0
    OrangePeel             = 251, 153, 2
    OrangeRed              = 255, 69, 0
    Orchid                 = 218, 112, 214
    OuterSpace             = 45, 56, 58
    OutrageousOrange       = 254, 90, 29
    Oxley                  = 95, 167, 119
    PacificBlue            = 0, 136, 220
    Padua                  = 128, 193, 151
    PalatinatePurple       = 112, 41, 99
    PaleBrown              = 160, 120, 90
    PaleChestnut           = 221, 173, 175
    PaleCornflowerBlue     = 188, 212, 230
    PaleGoldenrod          = 238, 232, 170
    PaleGreen              = 152, 251, 152
    PaleMagenta            = 249, 132, 239
    PalePink               = 250, 218, 221
    PaleSlate              = 201, 192, 187
    PaleTaupe              = 188, 152, 126
    PaleTurquoise          = 175, 238, 238
    PaleVioletRed          = 219, 112, 147
    PalmLeaf               = 53, 66, 48
    Panache                = 233, 255, 219
    PapayaWhip             = 255, 239, 213
    ParisDaisy             = 255, 244, 79
    Parsley                = 48, 96, 48
    PastelGreen            = 119, 221, 119
    PattensBlue            = 219, 233, 244
    Peach                  = 255, 203, 164
    PeachOrange            = 255, 204, 153
    PeachPuff              = 255, 218, 185
    PeachYellow            = 250, 223, 173
    Pear                   = 209, 226, 49
    PearlLusta             = 234, 224, 200
    Pelorous               = 42, 143, 189
    Perano                 = 172, 172, 230
    Periwinkle             = 197, 203, 225
    PersianBlue            = 34, 67, 182
    PersianGreen           = 0, 166, 147
    PersianIndigo          = 51, 0, 102
    PersianPink            = 247, 127, 190
    PersianRed             = 192, 54, 44
    PersianRose            = 233, 54, 167
    Persimmon              = 236, 88, 0
    Peru                   = 205, 133, 63
    Pesto                  = 128, 117, 50
    PictonBlue             = 102, 153, 204
    PigmentGreen           = 0, 173, 67
    PigPink                = 255, 218, 233
    PineGreen              = 1, 121, 111
    PineTree               = 42, 47, 35
    Pink                   = 255, 192, 203
    PinkFlare              = 191, 175, 178
    PinkLace               = 240, 211, 220
    PinkSwan               = 179, 179, 179
    Plum                   = 221, 160, 221
    Pohutukawa             = 102, 12, 33
    PoloBlue               = 119, 158, 203
    Pompadour              = 129, 20, 83
    Portage                = 146, 161, 207
    PotPourri              = 241, 221, 207
    PottersClay            = 132, 86, 60
    PowderBlue             = 176, 224, 230
    Prim                   = 228, 196, 207
    PrussianBlue           = 0, 58, 108
    PsychedelicPurple      = 223, 0, 255
    Puce                   = 204, 136, 153
    Pueblo                 = 108, 46, 31
    PuertoRico             = 67, 179, 174
    Pumpkin                = 255, 99, 28
    Purple                 = 128, 0, 128
    PurpleMountainsMajesty = 150, 123, 182
    PurpleTaupe            = 93, 57, 84
    QuarterSpanishWhite    = 230, 224, 212
    Quartz                 = 220, 208, 255
    Quincy                 = 106, 84, 69
    RacingGreen            = 26, 36, 33
    RadicalRed             = 255, 32, 82
    Rajah                  = 251, 171, 96
    RawUmber               = 123, 63, 0
    RazzleDazzleRose       = 254, 78, 218
    Razzmatazz             = 215, 10, 83
    Red                    = 255, 0, 0
    RedBerry               = 132, 22, 23
    RedDamask              = 203, 109, 81
    RedOxide               = 99, 15, 15
    RedRobin               = 128, 64, 64
    RichBlue               = 84, 90, 167
    Riptide                = 141, 217, 204
    RobinsEggBlue          = 0, 204, 204
    RobRoy                 = 225, 169, 95
    RockSpray              = 171, 56, 31
    RomanCoffee            = 131, 105, 83
    RoseBud                = 246, 164, 148
    RoseBudCherry          = 135, 50, 96
    RoseTaupe              = 144, 93, 93
    RosyBrown              = 188, 143, 143
    Rouge                  = 176, 48, 96
    RoyalBlue              = 65, 105, 225
    RoyalHeath             = 168, 81, 110
    RoyalPurple            = 102, 51, 152
    Ruby                   = 215, 24, 104
    Russet                 = 128, 70, 27
    Rust                   = 192, 64, 0
    RusticRed              = 72, 6, 7
    Saddle                 = 99, 81, 71
    SaddleBrown            = 139, 69, 19
    SafetyOrange           = 255, 102, 0
    Saffron                = 244, 196, 48
    Sage                   = 143, 151, 121
    Sail                   = 161, 202, 241
    Salem                  = 0, 133, 67
    Salmon                 = 250, 128, 114
    SandyBeach             = 253, 213, 177
    SandyBrown             = 244, 164, 96
    Sangria                = 134, 1, 17
    SanguineBrown          = 115, 54, 53
    SanMarino              = 80, 114, 167
    SanteFe                = 175, 110, 77
    Sapphire               = 6, 42, 120
    Saratoga               = 84, 90, 44
    Scampi                 = 102, 102, 153
    Scarlet                = 255, 36, 0
    ScarletGum             = 67, 28, 83
    SchoolBusYellow        = 255, 216, 0
    Schooner               = 139, 134, 128
    ScreaminGreen          = 102, 255, 102
    Scrub                  = 59, 60, 54
    SeaBuckthorn           = 249, 146, 69
    SeaGreen               = 46, 139, 87
    Seagull                = 140, 190, 214
    SealBrown              = 61, 12, 2
    Seance                 = 96, 47, 107
    SeaPink                = 215, 131, 127
    SeaShell               = 255, 245, 238
    Selago                 = 250, 230, 250
    SelectiveYellow        = 242, 180, 0
    SemiSweetChocolate     = 107, 68, 35
    Sepia                  = 150, 90, 62
    Serenade               = 255, 233, 209
    Shadow                 = 133, 109, 77
    Shakespeare            = 114, 160, 193
    Shalimar               = 252, 255, 164
    Shamrock               = 68, 215, 168
    ShamrockGreen          = 0, 153, 102
    SherpaBlue             = 0, 75, 73
    SherwoodGreen          = 27, 77, 62
    Shilo                  = 222, 165, 164
    ShipCove               = 119, 139, 165
    Shocking               = 241, 156, 187
    ShockingPink           = 255, 29, 206
    ShuttleGrey            = 84, 98, 111
    Sidecar                = 238, 224, 177
    Sienna                 = 160, 82, 45
    Silk                   = 190, 164, 147
    Silver                 = 192, 192, 192
    SilverChalice          = 175, 177, 174
    SilverTree             = 102, 201, 146
    SkyBlue                = 135, 206, 235
    SlateBlue              = 106, 90, 205
    SlateGray              = 112, 128, 144
    SlateGrey              = 112, 128, 144
    Smalt                  = 0, 48, 143
    SmaltBlue              = 74, 100, 108
    Snow                   = 255, 250, 250
    SoftAmber              = 209, 190, 168
    Solitude               = 235, 236, 240
    Sorbus                 = 233, 105, 44
    Spectra                = 53, 101, 77
    SpicyMix               = 136, 101, 78
    Spray                  = 126, 212, 230
    SpringBud              = 150, 255, 0
    SpringGreen            = 0, 255, 127
    SpringSun              = 236, 235, 189
    SpunPearl              = 170, 169, 173
    Stack                  = 130, 142, 132
    SteelBlue              = 70, 130, 180
    Stiletto               = 137, 63, 69
    Strikemaster           = 145, 92, 131
    StTropaz               = 50, 82, 123
    Studio                 = 115, 79, 150
    Sulu                   = 201, 220, 135
    SummerSky              = 33, 171, 205
    Sun                    = 237, 135, 45
    Sundance               = 197, 179, 88
    Sunflower              = 228, 208, 10
    Sunglow                = 255, 204, 51
    SunsetOrange           = 253, 82, 64
    SurfieGreen            = 0, 116, 116
    Sushi                  = 111, 153, 64
    SuvaGrey               = 140, 140, 140
    Swamp                  = 35, 43, 43
    SweetCorn              = 253, 219, 109
    SweetPink              = 243, 153, 152
    Tacao                  = 236, 177, 118
    TahitiGold             = 235, 97, 35
    Tan                    = 210, 180, 140
    Tangaroa               = 0, 28, 61
    Tangerine              = 228, 132, 0
    TangerineYellow        = 253, 204, 13
    Tapestry               = 183, 110, 121
    Taupe                  = 72, 60, 50
    TaupeGrey              = 139, 133, 137
    TawnyPort              = 102, 66, 77
    TaxBreak               = 79, 102, 106
    TeaGreen               = 208, 240, 192
    Teak                   = 176, 141, 87
    Teal                   = 0, 128, 128
    TeaRose                = 255, 133, 207
    Temptress              = 60, 20, 33
    Tenne                  = 200, 101, 0
    TerraCotta             = 226, 114, 91
    Thistle                = 216, 191, 216
    TickleMePink           = 245, 111, 161
    Tidal                  = 232, 244, 140
    TitanWhite             = 214, 202, 221
    Toast                  = 165, 113, 100
    Tomato                 = 255, 99, 71
    TorchRed               = 255, 3, 62
    ToryBlue               = 54, 81, 148
    Tradewind              = 110, 174, 161
    TrendyPink             = 133, 96, 136
    TropicalRainForest     = 0, 127, 102
    TrueV                  = 139, 114, 190
    TulipTree              = 229, 183, 59
    Tumbleweed             = 222, 170, 136
    Turbo                  = 255, 195, 36
    TurkishRose            = 152, 119, 123
    Turquoise              = 64, 224, 208
    TurquoiseBlue          = 118, 215, 234
    Tuscany                = 175, 89, 62
    TwilightBlue           = 253, 255, 245
    Twine                  = 186, 135, 89
    TyrianPurple           = 102, 2, 60
    Ultramarine            = 10, 17, 149
    UltraPink              = 255, 111, 255
    Valencia               = 222, 82, 70
    VanCleef               = 84, 61, 55
    VanillaIce             = 229, 204, 201
    VenetianRed            = 209, 0, 28
    Venus                  = 138, 127, 128
    Vermilion              = 251, 79, 20
    VeryLightGrey          = 207, 207, 207
    VidaLoca               = 94, 140, 49
    Viking                 = 71, 171, 204
    Viola                  = 180, 131, 149
    ViolentViolet          = 50, 23, 77
    Violet                 = 238, 130, 238
    VioletRed              = 255, 57, 136
    Viridian               = 64, 130, 109
    VistaBlue              = 159, 226, 191
    VividViolet            = 127, 62, 152
    WaikawaGrey            = 83, 104, 149
    Wasabi                 = 150, 165, 60
    Watercourse            = 0, 106, 78
    Wedgewood              = 67, 107, 149
    WellRead               = 147, 61, 65
    Wewak                  = 255, 152, 153
    Wheat                  = 245, 222, 179
    Whiskey                = 217, 154, 108
    WhiskeySour            = 217, 144, 88
    White                  = 255, 255, 255
    WhiteSmoke             = 245, 245, 245
    WildRice               = 228, 217, 111
    WildSand               = 229, 228, 226
    WildStrawberry         = 252, 65, 154
    WildWatermelon         = 255, 84, 112
    WildWillow             = 172, 191, 96
    Windsor                = 76, 40, 130
    Wisteria               = 191, 148, 228
    Wistful                = 162, 162, 208
    Yellow                 = 255, 255, 0
    YellowGreen            = 154, 205, 50
    YellowOrange           = 255, 174, 66
    YourPink               = 244, 194, 194
}
function Convert-BinaryToIP {
    <#
    .SYNOPSIS
    Converts a binary string to an IP address format.
 
    .DESCRIPTION
    This function takes a binary string as input and converts it to an IP address format. The binary string must be evenly divisible by 8.
 
    .PARAMETER Binary
    The binary string to convert to an IP address format.
 
    .EXAMPLE
    Convert-BinaryToIP -Binary "01000001000000100000000100000001"
    Output: 65.0.1.1
    #>

    [cmdletBinding()]
    param(
        [string] $Binary
    )
    $Binary = $Binary -replace '\s+'
    if ($Binary.Length % 8) {
        Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8."
        return $Null
    }
    [int] $NumberOfBytes = $Binary.Length / 8
    $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) {
            try {

                [System.Convert]::ToByte($Binary.Substring(($i * 8), 8), 2)
            }
            catch {
                Write-Warning -Message "Convert-BinaryToIP - Error converting '$Binary' to bytes. `$i was $i."
                return $Null
            }
        })
    return $Bytes -join '.'
}
function Convert-GenericRightsToFileSystemRights {
    <#
    .SYNOPSIS
    Converts generic rights to file system rights for a given set of original rights.
 
    .DESCRIPTION
    This function maps generic rights to corresponding file system rights based on the provided original rights.
 
    .PARAMETER OriginalRights
    Specifies the original generic rights to be converted to file system rights.
 
    .EXAMPLE
    Convert-GenericRightsToFileSystemRights -OriginalRights GENERIC_READ
    Converts the generic read rights to file system rights.
 
    .NOTES
    This function is based on the mapping provided in the blog post: https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/
 
    .LINK
    https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/
    #>

    [cmdletBinding()]
    param(
        [System.Security.AccessControl.FileSystemRights] $OriginalRights
    )
    Begin {
        $FileSystemRights = [System.Security.AccessControl.FileSystemRights]
        $GenericRights = @{
            GENERIC_READ    = 0x80000000;
            GENERIC_WRITE   = 0x40000000;
            GENERIC_EXECUTE = 0x20000000;
            GENERIC_ALL     = 0x10000000;
            FILTER_GENERIC  = 0x0FFFFFFF;
        }
        $MappedGenericRights = @{
            FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize
            FILE_GENERIC_READ    = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_WRITE   = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize
            FILE_GENERIC_ALL     = $FileSystemRights::FullControl
        }
    }
    Process {
        $MappedRights = [System.Security.AccessControl.FileSystemRights]::new()
        if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) {
            $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE
        }
        if ($OriginalRights -band $GenericRights.GENERIC_READ) {
            $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ
        }
        if ($OriginalRights -band $GenericRights.GENERIC_WRITE) {
            $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE
        }
        if ($OriginalRights -band $GenericRights.GENERIC_ALL) {
            $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL
        }
        (($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights
    }
    End {
    }
}
function Convert-IPToBinary {
    <#
    .SYNOPSIS
    Converts an IPv4 address to binary format.
 
    .DESCRIPTION
    This function takes an IPv4 address as input and converts it to binary format.
 
    .PARAMETER IP
    Specifies the IPv4 address to convert to binary format.
 
    .EXAMPLE
    Convert-IPToBinary -IP "192.168.1.1"
    Converts the IPv4 address "192.168.1.1" to binary format.
 
    .EXAMPLE
    Convert-IPToBinary -IP "10.0.0.1"
    Converts the IPv4 address "10.0.0.1" to binary format.
 
    #>

    [cmdletBinding()]
    param(
        [string] $IP
    )
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'
    $IP = $IP.Trim()
    if ($IP -match "\A${IPv4Regex}\z") {
        try {
            return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join ''
        }
        catch {
            Write-Warning -Message "Convert-IPToBinary - Error converting '$IP' to a binary string: $_"
            return $Null
        }
    }
    else {
        Write-Warning -Message "Convert-IPToBinary - Invalid IP detected: '$IP'. Conversion failed."
        return $Null
    }
}
function ConvertFrom-HTML {
    <#
    .SYNOPSIS
    Converts HTML strings to plain text.
 
    .DESCRIPTION
    This function converts HTML strings to plain text. It can also remove HTML tags if specified.
 
    .PARAMETER HTML
    Specifies the HTML strings to convert.
 
    .PARAMETER RemoveTags
    Indicates whether to remove HTML tags from the input HTML strings.
 
    .EXAMPLE
    ConvertFrom-HTML -HTML "<p>Hello, <b>World</b>!</p>" -RemoveTags
    This example converts the HTML string "<p>Hello, <b>World</b>!</p>" to plain text and removes the HTML tags.
 
    #>

    [alias('Convert-HTMLToString')]
    [CmdletBinding()]
    param(
        [string[]] $HTML,
        [switch] $RemoveTags
    )
    foreach ($H in $HTML) {
        if ($RemoveTags) {

            $H = $H -replace '<[^>]+>', ''
        }

        $H -replace '&#8220;', '"' -replace '&#8217;', "'" -replace '&#8221;', '"' -replace '&#8230;', '...' -replace '&#8212;', '-' -replace '&#8211;', '-'
    }
}
function ConvertTo-HkeyUser {
    <#
    .SYNOPSIS
    Converts registry paths based on specified criteria.
 
    .DESCRIPTION
    This function converts registry paths based on the provided HiveDictionary, SubKeys, DictionaryKey, and RegistryPath parameters.
 
    .PARAMETER HiveDictionary
    Specifies the dictionary containing the criteria for converting registry paths.
 
    .PARAMETER SubKeys
    Specifies an array of subkeys to process.
 
    .PARAMETER DictionaryKey
    Specifies the key in the RegistryPath to be replaced.
 
    .PARAMETER RegistryPath
    Specifies the original registry path to be converted.
 
    .EXAMPLE
    ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'AllDomain'; 'Key2' = 'All+Default' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', '.DEFAULT') -DictionaryKey 'Key1' -RegistryPath 'HKLM:\Software\Key1\SubKey'
 
    Description:
    Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys.
 
    .EXAMPLE
    ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'Users'; 'Key2' = 'AllDomain+Other' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', 'Offline_User1') -DictionaryKey 'Key2' -RegistryPath 'HKLM:\Software\Key2\SubKey'
 
    Description:
    Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $HiveDictionary,
        [Array] $SubKeys,
        [string] $DictionaryKey,
        [string] $RegistryPath
    )
    $OutputRegistryKeys = foreach ($Sub in $Subkeys) {
        if ($HiveDictionary[$DictionaryKey] -eq 'All') {
            if ($Sub -notlike "*_Classes*" -and $Sub -ne '.DEFAULT') {
                $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'All+Default') {
            if ($Sub -notlike "*_Classes*") {
                if (-not $Script:DefaultRegistryMounted) {
                    $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath
                }
                if ($Sub -eq '.DEFAULT') {
                    $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER")
                }
                else {
                    $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
                }
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'Default') {
            if ($Sub -eq '.DEFAULT') {
                if (-not $Script:DefaultRegistryMounted) {
                    $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath
                }
                $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER")
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Default') {
            if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') {
                if (-not $Script:DefaultRegistryMounted) {
                    $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath
                }
                if ($Sub -eq '.DEFAULT') {
                    $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER")
                }
                else {
                    $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
                }
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other') {
            if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*")) {
                if (-not $Script:OfflineRegistryMounted) {
                    $Script:OfflineRegistryMounted = Mount-AllRegistryPath
                    foreach ($Key in $Script:OfflineRegistryMounted.Keys) {
                        $RegistryPath.Replace($DictionaryKey, "Users\$Key")
                    }
                }
                $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other+Default') {
            if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') {
                if (-not $Script:DefaultRegistryMounted) {
                    $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath
                }
                if (-not $Script:OfflineRegistryMounted) {
                    $Script:OfflineRegistryMounted = Mount-AllRegistryPath
                    foreach ($Key in $Script:OfflineRegistryMounted.Keys) {
                        $RegistryPath.Replace($DictionaryKey, "Users\$Key")
                    }
                }
                if ($Sub -eq '.DEFAULT') {
                    $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER")
                }
                else {
                    $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
                }
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain') {
            if ($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") {
                $RegistryPath.Replace($DictionaryKey, "Users\$Sub")
            }
        }
        elseif ($HiveDictionary[$DictionaryKey] -eq 'Users') {
            if ($Sub -like "Offline_*") {
                $Script:OfflineRegistryMounted = Mount-AllRegistryPath -MountUsers $Sub
                foreach ($Key in $Script:OfflineRegistryMounted.Keys) {
                    if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) {
                        $RegistryPath
                    }
                }
            }
        }
    }
    $OutputRegistryKeys | Sort-Object -Unique
}
function ConvertTo-StringByType {
    <#
    .SYNOPSIS
    Private function to use within ConvertTo-JsonLiteral
 
    .DESCRIPTION
    Private function to use within ConvertTo-JsonLiteral
 
    .PARAMETER Value
    Value to convert to JsonValue
 
     .PARAMETER Depth
    Specifies how many levels of contained objects are included in the JSON representation. The default value is 0.
 
    .PARAMETER AsArray
    Outputs the object in array brackets, even if the input is a single object.
 
    .PARAMETER DateTimeFormat
    Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss"
 
    .PARAMETER NumberAsString
    Provides an alternative serialization option that converts all numbers to their string representation.
 
    .PARAMETER BoolAsString
    Provides an alternative serialization option that converts all bool to their string representation.
 
    .PARAMETER PropertyName
    Uses PropertyNames provided by user (only works with Force)
 
    .PARAMETER ArrayJoin
    Forces any array to be a string regardless of depth level
 
    .PARAMETER ArrayJoinString
    Uses defined string or char for array join. By default it uses comma with a space when used.
 
    .PARAMETER Force
    Forces using property names from first object or given thru PropertyName parameter
 
    .EXAMPLE
    $Value = ConvertTo-StringByType -Value $($Object[$a][$i]) -DateTimeFormat $DateTimeFormat
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [Object] $Value,
        [int] $Depth,
        [int] $MaxDepth,
        [string] $DateTimeFormat,
        [switch] $NumberAsString,
        [switch] $BoolAsString,
        [System.Collections.IDictionary] $NewLineFormat = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $NewLineFormatProperty = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $AdvancedReplace,
        [System.Text.StringBuilder] $TextBuilder,
        [string[]] $PropertyName,
        [switch] $ArrayJoin,
        [string] $ArrayJoinString,
        [switch] $Force
    )
    Process {
        if ($null -eq $Value) {
            "`"`""
        }
        elseif ($Value -is [string]) {
            $Value = $Value.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)

            foreach ($Key in $AdvancedReplace.Keys) {
                $Value = $Value.Replace($Key, $AdvancedReplace[$Key])
            }
            "`"$Value`""
        }
        elseif ($Value -is [DateTime]) {
            "`"$($($Value).ToString($DateTimeFormat))`""
        }
        elseif ($Value -is [bool]) {
            if ($BoolAsString) {
                "`"$($Value)`""
            }
            else {
                $Value.ToString().ToLower()
            }
        }
        elseif ($Value -is [System.Collections.IDictionary]) {
            if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) {
                "`"$($Value)`""
            }
            else {
                $Depth++
                $null = $TextBuilder.AppendLine("{")
                for ($i = 0; $i -lt ($Value.Keys).Count; $i++) {
                    $Property = ([string[]]$Value.Keys)[$i]
                    $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $null = $TextBuilder.Append("`"$DisplayProperty`":")
                    $OutputValue = ConvertTo-StringByType -Value $Value[$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent
                    $null = $TextBuilder.Append("$OutputValue")
                    if ($i -ne ($Value.Keys).Count - 1) {
                        $null = $TextBuilder.AppendLine(',')
                    }
                }
                $null = $TextBuilder.Append("}")
            }
        }
        elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) {
            if ($ArrayJoin) {
                $Value = $Value -join $ArrayJoinString
                $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                "`"$Value`""
            }
            else {
                if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) {
                    $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    "`"$Value`""
                }
                else {
                    $CountInternalObjects = 0
                    $null = $TextBuilder.Append("[")
                    foreach ($V in $Value) {
                        $CountInternalObjects++
                        if ($CountInternalObjects -gt 1) {
                            $null = $TextBuilder.Append(',')
                        }
                        if ($Force -and -not $PropertyName) {
                            $PropertyName = $V.PSObject.Properties.Name
                        }
                        elseif ($Force -and $PropertyName) {
                        }
                        else {
                            $PropertyName = $V.PSObject.Properties.Name
                        }
                        $OutputValue = ConvertTo-StringByType -Value $V -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -PropertyName $PropertyName -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent
                        $null = $TextBuilder.Append($OutputValue)
                    }
                    $null = $TextBuilder.Append("]")
                }
            }
        }
        elseif ($Value -is [System.Enum]) {
            "`"$($($Value).ToString())`""
        }
        elseif (($Value | IsNumeric) -eq $true) {
            $Value = $($Value).ToString().Replace(',', '.')
            if ($NumberAsString) {
                "`"$Value`""
            }
            else {
                $Value
            }
        }
        elseif ($Value -is [PSObject]) {
            if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) {
                "`"$($Value)`""
            }
            else {
                $Depth++
                $CountInternalObjects = 0
                $null = $TextBuilder.AppendLine("{")
                if ($Force -and -not $PropertyName) {
                    $PropertyName = $Value.PSObject.Properties.Name
                }
                elseif ($Force -and $PropertyName) {
                }
                else {
                    $PropertyName = $Value.PSObject.Properties.Name
                }
                foreach ($Property in $PropertyName) {
                    $CountInternalObjects++
                    if ($CountInternalObjects -gt 1) {
                        $null = $TextBuilder.AppendLine(',')
                    }
                    $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $null = $TextBuilder.Append("`"$DisplayProperty`":")
                    $OutputValue = ConvertTo-StringByType -Value $Value.$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent
                    $null = $TextBuilder.Append("$OutputValue")
                }
                $null = $TextBuilder.Append("}")
            }
        }
        else {
            $Value = $Value.ToString().Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
            "`"$Value`""
        }
    }
}
function Get-ComputerSMBInfo {
    <#
    .SYNOPSIS
    Retrieves information about SMB shares on a remote computer.
 
    .DESCRIPTION
    This function retrieves information about SMB shares on a remote computer using the NetShareEnum method.
 
    .PARAMETER ComputerName
    Specifies the name of the remote computer to retrieve SMB share information from.
 
    .PARAMETER Name
    Specifies an array of share names to filter the results. If not specified, all shares will be retrieved.
 
    .PARAMETER SkipDiskSpace
    Indicates whether to skip retrieving disk space information for each share.
 
    .EXAMPLE
    Get-ComputerSMBInfo -ComputerName "Server01"
 
    Description:
    Retrieves all SMB share information from the remote computer "Server01".
 
    .EXAMPLE
    Get-ComputerSMBInfo -ComputerName "Server01" -Name "Data"
 
    Description:
    Retrieves SMB share information for the share named "Data" on the remote computer "Server01".
 
    .NOTES
    File Name : Get-ComputerSMBInfo.ps1
    Prerequisite : This function requires administrative privileges on the remote computer.
    #>

    [cmdletbinding()]
    param(
        [string] $ComputerName,
        [string[]] $Name,
        [switch] $SkipDiskSpace #,
        # [System.Management.Automation.PSCmdlet]$PSC
    )
    $buffer = [IntPtr]::Zero
    $read = 0
    $total = 0
    $resume = 0

    $res = [Win32Share.NativeMethods]::NetShareEnum(
        $ComputerName,
        1, 
        [ref]$buffer,
        ([UInt32]"0xFFFFFFFF"), 
        [ref]$read,
        [ref]$total,
        [ref]$resume
    )

    if ($res -ne 0) {
        $exp = [System.ComponentModel.Win32Exception]$res
        $er = [System.Management.Automation.ErrorRecord]::new(
            $exp,
            'Win32Share.NativeMethods.GetSmbInf.RemoteException',
            [System.Management.Automation.ErrorCategory]::NotSpecified,
            $ComputerName
        )
        $er.ErrorDetails = "Failed to enum share for '$ComputerName': $($exp.Message)"
        if ($ErrorActionPreference -eq 'Stop') {
            Write-Error -ErrorRecord $er
        }
        else {
            Write-Warning -Message "Get-ComputerSMBShareList - Failed to enumarate share on '$ComputerName'."
        }
        return
    }
    try {
        $entryPtr = $buffer
        for ($i = 0; $i -lt $total; $i++) {
            $shareInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($entryPtr, [Type]([Win32Share.NativeHelpers+SHARE_INFO_1]))

            $netNm = $shareInfo.shi1_netname
            if ($Name) {
                $isLike = $false
                foreach ($nm in $Name) {
                    if ($netNm -like $nm) {
                        $isLike = $true
                        continue
                    }
                }
                if (-not $isLike) {
                    $entryPtr = [IntPtr]::Add($entryPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($shareInfo))
                    continue
                }
            }
            $shTyp = $shareInfo.shi1_type

            $shrPath = "\\$ComputerName\$netNm\"
            if (-not $SkipDiskSpace) {
                $freeBytesAvailableToCaller = 0
                [System.Nullable[UInt64]]$freeBytesAvailableToCallerNull = $null
                $totalNumberOfBytes = 0
                [System.Nullable[UInt64]]$totalNumberOfBytesNull = $null
                $totalNumberOfFreeBytes = 0
                [System.Nullable[UInt64]]$totalNumberOfFreeBytesNull = $null
                $lastWin32Error = 0

                if (($shTyp -bor [Win32Share.ShareType]::Disk) -eq [Win32Share.ShareType]::Disk) {
                    $dskRes = [Win32Share.NativeMethods]::GetDiskFreeSpaceEx(
                        $shrPath,
                        [ref]$freeBytesAvailableToCaller,
                        [ref]$totalNumberOfBytes,
                        [ref]$totalNumberOfFreeBytes
                    )
                    if ($dskRes) {
                        $freeBytesAvailableToCallerNull = $freeBytesAvailableToCaller
                        $totalNumberOfBytesNull = $totalNumberOfBytes
                        $totalNumberOfFreeBytesNull = $totalNumberOfFreeBytes
                    }
                    else {

                        $lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
                        $exp = [System.ComponentModel.Win32Exception]$lastWin32Error
                        $er = [System.Management.Automation.ErrorRecord]::new(
                            $exp,
                            'Win32Share.NativeMethods.GetSmbInf.ShareException',
                            [System.Management.Automation.ErrorCategory]::NotSpecified,
                            $shrPath
                        )
                        $er.ErrorDetails = "Failed to get disk space on '$shrPath' for '$ComputerName': $($exp.Message)"

                        if ($ErrorActionPreference -eq 'Stop') {
                            Write-Error -ErrorRecord $er
                        }
                        else {
                            Write-Warning -Message "Get-ComputerSMBShareList - Failed to get disk space on '$shrPath' for '$ComputerName': $($exp.Message)"
                        }
                    }
                }
                [PSCustomObject]@{
                    PSTypeName               = 'Win32Share.NativeMethods' 
                    ComputerName             = $ComputerName
                    Path                     = $shrPath
                    Name                     = $netNm
                    Type                     = $shTyp
                    Remark                   = $shareInfo.shi1_remark
                    TotalBytes               = $totalNumberOfBytesNull
                    TotalFreeBytes           = $totalNumberOfFreeBytesNull
                    FreeBytesAvailableToUser = $freeBytesAvailableToCallerNull
                }
            }
            else {
                [PSCustomObject]@{
                    PSTypeName   = 'Win32Share.NativeMethods' 
                    ComputerName = $ComputerName
                    Path         = $shrPath
                    Name         = $netNm
                    Type         = $shTyp
                    Remark       = $shareInfo.shi1_remark
                }
            }

            $entryPtr = [IntPtr]::Add($entryPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($shareInfo))
        }
    }
    finally {
        $null = [Win32Share.NativeMethods]::NetApiBufferFree($buffer)
    }
}
function Get-ComputerSplit {
    <#
    .SYNOPSIS
    This function splits the list of computer names provided into two arrays: one containing remote computers and another containing the local computer.
 
    .DESCRIPTION
    The Get-ComputerSplit function takes an array of computer names as input and splits them into two arrays based on whether they are remote computers or the local computer. It determines the local computer by comparing the provided computer names with the local computer name and DNS name.
 
    .PARAMETER ComputerName
    Specifies an array of computer names to split into remote and local computers.
 
    .EXAMPLE
    Get-ComputerSplit -ComputerName "Computer1", "Computer2", $Env:COMPUTERNAME
    This example splits the computer names "Computer1" and "Computer2" into the remote computers array and the local computer array based on the local computer's name.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName
    )
    if ($null -eq $ComputerName) {
        $ComputerName = $Env:COMPUTERNAME
    }
    try {
        $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName
    }
    catch {
        $LocalComputerDNSName = $Env:COMPUTERNAME
    }
    $ComputersLocal = $null
    [Array] $Computers = foreach ($Computer in $ComputerName) {
        if ($Computer -eq '' -or $null -eq $Computer) {
            $Computer = $Env:COMPUTERNAME
        }
        if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) {
            $Computer
        }
        else {
            $ComputersLocal = $Computer
        }
    }
    , @($ComputersLocal, $Computers)
}
function Get-IPRange {
    <#
    .SYNOPSIS
    Generates a list of IP addresses within a specified binary range.
 
    .DESCRIPTION
    This function takes two binary strings representing the start and end IP addresses and generates a list of IP addresses within that range.
 
    .PARAMETER StartBinary
    Specifies the starting IP address in binary format.
 
    .PARAMETER EndBinary
    Specifies the ending IP address in binary format.
 
    .EXAMPLE
    Get-IPRange -StartBinary '11000000' -EndBinary '11000010'
    Description:
    Generates a list of IP addresses between '192.0.0.0' and '192.0.2.0'.
 
    .EXAMPLE
    Get-IPRange -StartBinary '10101010' -EndBinary '10101100'
    Description:
    Generates a list of IP addresses between '170.0.0.0' and '172.0.0.0'.
    #>

    [cmdletBinding()]
    param(
        [string] $StartBinary,
        [string] $EndBinary
    )
    [int64] $StartInt = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt = [System.Convert]::ToInt64($EndBinary, 2)
    for ($BinaryIP = $StartInt; $BinaryIP -le $EndInt; $BinaryIP++) {
        Convert-BinaryToIP ([System.Convert]::ToString($BinaryIP, 2).PadLeft(32, '0'))
    }
}
function Get-LocalComputerSid {
    <#
    .SYNOPSIS
    Get the SID of the local computer.
 
    .DESCRIPTION
    Get the SID of the local computer.
 
    .EXAMPLE
    Get-LocalComputerSid
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param()
    try {
        Add-Type -AssemblyName System.DirectoryServices.AccountManagement
        $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Machine)
        $UserPrincipal = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($PrincipalContext)
        $Searcher = [System.DirectoryServices.AccountManagement.PrincipalSearcher]::new()
        $Searcher.QueryFilter = $UserPrincipal
        $User = $Searcher.FindAll()
        foreach ($U in $User) {
            if ($U.Sid.Value -like "*-500") {
                return $U.Sid.Value.TrimEnd("-500")
            }
        }
    }
    catch {
        Write-Warning -Message "Get-LocalComputerSid - Error: $($_.Exception.Message)"
    }
}
function Get-OfflineRegistryProfilesPath {
    <#
    .SYNOPSIS
    Retrieves the paths of offline user profiles in the Windows registry.
 
    .DESCRIPTION
    This function retrieves the paths of offline user profiles in the Windows registry by comparing the profiles listed in 'HKEY_USERS' with those in 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'. It then checks for the existence of the 'NTUSER.DAT' file for each profile and returns the paths of offline profiles found.
 
    .EXAMPLE
    Get-OfflineRegistryProfilesPath
    Retrieves the paths of offline user profiles in the Windows registry and returns a hashtable containing the profile paths.
 
    .NOTES
    Name Value
    ---- -----
    Przemek {[FilePath, C:\Users\Przemek\NTUSER.DAT], [Status, ]}
    test.1 {[FilePath, C:\Users\test.1\NTUSER.DAT], [Status, ]}
 
    #>

    [CmdletBinding()]
    param(

    )
    $Profiles = [ordered] @{}
    $CurrentMapping = (Get-PSRegistry -RegistryPath 'HKEY_USERS' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys
    $UsersInSystem = (Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys
    $MissingProfiles = foreach ($Profile in $UsersInSystem) {
        if ($Profile.StartsWith("S-1-5-21") -and $CurrentMapping -notcontains $Profile) {
            Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$Profile" -ExpandEnvironmentNames -DoNotUnmount
        }
    }
    foreach ($Profile in $MissingProfiles) {
        $PathToNTUser = [io.path]::Combine($Profile.ProfileImagePath, 'NTUSER.DAT')
        $ProfileName = [io.path]::GetFileName($Profile.ProfileImagePath)
        $StartPath = "Offline_$ProfileName"
        try {
            $PathExists = Test-Path -LiteralPath $PathToNTUser -ErrorAction Stop
            if ($PathExists) {
                $Profiles[$StartPath] = [ordered] @{
                    FilePath = $PathToNTUser
                    Status   = $null
                }
            }
        }
        catch {
            Write-Warning -Message "Mount-OfflineRegistryPath - Couldn't execute. Error: $($_.Exception.Message)"
            continue
        }
    }
    $Profiles
}
function Get-PrivateRegistryTranslated {
    <#
    .SYNOPSIS
    Retrieves translated private registry information based on the provided parameters.
 
    .DESCRIPTION
    This function retrieves translated private registry information based on the specified RegistryPath, HiveDictionary, ReverseTypesDictionary, Type, Key, and Value parameters.
 
    .PARAMETER RegistryPath
    Specifies the array of registry paths to be translated.
 
    .PARAMETER HiveDictionary
    Specifies the dictionary containing mappings of registry hives.
 
    .PARAMETER ReverseTypesDictionary
    Specifies the dictionary containing mappings of registry value types.
 
    .PARAMETER Type
    Specifies the type of the registry value. Valid values are 'REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring'.
 
    .PARAMETER Key
    Specifies the key associated with the registry value.
 
    .PARAMETER Value
    Specifies the value of the registry key.
 
    .EXAMPLE
    Get-PrivateRegistryTranslated -RegistryPath "HKLM\Software\Microsoft" -HiveDictionary @{"HKLM"="HKEY_LOCAL_MACHINE"} -ReverseTypesDictionary @{"string"="REG_SZ"} -Type "string" -Key "Version" -Value "10.0.19041"
 
    Description
    -----------
    Retrieves translated registry information for the specified registry path.
 
    .EXAMPLE
    Get-PrivateRegistryTranslated -RegistryPath "HKCU\Software\Settings" -HiveDictionary @{"HKCU"="HKEY_CURRENT_USER"} -ReverseTypesDictionary @{"dword"="REG_DWORD"} -Type "dword" -Key "SettingA" -Value 1
 
    Description
    -----------
    Retrieves translated registry information for the specified registry path.
 
    #>

    [cmdletBinding()]
    param(
        [Array] $RegistryPath,
        [System.Collections.IDictionary] $HiveDictionary,
        [System.Collections.IDictionary] $ReverseTypesDictionary,
        [Parameter()][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type,
        [Parameter()][string] $Key,
        [Parameter()][object] $Value
    )
    foreach ($Registry in $RegistryPath) {

        if ($Registry -is [string]) {
            $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\")
        }
        else {
            $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\")
        }
        foreach ($Hive in $HiveDictionary.Keys) {
            if ($Registry -is [string] -and $Registry.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                if ($Hive.Length -eq $Registry.Length) {
                    [ordered] @{
                        HiveKey    = $HiveDictionary[$Hive]
                        SubKeyName = $null
                        ValueKind  = if ($Type) {
                            [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) 
                        }
                        else {
                            $null 
                        }
                        Key        = $Key
                        Value      = $Value
                    }
                }
                else {
                    [ordered] @{
                        HiveKey    = $HiveDictionary[$Hive]
                        SubKeyName = $Registry.substring($Hive.Length + 1)
                        ValueKind  = if ($Type) {
                            [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) 
                        }
                        else {
                            $null 
                        }
                        Key        = $Key
                        Value      = $Value
                    }
                }
                break
            }
            elseif ($Registry -isnot [string] -and $Registry.RegistryPath.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) {
                if ($Hive.Length -eq $Registry.RegistryPath.Length) {
                    [ordered] @{
                        ComputerName = $Registry.ComputerName
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $null
                        ValueKind    = if ($Type) {
                            [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) 
                        }
                        else {
                            $null 
                        }
                        Key          = $Key
                        Value        = $Value
                    }
                }
                else {
                    [ordered] @{
                        ComputerName = $Registry.ComputerName
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $Registry.RegistryPath.substring($Hive.Length + 1)
                        ValueKind    = if ($Type) {
                            [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) 
                        }
                        else {
                            $null 
                        }
                        Key          = $Key
                        Value        = $Value
                    }
                }
                break
            }
        }
    }
}
function Get-PSConvertSpecialRegistry {
    <#
    .SYNOPSIS
    Converts special registry paths for specified computers.
 
    .DESCRIPTION
    This function converts special registry paths for the specified computers using the provided HiveDictionary.
 
    .PARAMETER RegistryPath
    Specifies the array of registry paths to convert.
 
    .PARAMETER Computers
    Specifies the array of computers to convert registry paths for.
 
    .PARAMETER HiveDictionary
    Specifies the dictionary containing hive keys and their corresponding values.
 
    .PARAMETER ExpandEnvironmentNames
    Indicates whether to expand environment names in the registry paths.
 
    .EXAMPLE
    Get-PSConvertSpecialRegistry -RegistryPath "Users\Offline_Przemek\Software\Policies1\Microsoft\Windows\CloudContent" -Computers "Computer1", "Computer2" -HiveDictionary $HiveDictionary -ExpandEnvironmentNames
 
    Converts the specified registry path for the specified computers using the provided HiveDictionary.
 
    #>

    [cmdletbinding()]
    param(
        [Array] $RegistryPath,
        [Array] $Computers,
        [System.Collections.IDictionary] $HiveDictionary,
        [switch] $ExpandEnvironmentNames
    )
    $FixedPath = foreach ($R in $RegistryPath) {
        foreach ($DictionaryKey in $HiveDictionary.Keys) {
            $SplitParts = $R.Split("\")
            $FirstPart = $SplitParts[0]
            if ($FirstPart -eq $DictionaryKey) {

                if ($HiveDictionary[$DictionaryKey] -in 'All', 'All+Default', 'Default', 'AllDomain+Default', 'AllDomain', 'AllDomain+Other', 'AllDomain+Other+Default') {
                    foreach ($Computer in $Computers) {
                        $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount
                        if ($SubKeys.PSSubKeys) {
                            $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R
                            foreach ($S in $RegistryKeys) {
                                [PSCustomObject] @{
                                    ComputerName = $Computer
                                    RegistryPath = $S
                                    Error        = $null
                                    ErrorMessage = $null
                                }
                            }
                        }
                        else {
                            [PSCustomObject] @{
                                ComputerName = $Computer
                                RegistryPath = $R
                                Error        = $true
                                ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS"
                            }
                        }
                    }
                }
                elseif ($FirstPart -in 'Users', 'HKEY_USERS', 'HKU' -and $SplitParts[1] -and $SplitParts[1] -like "Offline_*") {

                    foreach ($Computer in $Computers) {
                        $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount
                        if ($SubKeys.PSSubKeys) {
                            $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys + $SplitParts[1] | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R
                            foreach ($S in $RegistryKeys) {
                                [PSCustomObject] @{
                                    ComputerName = $Computer
                                    RegistryPath = $S
                                    Error        = $null
                                    ErrorMessage = $null
                                }
                            }
                        }
                        else {
                            [PSCustomObject] @{
                                ComputerName = $Computer
                                RegistryPath = $R
                                Error        = $true
                                ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS"
                            }
                        }
                    }
                }
                else {
                    $R
                }
                break
            }
        }
    }
    $FixedPath
}
function Get-PSSubRegistry {
    <#
    .SYNOPSIS
    Retrieves a subkey from the Windows Registry on a local or remote computer.
 
    .DESCRIPTION
    The Get-PSSubRegistry function retrieves a subkey from the Windows Registry on a local or remote computer. It can be used to access specific registry keys and their values.
 
    .PARAMETER Registry
    Specifies the registry key to retrieve. This parameter should be an IDictionary object containing information about the registry key.
 
    .PARAMETER ComputerName
    Specifies the name of the computer from which to retrieve the registry key. This parameter is optional and defaults to the local computer.
 
    .PARAMETER Remote
    Indicates that the registry key should be retrieved from a remote computer.
 
    .PARAMETER ExpandEnvironmentNames
    Indicates whether environment variable names in the registry key should be expanded.
 
    .EXAMPLE
    Get-PSSubRegistry -Registry $Registry -ComputerName "RemoteComputer" -Remote
    Retrieves a subkey from the Windows Registry on a remote computer named "RemoteComputer".
 
    .EXAMPLE
    Get-PSSubRegistry -Registry $Registry -ExpandEnvironmentNames
    Retrieves a subkey from the Windows Registry on the local computer with expanded environment variable names.
 
    #>

    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $Registry,
        [string] $ComputerName,
        [switch] $Remote,
        [switch] $ExpandEnvironmentNames
    )
    if ($Registry.ComputerName) {
        if ($Registry.ComputerName -ne $ComputerName) {
            return
        }
    }
    if (-not $Registry.Error) {
        try {
            if ($Remote) {
                $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 )
            }
            else {
                $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 )
            }
            $PSConnection = $true
            $PSError = $null
        }
        catch {
            $PSConnection = $false
            $PSError = $($_.Exception.Message)
        }
    }
    else {

        $PSConnection = $false
        $PSError = $($Registry.ErrorMessage)
    }
    if ($PSError) {
        [PSCustomObject] @{
            PSComputerName = $ComputerName
            PSConnection   = $PSConnection
            PSError        = $true
            PSErrorMessage = $PSError
            PSPath         = $Registry.Registry
            PSKey          = $Registry.Key
            PSValue        = $null
            PSType         = $null
        }
    }
    else {
        try {
            $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false)
            if ($null -ne $SubKey) {
                [PSCustomObject] @{
                    PSComputerName = $ComputerName
                    PSConnection   = $PSConnection
                    PSError        = $false
                    PSErrorMessage = $null
                    PSPath         = $Registry.Registry
                    PSKey          = $Registry.Key
                    PSValue        = if (-not $ExpandEnvironmentNames) {
                        $SubKey.GetValue($Registry.Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
                    }
                    else {
                        $SubKey.GetValue($Registry.Key)
                    }
                    PSType         = $SubKey.GetValueKind($Registry.Key)
                }
            }
            else {
                [PSCustomObject] @{
                    PSComputerName = $ComputerName
                    PSConnection   = $PSConnection
                    PSError        = $true
                    PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists."
                    PSPath         = $Registry.Registry
                    PSKey          = $Registry.Key
                    PSValue        = $null
                    PSType         = $null
                }
            }
        }
        catch {
            [PSCustomObject] @{
                PSComputerName = $ComputerName
                PSConnection   = $PSConnection
                PSError        = $true
                PSErrorMessage = $_.Exception.Message
                PSPath         = $Registry.Registry
                PSKey          = $Registry.Key
                PSValue        = $null
                PSType         = $null
            }
        }
    }
    if ($null -ne $SubKey) {
        $SubKey.Close()
        $SubKey.Dispose()
    }
    if ($null -ne $BaseHive) {
        $BaseHive.Close()
        $BaseHive.Dispose()
    }
}
function Get-PSSubRegistryComplete {
    <#
    .SYNOPSIS
    Retrieves sub-registry information from a specified registry key.
 
    .DESCRIPTION
    This function retrieves sub-registry information from a specified registry key on a local or remote computer.
 
    .PARAMETER Registry
    Specifies the registry key information to retrieve.
 
    .PARAMETER ComputerName
    Specifies the name of the computer from which to retrieve the registry information.
 
    .PARAMETER Remote
    Indicates whether the registry key is located on a remote computer.
 
    .PARAMETER Advanced
    Indicates whether to retrieve advanced registry information.
 
    .PARAMETER ExpandEnvironmentNames
    Indicates whether to expand environment variable names in the registry.
 
    .EXAMPLE
    Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer01" -Remote -Advanced
    Retrieves advanced sub-registry information from the specified registry key on a remote computer named "Computer01".
 
    .EXAMPLE
    Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer02"
    Retrieves sub-registry information from the specified registry key on a local computer named "Computer02".
    #>

    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $Registry,
        [string] $ComputerName,
        [switch] $Remote,
        [switch] $Advanced,
        [switch] $ExpandEnvironmentNames
    )
    if ($Registry.ComputerName) {
        if ($Registry.ComputerName -ne $ComputerName) {
            return
        }
    }
    if (-not $Registry.Error) {
        try {
            if ($Remote) {
                $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 )
            }
            else {
                $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 )
            }
            $PSConnection = $true
            $PSError = $null
        }
        catch {
            $PSConnection = $false
            $PSError = $($_.Exception.Message)
        }
    }
    else {

        $PSConnection = $false
        $PSError = $($Registry.ErrorMessage)
    }
    if ($PSError) {
        [PSCustomObject] @{
            PSComputerName = $ComputerName
            PSConnection   = $PSConnection
            PSError        = $true
            PSErrorMessage = $PSError
            PSSubKeys      = $null
            PSPath         = $Registry.Registry
            PSKey          = $Registry.Key
        }
    }
    else {
        try {
            $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false)
            if ($null -ne $SubKey) {
                $Object = [ordered] @{
                    PSComputerName = $ComputerName
                    PSConnection   = $PSConnection
                    PSError        = $false
                    PSErrorMessage = $null
                    PSSubKeys      = $SubKey.GetSubKeyNames()
                    PSPath         = $Registry.Registry
                }
                $Keys = $SubKey.GetValueNames()
                foreach ($K in $Keys) {
                    if ($K -eq "") {
                        if ($Advanced) {
                            $Object['DefaultKey'] = [ordered] @{
                                Value = if (-not $ExpandEnvironmentNames) {
                                    $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
                                }
                                else {
                                    $SubKey.GetValue($K)
                                }
                                Type  = $SubKey.GetValueKind($K)
                            }
                        }
                        else {
                            $Object['DefaultKey'] = $SubKey.GetValue($K)
                        }
                    }
                    else {
                        if ($Advanced) {
                            $Object[$K] = [ordered] @{
                                Value = if (-not $ExpandEnvironmentNames) {
                                    $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
                                }
                                else {
                                    $SubKey.GetValue($K)
                                }
                                Type  = $SubKey.GetValueKind($K)
                            }
                        }
                        else {
                            $Object[$K] = if (-not $ExpandEnvironmentNames) {
                                $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
                            }
                            else {
                                $SubKey.GetValue($K)
                            }
                        }
                    }
                }
                [PSCustomObject] $Object
            }
            else {
                [PSCustomObject] @{
                    PSComputerName = $ComputerName
                    PSConnection   = $PSConnection
                    PSError        = $true
                    PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists."
                    PSSubKeys      = $null
                    PSPath         = $Registry.Registry
                }
            }
        }
        catch {
            [PSCustomObject] @{
                PSComputerName = $ComputerName
                PSConnection   = $PSConnection
                PSError        = $true
                PSErrorMessage = $_.Exception.Message
                PSSubKeys      = $null
                PSPath         = $Registry.Registry
            }
        }
    }
    if ($null -ne $SubKey) {
        $SubKey.Close()
        $SubKey.Dispose()
    }
    if ($null -ne $BaseHive) {
        $BaseHive.Close()
        $BaseHive.Dispose()
    }
}
function Get-PSSubRegistryTranslated {
    <#
    .SYNOPSIS
    Retrieves the translated sub-registry information based on the provided RegistryPath, HiveDictionary, and Key.
 
    .DESCRIPTION
    This function retrieves the translated sub-registry information by matching the RegistryPath with the HiveDictionary. It returns an ordered hashtable with details such as Registry, HiveKey, SubKeyName, Key, Error, and ErrorMessage.
 
    .PARAMETER RegistryPath
    Specifies an array of registry paths to be translated.
 
    .PARAMETER HiveDictionary
    Specifies a dictionary containing mappings of hive names to their corresponding keys.
 
    .PARAMETER Key
    Specifies a string key to be included in the output.
 
    .EXAMPLE
    Get-PSSubRegistryTranslated -RegistryPath "HKLM\Software\Microsoft" -HiveDictionary @{ "HKLM" = "HKEY_LOCAL_MACHINE" } -Key "Version"
    Retrieves the translated sub-registry information for the specified registry path under HKEY_LOCAL_MACHINE hive with the key "Version".
 
    .EXAMPLE
    Get-PSSubRegistryTranslated -RegistryPath "HKCU\Software\Microsoft" -HiveDictionary @{ "HKCU" = "HKEY_CURRENT_USER" }
    Retrieves the translated sub-registry information for the specified registry path under HKEY_CURRENT_USER hive without specifying a key.
 
    #>

    [cmdletBinding()]
    param(
        [Array] $RegistryPath,
        [System.Collections.IDictionary] $HiveDictionary,
        [string] $Key
    )
    foreach ($Registry in $RegistryPath) {

        if ($Registry -is [string]) {
            $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\")
            $FirstPartSplit = $Registry -split "\\"
            $FirstPart = $FirstPartSplit[0]
        }
        else {
            $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\")
            $FirstPartSplit = $Registry.RegistryPath -split "\\"
            $FirstPart = $FirstPartSplit[0]
        }

        foreach ($Hive in $HiveDictionary.Keys) {
            if ($Registry -is [string] -and $FirstPart -eq $Hive) {

                if ($Hive.Length -eq $Registry.Length) {
                    [ordered] @{
                        Registry     = $Registry
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $null
                        Key          = if ($Key -eq "") {
                            $null 
                        }
                        else {
                            $Key 
                        }
                        Error        = $null
                        ErrorMessage = $null
                    }
                }
                else {
                    [ordered] @{
                        Registry     = $Registry
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $Registry.substring($Hive.Length + 1)
                        Key          = if ($Key -eq "") {
                            $null 
                        }
                        else {
                            $Key 
                        }
                        Error        = $null
                        ErrorMessage = $null
                    }
                }
                break
            }
            elseif ($Registry -isnot [string] -and $FirstPart -eq $Hive) {

                if ($Hive.Length -eq $Registry.RegistryPath.Length) {
                    [ordered] @{
                        ComputerName = $Registry.ComputerName
                        Registry     = $Registry.RegistryPath
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $null
                        Key          = if ($Key -eq "") {
                            $null 
                        }
                        else {
                            $Key 
                        }
                        Error        = $Registry.Error
                        ErrorMessage = $Registry.ErrorMessage
                    }
                }
                else {
                    [ordered] @{
                        ComputerName = $Registry.ComputerName
                        Registry     = $Registry.RegistryPath
                        HiveKey      = $HiveDictionary[$Hive]
                        SubKeyName   = $Registry.RegistryPath.substring($Hive.Length + 1)
                        Key          = if ($Key -eq "") {
                            $null 
                        }
                        else {
                            $Key 
                        }
                        Error        = $Registry.Error
                        ErrorMessage = $Registry.ErrorMessage
                    }
                }
                break
            }
        }
    }
}
function Mount-AllRegistryPath {
    <#
    .SYNOPSIS
    Mounts offline registry paths to specified mount points.
 
    .DESCRIPTION
    This function mounts offline registry paths to specified mount points. It iterates through all offline registry profiles and mounts them to the specified mount point. Optionally, you can specify a specific user profile to mount.
 
    .PARAMETER MountPoint
    Specifies the mount point where the registry paths will be mounted. Default is "HKEY_USERS\".
 
    .PARAMETER MountUsers
    Specifies the user profile to mount. If specified, only the specified user profile will be mounted.
 
    .EXAMPLE
    Mount-AllRegistryPath -MountPoint "HKEY_USERS\" -MountUsers "User1"
    Mounts the offline registry path of user profile "User1" to the default mount point "HKEY_USERS\".
 
    .EXAMPLE
    Mount-AllRegistryPath -MountPoint "HKEY_LOCAL_MACHINE\SOFTWARE" -MountUsers "User2"
    Mounts the offline registry path of user profile "User2" to the specified mount point "HKEY_LOCAL_MACHINE\SOFTWARE".
 
    #>

    [CmdletBinding()]
    param(
        [string] $MountPoint = "HKEY_USERS\",
        [string] $MountUsers
    )
    $AllProfiles = Get-OfflineRegistryProfilesPath
    foreach ($Profile in $AllProfiles.Keys) {
        if ($MountUsers) {
            if ($MountUsers -ne $Profile) {
                continue
            }
        }
        $WhereMount = "$MountPoint\$Profile".Replace("\\", "\")
        Write-Verbose -Message "Mount-OfflineRegistryPath - Mounting $WhereMount to $($AllProfiles[$Profile].FilePath)"
        $AllProfiles[$Profile].Status = Mount-PSRegistryPath -MountPoint $WhereMount -FilePath $AllProfiles[$Profile].FilePath
    }
    $AllProfiles
}
function Mount-DefaultRegistryPath {
    <#
    .SYNOPSIS
    Mounts the default registry path to a specified mount point.
 
    .DESCRIPTION
    This function mounts the default registry path to a specified mount point. If an error occurs during the process, it provides appropriate feedback.
 
    .PARAMETER MountPoint
    Specifies the mount point where the default registry path will be mounted. Default value is "HKEY_USERS\.DEFAULT_USER".
 
    .EXAMPLE
    Mount-DefaultRegistryPath -MountPoint "HKLM:\Software\CustomMountPoint"
    Mounts the default registry path to the specified custom mount point "HKLM:\Software\CustomMountPoint".
 
    .EXAMPLE
    Mount-DefaultRegistryPath
    Mounts the default registry path to the default mount point "HKEY_USERS\.DEFAULT_USER".
 
    #>

    [CmdletBinding()]
    param(
        [string] $MountPoint = "HKEY_USERS\.DEFAULT_USER"
    )
    $DefaultRegistryPath = Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Key 'Default' -ExpandEnvironmentNames -DoNotUnmount
    if ($PSError -ne $true) {
        $PathToNTUser = [io.path]::Combine($DefaultRegistryPath.PSValue, 'NTUSER.DAT')
        Write-Verbose -Message "Mount-DefaultRegistryPath - Mounting $MountPoint to $PathToNTUser"
        Mount-PSRegistryPath -MountPoint $MountPoint -FilePath $PathToNTUser
    }
    else {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw $PSErrorMessage
        }
        else {
            Write-Warning -Message "Mount-DefaultRegistryPath - Couldn't execute. Error: $PSErrorMessage"
        }
    }
}
function New-PrivateRegistry {
    <#
    .SYNOPSIS
    Creates or updates a private registry key on a local or remote computer.
 
    .DESCRIPTION
    The New-PrivateRegistry function creates or updates a registry key on a specified computer. It can be used to set registry values for a specific hive key, subkey, value name, value data, and value kind.
 
    .PARAMETER RegistryValue
    Specifies the registry value to be set. This should be an IDictionary object containing the hive key, subkey, value name, value data, and value kind.
 
    .PARAMETER Computer
    Specifies the name of the computer where the registry key will be created or updated.
 
    .PARAMETER Remote
    Indicates whether the registry operation should be performed on a remote computer.
 
    .EXAMPLE
    New-PrivateRegistry -RegistryValue @{ HiveKey = 'LocalMachine'; SubKeyName = 'Software\MyApp'; Value = 'Version'; ValueData = '1.0'; ValueKind = 'String' } -Computer 'Server01'
 
    Description:
    Creates a registry key 'Version' with value '1.0' under 'LocalMachine\Software\MyApp' on the local computer 'Server01'.
 
    .EXAMPLE
    New-PrivateRegistry -RegistryValue @{ HiveKey = 'CurrentUser'; SubKeyName = 'Control Panel\Desktop'; Value = 'Wallpaper'; ValueData = 'C:\Wallpapers\image.jpg'; ValueKind = 'String' } -Computer 'Workstation01' -Remote
 
    Description:
    Creates a registry key 'Wallpaper' with value 'C:\Wallpapers\image.jpg' under 'CurrentUser\Control Panel\Desktop' on the remote computer 'Workstation01'.
 
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [System.Collections.IDictionary] $RegistryValue,
        [string] $Computer,
        [switch] $Remote
    )
    try {
        if ($Remote) {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 )
        }
        else {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 )
        }
        $PSConnection = $true
        $PSError = $null
    }
    catch {
        $PSConnection = $false
        $PSError = $($_.Exception.Message)
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            if ($null -ne $BaseHive) {
                $BaseHive.Close()
                $BaseHive.Dispose()
            }
            throw
        }
        else {
            Write-Warning "New-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
        }
    }
    if ($PSError) {
        if (-not $Suppress) {
            [PSCustomObject] @{
                PSComputerName = $Computer
                PSConnection   = $PSConnection
                PSError        = $true
                PSErrorMessage = $PSError
                Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
            }
        }
    }
    else {
        try {
            if ($PSCmdlet.ShouldProcess($Computer, "Creating registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)")) {
                $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true)
                if (-not $SubKey) {
                    $SubKeysSplit = $RegistryValue.SubKeyName.Split('\')
                    $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true)
                    if (-not $SubKey) {
                        $SubKey = $BaseHive.CreateSubKey($SubKeysSplit[0])
                    }
                    $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true)
                    foreach ($S in $SubKeysSplit | Select-Object -Skip 1) {
                        $SubKey = $SubKey.CreateSubKey($S)
                    }
                    $PSError = $false
                    $PSErrorMessage = $null
                }
                else {
                    $PSError = $false
                    $PSErrorMessage = "$($RegistryValue.SubKeyName) already exists."
                }
            }
            else {
                $PSError = $true
                $PSErrorMessage = "WhatIf was used. No changes done."
            }
            if (-not $Suppress) {
                [PSCustomObject] @{
                    PSComputerName = $Computer
                    PSConnection   = $PSConnection
                    PSError        = $PSError
                    PSErrorMessage = $PSErrorMessage
                    Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                }
            }
        }
        catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                if ($null -ne $SubKey) {
                    $SubKey.Close()
                    $SubKey.Dispose()
                }
                if ($null -ne $BaseHive) {
                    $BaseHive.Close()
                    $BaseHive.Dispose()
                }
                throw
            }
            else {
                Write-Warning "New-PSRegistry - Creating registry $RegistryPath on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
            }
            if (-not $Suppress) {
                [PSCustomObject] @{
                    PSComputerName = $Computer
                    PSConnection   = $PSConnection
                    PSError        = $true
                    PSErrorMessage = $($_.Exception.Message)
                    Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                }
            }
        }
    }
    if ($null -ne $SubKey) {
        $SubKey.Close()
        $SubKey.Dispose()
    }
    if ($null -ne $BaseHive) {
        $BaseHive.Close()
        $BaseHive.Dispose()
    }
}
function Remove-PrivateRegistry {
    <#
    .SYNOPSIS
    Removes a private registry key on a local or remote computer.
 
    .DESCRIPTION
    The Remove-PrivateRegistry function removes a registry key on a specified computer. It can be used to delete registry keys for a specific hive key, subkey, and key value.
 
    .PARAMETER Computer
    Specifies the name of the computer where the registry key will be removed.
 
    .PARAMETER Key
    Specifies the key value to be removed.
 
    .PARAMETER RegistryValue
    Specifies the registry key information to be removed. This should be an IDictionary object containing the hive key, subkey, and key value.
 
    .PARAMETER Remote
    Indicates whether the registry operation should be performed on a remote computer.
 
    .PARAMETER Suppress
    Suppresses the error message if set to true.
 
    .EXAMPLE
    Remove-PrivateRegistry -Computer 'Server01' -Key 'Version' -RegistryValue @{ HiveKey = 'LocalMachine'; SubKeyName = 'Software\MyApp' }
 
    Description:
    Removes the registry key 'Version' under 'LocalMachine\Software\MyApp' on the local computer 'Server01'.
 
    .EXAMPLE
    Remove-PrivateRegistry -Computer 'Workstation01' -Key 'Wallpaper' -RegistryValue @{ HiveKey = 'CurrentUser'; SubKeyName = 'Control Panel\Desktop' } -Remote
 
    Description:
    Removes the registry key 'Wallpaper' under 'CurrentUser\Control Panel\Desktop' on the remote computer 'Workstation01'.
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string] $Computer,
        [string] $Key,
        [System.Collections.IDictionary] $RegistryValue,
        [switch] $Remote,
        [switch] $Suppress
    )
    $PSConnection = $null
    $PSError = $null
    $PSErrorMessage = $null
    try {
        if ($Remote) {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 )
        }
        else {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 )
        }
        $PSConnection = $true
        $PSError = $null
    }
    catch {
        $PSConnection = $false
        $PSError = $($_.Exception.Message)
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            if ($null -ne $BaseHive) {
                $BaseHive.Close()
                $BaseHive.Dispose()
            }
            throw
        }
        else {
            Write-Warning "Remove-PSRegistry - Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
        }
    }
    if ($PSError) {
        if (-not $Suppress) {
            [PSCustomObject] @{
                PSComputerName = $Computer
                PSConnection   = $PSConnection
                PSError        = $true
                PSErrorMessage = $PSError
                Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                Key            = $RegistryValue.Key
            }
        }
    }
    else {
        try {
            if ($Key) {
                $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true)
                if ($PSCmdlet.ShouldProcess($Computer, "Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key)")) {
                    if ($SubKey) {
                        $SubKey.DeleteValue($RegistryValue.Key, $true)
                    }
                }
                else {
                    $PSError = $true
                    $PSErrorMessage = "WhatIf was used. No changes done."
                }
            }
            else {
                if ($PSCmdlet.ShouldProcess($Computer, "Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) folder")) {
                    if ($BaseHive) {
                        if ($Recursive) {
                            $BaseHive.DeleteSubKeyTree($RegistryValue.SubKeyName, $true)
                        }
                        else {
                            $BaseHive.DeleteSubKey($RegistryValue.SubKeyName, $true)
                        }
                    }
                }
                else {
                    $PSError = $true
                    $PSErrorMessage = "WhatIf was used. No changes done."
                }
            }
            if (-not $Suppress) {
                [PSCustomObject] @{
                    PSComputerName = $Computer
                    PSConnection   = $PSConnection
                    PSError        = $PSError
                    PSErrorMessage = $PSErrorMessage
                    Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                    Key            = $RegistryValue.Key
                }
            }
        }
        catch {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                if ($null -ne $SubKey) {
                    $SubKey.Close()
                    $SubKey.Dispose()
                }
                if ($null -ne $BaseHive) {
                    $BaseHive.Close()
                    $BaseHive.Dispose()
                }
                throw
            }
            else {
                Write-Warning "Remove-PSRegistry - Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
            }
            if (-not $Suppress) {
                [PSCustomObject] @{
                    PSComputerName = $Computer
                    PSConnection   = $PSConnection
                    PSError        = $true
                    PSErrorMessage = $_.Exception.Message
                    Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                    Key            = $RegistryValue.Key
                }
            }
        }
    }
    if ($null -ne $SubKey) {
        $SubKey.Close()
        $SubKey.Dispose()
    }
    if ($null -ne $BaseHive) {
        $BaseHive.Close()
        $BaseHive.Dispose()
    }
}
function Request-Credentials {
    <#
    .SYNOPSIS
    Requests credentials for authentication purposes.
 
    .DESCRIPTION
    The Request-Credentials function is used to prompt the user for credentials. It provides options to input the username and password directly, read the password from a file, convert the password to a secure string, and handle various error scenarios.
 
    .PARAMETER UserName
    Specifies the username for authentication.
 
    .PARAMETER Password
    Specifies the password for authentication.
 
    .PARAMETER AsSecure
    Indicates whether the password should be converted to a secure string.
 
    .PARAMETER FromFile
    Specifies whether the password should be read from a file.
 
    .PARAMETER Output
    Indicates whether the function should return output in case of errors.
 
    .PARAMETER NetworkCredentials
    Specifies if network credentials are being requested.
 
    .PARAMETER Service
    Specifies the service for which credentials are being requested.
 
    .EXAMPLE
    Request-Credentials -UserName 'JohnDoe' -Password 'P@ssw0rd' -AsSecure
    Requests credentials for the user 'JohnDoe' with the password 'P@ssw0rd' in a secure format.
 
    .EXAMPLE
    Request-Credentials -FromFile -Password 'C:\Credentials.txt' -Output -Service 'FTP'
    Reads the password from the file 'C:\Credentials.txt' and returns an error message if the file is unreadable for the FTP service.
 
    #>

    [CmdletBinding()]
    param(
        [string] $UserName,
        [string] $Password,
        [switch] $AsSecure,
        [switch] $FromFile,
        [switch] $Output,
        [switch] $NetworkCredentials,
        [string] $Service
    )
    if ($FromFile) {
        if (($Password -ne '') -and (Test-Path $Password)) {

            Write-Verbose "Request-Credentials - Reading password from file $Password"
            $Password = Get-Content -Path $Password
        }
        else {

            if ($Output) {
                return @{ Status = $false; Output = $Service; Extended = 'File with password unreadable.' }
            }
            else {
                Write-Warning "Request-Credentials - Secure password from file couldn't be read. File not readable. Terminating."
                return
            }
        }
    }
    if ($AsSecure) {
        try {
            $NewPassword = $Password | ConvertTo-SecureString -ErrorAction Stop
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                if ($Output) {
                    return @{ Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." }
                }
                else {
                    Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                    return
                }
            }
            else {
                if ($Output) {
                    return @{ Status = $false; Output = $Service; Extended = $ErrorMessage }
                }
                else {
                    Write-Warning -Message "Request-Credentials - $ErrorMessage"
                    return
                }
            }
        }
    }
    else {
        $NewPassword = $Password
    }
    if ($UserName -and $NewPassword) {
        if ($AsSecure) {
            $Credentials = New-Object System.Management.Automation.PSCredential($Username, $NewPassword)
        }
        else {
            Try {
                $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                if ($ErrorMessage -like '*Key not valid for use in specified state*') {
                    if ($Output) {
                        return  @{ Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." }
                    }
                    else {
                        Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer."
                        return
                    }
                }
                else {
                    if ($Output) {
                        return @{ Status = $false; Output = $Service; Extended = $ErrorMessage }
                    }
                    else {
                        Write-Warning -Message "Request-Credentials - $ErrorMessage"
                        return
                    }
                }
            }
            $Credentials = New-Object System.Management.Automation.PSCredential($Username, $SecurePassword)
        }
    }
    else {
        if ($Output) {
            return @{ Status = $false; Output = $Service; Extended = 'Username or/and Password is empty' }
        }
        else {
            Write-Warning -Message 'Request-Credentials - UserName or Password are empty.'
            return
        }
    }
    if ($NetworkCredentials) {
        return $Credentials.GetNetworkCredential()
    }
    else {
        return $Credentials
    }
}
function Resolve-PrivateRegistry {
    <#
    .SYNOPSIS
    Resolves and standardizes registry paths for consistency and compatibility.
 
    .DESCRIPTION
    The Resolve-PrivateRegistry function resolves and standardizes registry paths to ensure uniformity and compatibility across different systems. It cleans up the paths, converts short hive names to full names, and handles special cases like DEFAULT USER mappings.
 
    .PARAMETER RegistryPath
    Specifies an array of registry paths to be resolved and standardized.
 
    .EXAMPLE
    Resolve-PrivateRegistry -RegistryPath 'Users\.DEFAULT_USER\Software\MyApp'
    Resolves the registry path 'Users\.DEFAULT_USER\Software\MyApp' to 'HKUD\Software\MyApp' for consistent usage.
 
    .EXAMPLE
    Resolve-PrivateRegistry -RegistryPath 'HKCU\Software\MyApp'
    Resolves the registry path 'HKCU\Software\MyApp' to 'HKEY_CURRENT_USER\Software\MyApp' for compatibility with standard naming conventions.
 
    #>

    [CmdletBinding()]
    param(
        [alias('Path')][string[]] $RegistryPath
    )
    foreach ($R in $RegistryPath) {

        $R = $R.Replace("\\", "\").Replace("\\", "\")

        If ($R.StartsWith("Users\.DEFAULT_USER") -or $R.StartsWith('HKEY_USERS\.DEFAULT_USER')) {
            $R = $R.Replace("Users\.DEFAULT_USER", "HKUD")
            $R.Replace('HKEY_USERS\.DEFAULT_USER', "HKUD")
        }
        elseif ($R -like '*:*') {
            $Found = $false

            foreach ($DictionaryKey in $Script:Dictionary.Keys) {
                $SplitParts = $R.Split("\")
                $FirstPart = $SplitParts[0]
                if ($FirstPart -eq $DictionaryKey) {
                    $R -replace $DictionaryKey, $Script:Dictionary[$DictionaryKey]
                    $Found = $true
                    break
                }
            }

            if (-not $Found) {
                $R.Replace(":", "")
            }
        }
        else {

            $R
        }
    }
}
function Set-PrivateRegistry {
    <#
    .SYNOPSIS
    Sets a registry value on a local or remote computer.
 
    .DESCRIPTION
    The Set-PrivateRegistry function sets a registry value on a specified computer. It can be used to create new registry keys and values, update existing ones, or delete them.
 
    .PARAMETER RegistryValue
    Specifies the registry value to be set. This parameter should be an IDictionary object containing the following properties:
        - HiveKey: The registry hive key (e.g., 'LocalMachine', 'CurrentUser').
        - SubKeyName: The subkey path where the value will be set.
        - Key: The name of the registry value.
        - Value: The data to be stored in the registry value.
        - ValueKind: The type of data being stored (e.g., String, DWord, MultiString).
 
    .PARAMETER Computer
    Specifies the name of the computer where the registry value will be set.
 
    .PARAMETER Remote
    Indicates that the registry value should be set on a remote computer.
 
    .PARAMETER Suppress
    Suppresses error messages and warnings.
 
    .EXAMPLE
    Set-PrivateRegistry -RegistryValue @{HiveKey='LocalMachine'; SubKeyName='SOFTWARE\MyApp'; Key='Version'; Value='1.0'; ValueKind='String'} -Computer 'Server01'
    Sets the registry value 'Version' under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' to '1.0' on the local computer 'Server01'.
 
    .EXAMPLE
    Set-PrivateRegistry -RegistryValue @{HiveKey='CurrentUser'; SubKeyName='Environment'; Key='Path'; Value='C:\MyApp'; ValueKind='String'} -Computer 'Server02' -Remote
    Sets the registry value 'Path' under 'HKEY_CURRENT_USER\Environment' to 'C:\MyApp' on the remote computer 'Server02'.
 
    .NOTES
    File Name : Set-PrivateRegistry.ps1
    Prerequisite : This function requires administrative privileges to modify the registry.
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [System.Collections.IDictionary] $RegistryValue,
        [string] $Computer,
        [switch] $Remote,
        [switch] $Suppress
    )
    Write-Verbose -Message "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer"
    if ($RegistryValue.ComputerName) {
        if ($RegistryValue.ComputerName -ne $Computer) {
            return
        }
    }
    try {
        if ($Remote) {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 )
        }
        else {
            $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 )
        }
        $PSConnection = $true
        $PSError = $null
    }
    catch {
        $PSConnection = $false
        $PSError = $($_.Exception.Message)
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            if ($null -ne $BaseHive) {
                $BaseHive.Close()
                $BaseHive.Dispose()
            }
            throw
        }
        else {
            Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
        }
    }
    if ($PSCmdlet.ShouldProcess($Computer, "Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind)")) {
        if ($PSError) {
            if (-not $Suppress) {
                [PSCustomObject] @{
                    PSComputerName = $Computer
                    PSConnection   = $PSConnection
                    PSError        = $true
                    PSErrorMessage = $PSError
                    Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                    Key            = $RegistryValue.Key
                    Value          = $RegistryValue.Value
                    Type           = $RegistryValue.ValueKind
                }
            }
        }
        else {
            try {

                $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true)
                if (-not $SubKey) {
                    $SubKeysSplit = $RegistryValue.SubKeyName.Split('\')
                    $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true)
                    if (-not $SubKey) {
                        $SubKey = $BaseHive.CreateSubKey($SubKeysSplit[0])
                    }
                    $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true)
                    foreach ($S in $SubKeysSplit | Select-Object -Skip 1) {
                        $SubKey = $SubKey.CreateSubKey($S)
                    }
                }
                if ($RegistryValue.ValueKind -eq [Microsoft.Win32.RegistryValueKind]::MultiString) {
                    $SubKey.SetValue($RegistryValue.Key, [string[]] $RegistryValue.Value, $RegistryValue.ValueKind)
                }
                elseif ($RegistryValue.ValueKind -in [Microsoft.Win32.RegistryValueKind]::None, [Microsoft.Win32.RegistryValueKind]::Binary) {
                    $SubKey.SetValue($RegistryValue.Key, [byte[]] $RegistryValue.Value, $RegistryValue.ValueKind)
                }
                else {
                    $SubKey.SetValue($RegistryValue.Key, $RegistryValue.Value, $RegistryValue.ValueKind)
                }
                if (-not $Suppress) {
                    [PSCustomObject] @{
                        PSComputerName = $Computer
                        PSConnection   = $PSConnection
                        PSError        = $false
                        PSErrorMessage = $null
                        Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                        Key            = $RegistryValue.Key
                        Value          = $RegistryValue.Value
                        Type           = $RegistryValue.ValueKind
                    }
                }
            }
            catch {
                if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                    if ($null -ne $SubKey) {
                        $SubKey.Close()
                        $SubKey.Dispose()
                    }
                    if ($null -ne $BaseHive) {
                        $BaseHive.Close()
                        $BaseHive.Dispose()
                    }
                    throw
                }
                else {
                    Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))"
                }
                if (-not $Suppress) {
                    [PSCustomObject] @{
                        PSComputerName = $Computer
                        PSConnection   = $PSConnection
                        PSError        = $true
                        PSErrorMessage = $_.Exception.Message
                        Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                        Key            = $RegistryValue.Key
                        Value          = $RegistryValue.Value
                        Type           = $RegistryValue.ValueKind
                    }
                }
            }
        }
    }
    else {
        if (-not $Suppress) {
            [PSCustomObject] @{
                PSComputerName = $Computer
                PSConnection   = $PSConnection
                PSError        = $true
                PSErrorMessage = if ($PSError) {
                    $PSError 
                }
                else {
                    "WhatIf used - skipping registry setting" 
                }
                Path           = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)"
                Key            = $RegistryValue.Key
                Value          = $RegistryValue.Value
                Type           = $RegistryValue.ValueKind
            }
        }
    }
    if ($null -ne $SubKey) {
        $SubKey.Close()
        $SubKey.Dispose()
    }
    if ($null -ne $BaseHive) {
        $BaseHive.Close()
        $BaseHive.Dispose()
    }
}
function Test-IPIsInNetwork {
    <#
    .SYNOPSIS
    Checks if an IP address falls within a specified range defined by binary start and end values.
 
    .DESCRIPTION
    This function compares the binary representation of an IP address with the binary start and end values to determine if the IP address falls within the specified range.
 
    .EXAMPLE
    Test-IPIsInNetwork -IP "192.168.1.10" -StartBinary "11000000101010000000000100000000" -EndBinary "11000000101010000000000111111111"
 
    Description:
    Checks if the IP address 192.168.1.10 falls within the range defined by the binary start and end values.
 
    #>

    [cmdletBinding()]
    param(
        [string] $IP,
        [string] $StartBinary,
        [string] $EndBinary
    )
    $TestIPBinary = Convert-IPToBinary $IP
    [int64] $TestIPInt64 = [System.Convert]::ToInt64($TestIPBinary, 2)
    [int64] $StartInt64 = [System.Convert]::ToInt64($StartBinary, 2)
    [int64] $EndInt64 = [System.Convert]::ToInt64($EndBinary, 2)
    if ($TestIPInt64 -ge $StartInt64 -and $TestIPInt64 -le $EndInt64) {
        return $True
    }
    else {
        return $False
    }
}
function Unregister-MountedRegistry {
    <#
    .SYNOPSIS
    Unregisters mounted registry paths.
 
    .DESCRIPTION
    This function unregisters mounted registry paths that were previously mounted using Mount-PSRegistryPath.
 
    .EXAMPLE
    Unregister-MountedRegistry
 
    Description:
    Unregisters all mounted registry paths.
 
    #>

    [CmdletBinding()]
    param(

    )
    if ($null -ne $Script:DefaultRegistryMounted) {
        Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\.DEFAULT_USER"
        $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\.DEFAULT_USER"
        $Script:DefaultRegistryMounted = $null
    }
    if ($null -ne $Script:OfflineRegistryMounted) {
        foreach ($Key in $Script:OfflineRegistryMounted.Keys) {
            if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) {
                Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\$Key"
                $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\$Key"
            }
        }
        $Script:OfflineRegistryMounted = $null
    }
}

function Add-WinADUserGroups {
    <#
    .SYNOPSIS
    Adds a user to specified Active Directory groups.
 
    .DESCRIPTION
    This function adds a user to the specified Active Directory groups. It retrieves the user's current group memberships and adds the user to the specified groups if they are not already a member.
 
    .PARAMETER User
    The user object to add to the groups.
 
    .PARAMETER Groups
    An array of group names to add the user to.
 
    .PARAMETER FieldSearch
    The field to search for group names. Default is 'Name'.
 
    .PARAMETER WhatIf
    Specifies whether to perform a test run without making any changes.
 
    .EXAMPLE
    Add-WinADUserGroups -User $UserObject -Groups @("Group1", "Group2")
 
    Adds the user specified by $UserObject to the groups "Group1" and "Group2".
 
    .EXAMPLE
    Add-WinADUserGroups -User $UserObject -Groups @("Group1", "Group2") -FieldSearch 'SamAccountName'
 
    Adds the user specified by $UserObject to the groups "Group1" and "Group2" using 'SamAccountName' for group name search.
 
    .NOTES
    File Name : Add-WinADUserGroups.ps1
    Prerequisite : Requires Active Directory module.
    #>

    [CmdletBinding()]
    [alias("Add-ADUserGroups")]
    param(
        [parameter(Mandatory = $true)][Object] $User,
        [string[]] $Groups,
        [string] $FieldSearch = 'Name',
        [switch] $WhatIf
    )
    $Object = @()
    try {
        $ADgroups = Get-ADPrincipalGroupMembership -Identity $User.DistinguishedName | Where-Object { $_.Name -ne "Domain Users" }
    }
    catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
    }
    if ($Groups) {
        foreach ($Group in $Groups) {
            if ($ADgroups.$FieldSearch -notcontains $Group) {
                try {
                    if (-not $WhatIf) {
                        Add-ADGroupMember -Identity $Group -Members $User.DistinguishedName -ErrorAction Stop
                    }
                    $Object += @{ Status = $true; Output = $Group; Extended = 'Added to group.' }
                }
                catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    $Object += @{ Status = $false; Output = $Group; Extended = $ErrorMessage }
                }
            }
            else {
            }
        }
    }
    return $Object
}
function Convert-ADGuidToSchema {
    <#
    .SYNOPSIS
    Converts Guid to schema properties
 
    .DESCRIPTION
    Converts Guid to schema properties
 
    .PARAMETER Guid
    Guid to Convert to Schema Name
 
    .PARAMETER Domain
    Domain to query. By default the current domain is used
 
   .PARAMETER RootDSE
    RootDSE to query. By default RootDSE is queried from the domain
 
    .PARAMETER DisplayName
    Return the schema name by display name. By default it returns as Name
 
    .EXAMPLE
    $T2 = '570b9266-bbb3-4fad-a712-d2e3fedc34dd'
    $T = [guid] '570b9266-bbb3-4fad-a712-d2e3fedc34dd'
 
    Convert-ADGuidToSchema -Guid $T
    Convert-ADGuidToSchema -Guid $T2
 
    .NOTES
    General notes
    #>

    [alias('Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs')]
    [cmdletbinding()]
    param(
        [string] $Guid,
        [string] $Domain,
        [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE,
        [switch] $DisplayName
    )
    if (-not $Script:ADSchemaMap -or -not $Script:ADSchemaMapDisplayName) {
        if ($RootDSE) {
            $Script:RootDSE = $RootDSE
        }
        elseif (-not $Script:RootDSE) {
            if ($Domain) {
                $Script:RootDSE = Get-ADRootDSE -Server $Domain
            }
            else {
                $Script:RootDSE = Get-ADRootDSE
            }
        }
        $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN
        $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0]

        $Script:ADSchemaMap = @{ }
        $Script:ADSchemaMapDisplayName = @{ }
        $Script:ADSchemaMapDisplayName['00000000-0000-0000-0000-000000000000'] = 'All'
        $Script:ADSchemaMap.Add('00000000-0000-0000-0000-000000000000', 'All')
        Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer"
        $Time = [System.Diagnostics.Stopwatch]::StartNew()
        if (-not $Script:StandardRights) {
            $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID
        }
        foreach ($S in $Script:StandardRights) {
            $Script:ADSchemaMap["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name
            $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.lDAPDisplayName
        }
        $Time.Stop()
        $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds"
        Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer took $TimeToExecute"
        Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer"
        $Time = [System.Diagnostics.Stopwatch]::StartNew()

        if (-not $Script:ExtendedRightsGuids) {
            $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid
        }
        foreach ($S in $Script:ExtendedRightsGuids) {
            $Script:ADSchemaMap["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name
            $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.displayName
        }
        $Time.Stop()
        $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds"
        Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer took $TimeToExecute"
    }
    if ($Guid) {
        if ($DisplayName) {
            $Script:ADSchemaMapDisplayName[$Guid]
        }
        else {
            $Script:ADSchemaMap[$Guid]
        }
    }
    else {
        if ($DisplayName) {
            $Script:ADSchemaMapDisplayName
        }
        else {
            $Script:ADSchemaMap
        }
    }
}
function Convert-ADSchemaToGuid {
    <#
    .SYNOPSIS
    Converts name of schema properties to guids
 
    .DESCRIPTION
    Converts name of schema properties to guids
 
    .PARAMETER SchemaName
    Schema Name to convert to guid
 
    .PARAMETER All
    Get hashtable of all schema properties and their guids
 
    .PARAMETER Domain
    Domain to query. By default the current domain is used
 
    .PARAMETER RootDSE
    RootDSE to query. By default RootDSE is queried from the domain
 
    .PARAMETER AsString
    Return the guid as a string
 
    .EXAMPLE
    Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie'
 
    .EXAMPLE
    Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' -AsString
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $SchemaName,
        [string] $Domain,
        [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE,
        [switch] $AsString
    )
    if (-not $Script:ADGuidMap -or -not $Script:ADGuidMapString) {

        if ($RootDSE) {
            $Script:RootDSE = $RootDSE
        }
        elseif (-not $Script:RootDSE) {
            if ($Domain) {
                $Script:RootDSE = Get-ADRootDSE -Server $Domain
            }
            else {
                $Script:RootDSE = Get-ADRootDSE
            }
        }
        $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN
        $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0]

        $Script:ADGuidMap = [ordered] @{
            'All' = [System.GUID]'00000000-0000-0000-0000-000000000000'
        }
        $Script:ADGuidMapString = [ordered] @{
            'All' = '00000000-0000-0000-0000-000000000000'
        }
        Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer"
        $Time = [System.Diagnostics.Stopwatch]::StartNew()
        if (-not $Script:StandardRights) {
            $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID
        }
        foreach ($Guid in $Script:StandardRights) {
            $Script:ADGuidMapString[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID).Guid
            $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID).Guid
            $Script:ADGuidMap[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID)
            $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID)
        }
        $Time.Stop()
        $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds"
        Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer took $TimeToExecute"
        Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer"
        $Time = [System.Diagnostics.Stopwatch]::StartNew()

        if (-not $Script:ExtendedRightsGuids) {
            $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid
        }
        foreach ($Guid in $Script:ExtendedRightsGuids) {
            $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.RightsGuid).Guid
            $Script:ADGuidMapString[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid).Guid
            $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.RightsGuid)
            $Script:ADGuidMap[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid)
        }
        $Time.Stop()
        $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds"
        Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer took $TimeToExecute"
    }
    if ($SchemaName) {
        if ($AsString) {
            return $Script:ADGuidMapString[$SchemaName]
        }
        else {
            return $Script:ADGuidMap[$SchemaName]
        }
    }
    else {
        if ($AsString) {
            $Script:ADGuidMapString
        }
        else {
            $Script:ADGuidMap
        }
    }
}
function Find-ADConnectServer {
    <#
    .SYNOPSIS
    Finds and retrieves information about AD Connect servers based on user descriptions.
 
    .DESCRIPTION
    This function searches for AD Connect servers based on user descriptions containing specific patterns. It extracts server name, tenant name, installation ID, and type of account created.
 
    .PARAMETER Description
    Specifies the user description to search for AD Connect server information.
 
    .EXAMPLE
    Find-ADConnectServer -Description "Account created by John Doe on computer Server1 to tenant Contoso. This is the installation identifier 12345 running on Server1 configured."
 
    Retrieves information about the AD Connect server named Server1 with tenant Contoso, installation ID 12345, and account created by John Doe.
 
    .NOTES
    File Name : Find-ADConnectServer.ps1
    Prerequisite : Requires Active Directory module.
    #>

    [alias('Find-ADSyncServer')]
    param(

    )
    $Description = Get-ADUser -Filter { Name -like "MSOL*" } -Properties Description | Select-Object Description -ExpandProperty Description

    foreach ($Desc in $Description) {
        $PatternType = "(?<=(Account created by ))(.*)(?=(with installation identifier))"
        $PatternServerName = "(?<=(on computer ))(.*)(?=(configured))"
        $PatternTenantName = "(?<=(to tenant ))(.*)(?=(. This))"
        $PatternInstallationID = "(?<=(installation identifier ))(.*)(?=( running on ))"
        if ($Desc -match $PatternServerName) {
            $ServerName = ($Matches[0]).Replace("'", '').Replace(' ', '')

            if ($Desc -match $PatternTenantName) {
                $TenantName = ($Matches[0]).Replace("'", '').Replace(' ', '')
            }
            else {
                $TenantName = ''
            }
            if ($Desc -match $PatternInstallationID) {
                $InstallationID = ($Matches[0]).Replace("'", '').Replace(' ', '')
            }
            else {
                $InstallationID = ''
            }
            if ($Desc -match $PatternType) {
                $Type = ($Matches[0]).Replace("'", '').Replace('by ', '').Replace('the ', '')
            }
            else {
                $Type = ''
            }

            $Data = Get-ADComputer -Identity $ServerName
            [PSCustomObject] @{
                Name              = $Data.Name
                FQDN              = $Data.DNSHostName
                DistinguishedName = $Data.DistinguishedName
                Type              = $Type
                TenantName        = $TenantName
                InstallatioNID    = $InstallationID 
            }
        }
    }
}
function Find-ExchangeServer {
    <#
    .SYNOPSIS
    Find Exchange Servers in Active Directory
 
    .DESCRIPTION
    Find Exchange Servers in Active Directory
 
    .EXAMPLE
    Find-ExchangeServer
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(

    )
    $ExchangeServers = Get-ADGroup -Identity "Exchange Servers"  
    foreach ($Server in $ExchangeServers) {
        $Data = Get-ADComputer -Identity $Server.SamAccountName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName
        [PSCustomObject] @{
            Name              = $Data.Name
            FQDN              = $Data.DNSHostName
            OperatingSystem   = $Data.OperatingSystem
            DistinguishedName = $Data.DistinguishedName
            Enabled           = $Data.Enabled
        }
    }
}
function Find-HyperVServer {
    <#
    .SYNOPSIS
    Finds Hyper-V servers in Active Directory.
 
    .DESCRIPTION
    This function retrieves information about Hyper-V servers from Active Directory service connection points.
 
    .EXAMPLE
    Find-HyperVServer
    Retrieves information about all Hyper-V servers in Active Directory.
 
    #>
          
    [cmdletbinding()]            
    param()            
    try {            
        $ADObjects = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint" -and Name -eq "Microsoft Hyper-V"' -ErrorAction Stop            
    }
    catch {            
        Write-Error "Error: $_"            
    }            
    foreach ($Server in $ADObjects) {            
        $Temporary = $Server.DistinguishedName.split(",")            
        $DistinguishedName = $Temporary[1..$Temporary.Count] -join ","        
        $Data = Get-ADComputer -Identity $DistinguishedName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName       
        [PSCustomObject] @{
            Name              = $Data.Name
            FQDN              = $Data.DNSHostName
            OperatingSystem   = $Data.OperatingSystem
            DistinguishedName = $Data.DistinguishedName
            Enabled           = $Data.Enabled
        }
    }      
}

function Find-ServerTypes {
    <#
    .SYNOPSIS
    Finds different types of servers in the Active Directory forest.
 
    .DESCRIPTION
    This function retrieves information about different types of servers in the Active Directory forest based on the specified server types.
 
    .PARAMETER Type
    Specifies the type of servers to retrieve. Valid values are 'All', 'ADConnect', 'DomainController', 'Exchange', 'Hyper-V', 'RDSLicense', 'SQL', and 'VirtualMachine'.
 
    .EXAMPLE
    Find-ServerTypes -Type Exchange
    Retrieves information about Exchange servers in the Active Directory forest.
 
    .EXAMPLE
    Find-ServerTypes -Type DomainController
    Retrieves information about Domain Controller servers in the Active Directory forest.
    #>

    [cmdletbinding()]
    param(
        [string[]][ValidateSet('All', 'ADConnect', 'DomainController', 'Exchange', 'Hyper-V', 'RDSLicense', 'SQL', 'VirtualMachine')] $Type = 'All'
    )
    $Forest = Get-ADForest

    foreach ($Domain in $Forest.Domains) {
        try {
            $DomainInformation = Get-ADDomain -Server $Domain -ErrorAction Stop
        }
        catch {
            Write-Warning "Find-ServerTypes - Domain $Domain couldn't be reached. Skipping"
            continue
        }

        try {
            $ServiceConnectionPoint = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint"' -ErrorAction Stop -Server $Domain
            foreach ($Point in $ServiceConnectionPoint) {  
                $Temporary = $Point.DistinguishedName.split(",")            
                $DistinguishedName = $Temporary[1..$Temporary.Count] -join ","    
                $Point | Add-Member -MemberType 'NoteProperty' -Name 'DN' -Value $DistinguishedName -Force
            }
        }
        catch {
            Write-Error "Find-ServerTypes - Get-ADObject command failed. Terminating. Error $_"
            return
        }

        $ADConnect = Find-ADConnectServer
        $Computers = Get-ADComputer -Filter * -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName -Server $Domain
        $Servers = foreach ($Computer in $Computers) {
            $Services = foreach ($Service in $Computer.servicePrincipalName) {
                ($Service -split '/')[0]
            }
            [PSCustomObject] @{
                Name                   = $Computer.Name
                FQDN                   = $Computer.DNSHostName
                OperatingSystem        = $Computer.OperatingSystem
                DistinguishedName      = $Computer.DistinguishedName
                Enabled                = $Computer.Enabled
                IsExchange             = if ($Services -like '*ExchangeMDB*' -or $Services -like '*ExchangeRFR*') {
                    $true 
                }
                else {
                    $false 
                }
                IsSql                  = if ($Services -like '*MSSql*') {
                    $true 
                }
                else {
                    $false 
                }
                IsVM                   = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Windows Virtual Machine') {
                    $true 
                }
                else {
                    $false 
                } 
                IsHyperV               = if ($Services -like '*Hyper-V Replica*') {
                    $true 
                }
                else {
                    $false 
                }
                IsSPHyperV             = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Microsoft Hyper-V') {
                    $true 
                }
                else {
                    $false 
                } 
                IsRDSLicense           = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'TermServLicensing') {
                    $true 
                }
                else {
                    $false 
                } 

                IsDC                   = if ($DomainInformation.ReplicaDirectoryServers -contains $Computer.DNSHostName) {
                    $true 
                }
                else {
                    $false 
                }  
                IsADConnect            = if ($ADConnect.FQDN -eq $Computer.DNSHostName) {
                    $true 
                }
                else {
                    $false 
                }
                Forest                 = $Forest.Name
                Domain                 = $Domain
                ServicePrincipalName   = ($Services | Sort-Object -Unique) -Join ','
                ServiceConnectionPoint = ($ServiceConnectionPoint | Where-Object { $_.DN -eq $Computer.DistinguishedName }).Name -join ','
            }
        }
        if ($Type -eq 'All') {
            $Servers
        }
        else {
            if ($Type -contains 'SQL') {
                $Servers | Where-Object { $_.IsSql -eq $true }
            }
            if ($Type -contains 'Exchange' ) {
                $Servers | Where-Object { $_.IsExchange -eq $true } 
            }
            if ($Type -contains 'Hyper-V') {
                $Servers | Where-Object { $_.IsHyperV -eq $true -or $_.IsSPHyperV -eq $true } 
            }
            if ($Type -contains 'VirtualMachine') {
                $Servers | Where-Object { $_.IsVM -eq $true } 
            }
            if ($Type -contains 'RDSLicense') {
                $Servers | Where-Object { $_.IsRDSLicense -eq $true } 
            }
            if ($Type -contains 'DomainController') {
                $Servers | Where-Object { $_.IsDC -eq $true } 
            }
            if ($Type -contains 'DomainController') {
                $Servers | Where-Object { $_.IsDC -eq $true } 
            }
            if ($Type -contains 'ADConnect') {
                $Servers | Where-Object { $_.IsADConnect -eq $true } 
            }
        }
    }
}

function Find-UsersProxyAddressesStatus {
    <#
    .SYNOPSIS
    This function checks the status of user proxy addresses.
 
    .DESCRIPTION
    The function takes a user object as input and determines the status of their proxy addresses. It checks if the user has any proxy addresses, if the primary proxy address is missing, if there are multiple primary proxy addresses, or if all proxy addresses are correctly set up.
 
    .PARAMETER User
    Specifies the user object for which the proxy addresses status needs to be checked.
 
    .EXAMPLE
    Find-UsersProxyAddressesStatus -User $user
    Checks the proxy addresses status for the specified user object.
 
    .NOTES
    File Name : Find-UserProxyAddressesStatus.ps1
    Prerequisite : This function requires a valid user object with proxy addresses.
    #>

    param(
        $User
    )
    $status = 'No proxy'
    if ($null -ne $user.proxyAddresses) {
        $count = 0
        foreach ($proxy in $($user.ProxyAddresses)) {
            if ($proxy.SubString(0, 4) -ceq 'SMTP') {
                $count++ 
            }
        }
        if ($count -eq 0) {
            $status = 'Missing primary proxy'
        }
        elseif ($count -gt 1) {
            $status = 'Multiple primary proxy'
        }
        else {
            $status = 'All OK'
        }
    }
    else {
        $status = 'Missing all proxy'
    }
    return $status
}
function Get-ADADministrativeGroups {
    <#
    .SYNOPSIS
    Retrieves administrative groups information from Active Directory.
 
    .DESCRIPTION
    This function retrieves information about administrative groups in Active Directory based on the specified parameters.
 
    .PARAMETER Type
    Specifies the type of administrative groups to retrieve. Valid values are 'DomainAdmins' and 'EnterpriseAdmins'.
 
    .PARAMETER Forest
    Specifies the name of the forest to query for administrative groups.
 
    .PARAMETER ExcludeDomains
    Specifies an array of domains to exclude from the query.
 
    .PARAMETER IncludeDomains
    Specifies an array of domains to include in the query.
 
    .PARAMETER ExtendedForestInformation
    Specifies additional information about the forest to include in the query.
 
    .EXAMPLE
    Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins
 
    Output (Where VALUE is Get-ADGroup output):
    Name Value
    ---- -----
    ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins}
    ad.evotec.xyz {DomainAdmins, EnterpriseAdmins}
    ad.evotec.pl {DomainAdmins}
 
    .NOTES
    This function requires Active Directory module to be installed on the system.
    #>

    [cmdletBinding()]
    param(
        [parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type,
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $ADDictionary = [ordered] @{ }
    $ADDictionary['ByNetBIOS'] = [ordered] @{ }
    $ADDictionary['BySID'] = [ordered] @{ }

    $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
    foreach ($Domain in $ForestInformation.Domains) {
        $ADDictionary[$Domain] = [ordered] @{ }
        $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
        $DomainInformation = Get-ADDomain -Server $QueryServer

        if ($Type -contains 'DomainAdmins') {
            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object {
                $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_
            }
        }
    }

    foreach ($Domain in $ForestInformation.Forest.Domains) {
        if (-not $ADDictionary[$Domain]) {
            $ADDictionary[$Domain] = [ordered] @{ }
        }
        if ($Type -contains 'EnterpriseAdmins') {
            $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0]
            $DomainInformation = Get-ADDomain -Server $QueryServer

            Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object {
                $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_
                $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)"
                $ADDictionary['BySID'][$_.SID.Value] = $_ 
            }
        }
    }
    return $ADDictionary
}

function Get-ADEncryptionTypes {
    <#
    .SYNOPSIS
    Retrieves the supported encryption types based on the specified value.
 
    .DESCRIPTION
    This function returns the list of encryption types supported by Active Directory based on the provided value. Each encryption type is represented by a string.
 
    .PARAMETER Value
    Specifies the integer value representing the encryption types to retrieve.
 
    .EXAMPLE
    Get-ADEncryptionTypes -Value 24
 
    Retrieves the following encryption types:
    - AES128-CTS-HMAC-SHA1-96
    - AES256-CTS-HMAC-SHA1-96
 
    .NOTES
    This function is designed to provide information about encryption types supported by Active Directory.
    #>

    [cmdletbinding()]
    Param(
        [parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value
    )

    [String[]]$EncryptionTypes = @(
        Foreach ($V in $Value) {
            if ([int32]$V -band 0x00000001) {
                "DES-CBC-CRC" 
            } 
            if ([int32]$V -band 0x00000002) {
                "DES-CBC-MD5" 
            } 
            if ([int32]$V -band 0x00000004) {
                "RC4-HMAC" 
            } 
            if ([int32]$V -band 0x00000008) {
                "AES128-CTS-HMAC-SHA1-96" 
            } 
            if ([int32]$V -band 0x00000010) {
                "AES256-CTS-HMAC-SHA1-96" 
            } 
            if ([int32]$V -band 0x00000020) {
                "FAST-supported" 
            } 
            if ([int32]$V -band 0x00000040) {
                "Compound-identity-supported" 
            } 
            if ([int32]$V -band 0x00000080) {
                "Claims-supported" 
            } 
            if ([int32]$V -band 0x00000200) {
                "Resource-SID-compression-disabled" 
            } 
        }
    )
    $EncryptionTypes
}
function Get-ADTrustAttributes {
    <#
    .SYNOPSIS
    Retrieves and interprets Active Directory trust attributes based on the provided value.
 
    .DESCRIPTION
    This function retrieves and interprets Active Directory trust attributes based on the provided value. It decodes the binary value into human-readable trust attributes.
 
    .PARAMETER Value
    Specifies the integer value representing the trust attributes.
 
    .EXAMPLE
    Get-ADTrustAttributes -Value 1
    Retrieves and interprets the trust attributes for the value 1.
 
    .EXAMPLE
    1, 2, 4 | Get-ADTrustAttributes
    Retrieves and interprets the trust attributes for the values 1, 2, and 4.
 
    .NOTES
    This function provides a convenient way to decode Active Directory trust attributes.
    #>

    [cmdletbinding()]
    Param(
        [parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value
    )

    [String[]]$TrustAttributes = @(
        Foreach ($V in $Value) {
            if ([int32]$V -band 0x00000001) {
                "Non Transitive" 
            } 
            if ([int32]$V -band 0x00000002) {
                "UpLevel Only" 
            } 
            if ([int32]$V -band 0x00000004) {
                "Quarantined Domain" 
            } 
            if ([int32]$V -band 0x00000008) {
                "Forest Transitive" 
            } 
            if ([int32]$V -band 0x00000010) {
                "Cross Organization" 
            } 
            if ([int32]$V -band 0x00000020) {
                "Within Forest" 
            } 
            if ([int32]$V -band 0x00000040) {
                "Treat as External" 
            } 
            if ([int32]$V -band 0x00000080) {
                "Uses RC4 Encryption" 
            } 
            if ([int32]$V -band 0x00000200) {
                "No TGT DELEGATION" 
            } 
            if ([int32]$V -band 0x00000800) {
                "Enable TGT DELEGATION" 
            } 
            if ([int32]$V -band 0x00000400) {
                "PIM Trust" 
            } 
        }
    )
    return $TrustAttributes
}
function Get-WinADDSAGuid {
    <#
    .SYNOPSIS
    Get DSA GUIDs from a forest for all domain controllers
 
    .DESCRIPTION
    This function retrieves DSA GUIDs from a forest for all domain controllers
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExcludeDomainControllers
    Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER IncludeDomainControllers
    Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER SkipRODC
    Skip Read-Only Domain Controllers. By default all domain controllers are included.
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .EXAMPLE
    Get-WinADDSAGuid | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    $Forest = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC
    $ListDSA = [ordered]@{}
    foreach ($DC in $Forest.ForestDomainControllers) {
        $ListDSA[$DC.DsaGuid] = [PSCustomObject] @{
            Domain      = $DC.Domain
            HostName    = $DC.HostName
            DsaGuid     = $DC.DsaGuid
            DsaGuidName = $DC.DsaGuidName
        }
    }
    if ($Hashtable) {
        $ListDSA
    }
    else {
        $ListDSA.Values | ForEach-Object { $_ }
    }
}
function Get-WinADForestControllers {
    <#
    .SYNOPSIS
    Retrieves information about domain controllers in the specified domain(s).
 
    .DESCRIPTION
    This function retrieves detailed information about domain controllers in the specified domain(s), including hostname, IP addresses, roles, and other relevant details.
 
    .PARAMETER TestAvailability
    Specifies whether to test the availability of domain controllers.
 
    .EXAMPLE
    Get-WinADForestControllers -TestAvailability
    Tests the availability of domain controllers in the forest.
 
    .EXAMPLE
    Get-WinADDomainControllers
    Retrieves information about all domain controllers in the forest.
 
    .EXAMPLE
    Get-WinADDomainControllers -Credential $Credential
    Retrieves information about all domain controllers in the forest using specified credentials.
 
    .EXAMPLE
    Get-WinADDomainControllers | Format-Table *
    Displays detailed information about all domain controllers in a tabular format.
 
    Output:
    Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment
    ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- -------
    ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True
    ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False
    ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau...
 
    .NOTES
    This function provides essential information about domain controllers in the forest.
    #>

    [alias('Get-WinADDomainControllers')]
    [CmdletBinding()]
    param(
        [string[]] $Domain,
        [switch] $TestAvailability,
        [switch] $SkipEmpty,
        [pscredential] $Credential
    )
    try {
        if ($Credential) {
            $Forest = Get-ADForest -Credential $Credential
        }
        else {
            $Forest = Get-ADForest
        }
        if (-not $Domain) {
            $Domain = $Forest.Domains
        }
    }
    catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage"
        return
    }
    $Servers = foreach ($D in $Domain) {
        try {
            $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop -Writable
            if ($Credential) {
                $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Credential $Credential -Filter * -ErrorAction Stop
            }
            else {
                $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop 
            }
            foreach ($S in $DC) {
                $Server = [ordered] @{
                    Domain               = $D
                    HostName             = $S.HostName
                    Name                 = $S.Name
                    Forest               = $Forest.RootDomain
                    IPV4Address          = $S.IPV4Address
                    IPV6Address          = $S.IPV6Address
                    IsGlobalCatalog      = $S.IsGlobalCatalog
                    IsReadOnly           = $S.IsReadOnly
                    Site                 = $S.Site
                    SchemaMaster         = ($S.OperationMasterRoles -contains 'SchemaMaster')
                    DomainNamingMaster   = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                    PDCEmulator          = ($S.OperationMasterRoles -contains 'PDCEmulator')
                    RIDMaster            = ($S.OperationMasterRoles -contains 'RIDMaster')
                    InfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                    LdapPort             = $S.LdapPort
                    SslPort              = $S.SslPort
                    Pingable             = $null
                    Comment              = ''
                }
                if ($TestAvailability) {
                    $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) {
                        Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue
                    }
                }
                [PSCustomObject] $Server
            }
        }
        catch {
            [PSCustomObject]@{
                Domain                   = $D
                HostName                 = ''
                Name                     = ''
                Forest                   = $Forest.RootDomain
                IPV4Address              = ''
                IPV6Address              = ''
                IsGlobalCatalog          = ''
                IsReadOnly               = ''
                Site                     = ''
                SchemaMaster             = $false
                DomainNamingMasterMaster = $false
                PDCEmulator              = $false
                RIDMaster                = $false
                InfrastructureMaster     = $false
                LdapPort                 = ''
                SslPort                  = ''
                Pingable                 = $null
                Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            }
        }
    }
    if ($SkipEmpty) {
        return $Servers | Where-Object { $_.HostName -ne '' }
    }
    return $Servers
}

function Get-WinADForestDetails {
    <#
    .SYNOPSIS
    Get details about Active Directory Forest, Domains and Domain Controllers in a single query
 
    .DESCRIPTION
    Get details about Active Directory Forest, Domains and Domain Controllers in a single query
 
    .PARAMETER Forest
    Target different Forest, by default current forest is used
 
    .PARAMETER ExcludeDomains
    Exclude domain from search, by default whole forest is scanned
 
    .PARAMETER IncludeDomains
    Include only specific domains, by default whole forest is scanned
 
    .PARAMETER ExcludeDomainControllers
    Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER IncludeDomainControllers
    Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored.
 
    .PARAMETER SkipRODC
    Skip Read-Only Domain Controllers. By default all domain controllers are included.
 
    .PARAMETER ExtendedForestInformation
    Ability to provide Forest Information from another command to speed up processing
 
    .PARAMETER Filter
    Filter for Get-ADDomainController
 
    .PARAMETER TestAvailability
    Check if Domain Controllers are available
 
    .PARAMETER Test
    Pick what to check for availability. Options are: All, Ping, WinRM, PortOpen, Ping+WinRM, Ping+PortOpen, WinRM+PortOpen. Default is All
 
    .PARAMETER Ports
    Ports to check for availability. Default is 135
 
    .PARAMETER PortsTimeout
    Ports timeout for availability check. Default is 100
 
    .PARAMETER PingCount
    How many pings to send. Default is 1
 
    .PARAMETER PreferWritable
    Prefer writable domain controllers over read-only ones when returning Query Servers
 
    .PARAMETER Extended
    Return extended information about domains with NETBIOS names
 
    .EXAMPLE
    Get-WinADForestDetails | Format-Table
 
    .EXAMPLE
    Get-WinADForestDetails -Forest 'ad.evotec.xyz' | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [string[]] $ExcludeDomainControllers,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers,
        [switch] $SkipRODC,
        [string] $Filter = '*',
        [switch] $TestAvailability,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1,
        [switch] $PreferWritable,
        [switch] $Extended,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }

    if (-not $ExtendedForestInformation) {

        $Findings = [ordered] @{ }
        try {
            if ($Forest) {
                $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest
            }
            else {
                $ForestInformation = Get-ADForest -ErrorAction Stop
            }
        }
        catch {
            Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)"
            return
        }
        if (-not $ForestInformation) {
            return
        }
        $Findings['Forest'] = $ForestInformation
        $Findings['ForestDomainControllers'] = @()
        $Findings['QueryServers'] = @{ }
        $Findings['DomainDomainControllers'] = @{ }
        [Array] $Findings['Domains'] = foreach ($Domain in $ForestInformation.Domains) {
            if ($IncludeDomains) {
                if ($Domain -in $IncludeDomains) {
                    $Domain.ToLower()
                }

                continue
            }
            if ($Domain -notin $ExcludeDomains) {
                $Domain.ToLower()
            }
        }

        [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) {
            try {
                $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop -Writable:$PreferWritable.IsPresent

                $OrderedDC = [ordered] @{
                    Domain      = $DC.Domain
                    Forest      = $DC.Forest
                    HostName    = [Array] $DC.HostName
                    IPv4Address = $DC.IPv4Address
                    IPv6Address = $DC.IPv6Address
                    Name        = $DC.Name
                    Site        = $DC.Site
                }
            }
            catch {
                Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)"
                continue
            }
            if ($Domain -eq $Findings['Forest']['Name']) {
                $Findings['QueryServers']['Forest'] = $OrderedDC
            }
            $Findings['QueryServers']["$Domain"] = $OrderedDC

            $Domain
        }

        [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) {
            if ($Domain -notin $DomainsActive) {
                Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping."
                continue
            }
            $Domain
        }

        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0]

            [Array] $AllDC = try {
                try {
                    $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop
                }
                catch {
                    Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)"
                    continue
                }
                foreach ($S in $DomainControllers) {
                    if ($IncludeDomainControllers.Count -gt 0) {
                        If (-not $IncludeDomainControllers[0].Contains('.')) {
                            if ($S.Name -notin $IncludeDomainControllers) {
                                continue
                            }
                        }
                        else {
                            if ($S.HostName -notin $IncludeDomainControllers) {
                                continue
                            }
                        }
                    }
                    if ($ExcludeDomainControllers.Count -gt 0) {
                        If (-not $ExcludeDomainControllers[0].Contains('.')) {
                            if ($S.Name -in $ExcludeDomainControllers) {
                                continue
                            }
                        }
                        else {
                            if ($S.HostName -in $ExcludeDomainControllers) {
                                continue
                            }
                        }
                    }

                    $DSAGuid = (Get-ADObject -Identity $S.NTDSSettingsObjectDN -Server $QueryServer).ObjectGUID
                    $Server = [ordered] @{
                        Domain                 = $Domain
                        HostName               = $S.HostName
                        Name                   = $S.Name
                        Forest                 = $ForestInformation.RootDomain
                        Site                   = $S.Site
                        IPV4Address            = $S.IPV4Address
                        IPV6Address            = $S.IPV6Address
                        IsGlobalCatalog        = $S.IsGlobalCatalog
                        IsReadOnly             = $S.IsReadOnly
                        IsSchemaMaster         = ($S.OperationMasterRoles -contains 'SchemaMaster')
                        IsDomainNamingMaster   = ($S.OperationMasterRoles -contains 'DomainNamingMaster')
                        IsPDC                  = ($S.OperationMasterRoles -contains 'PDCEmulator')
                        IsRIDMaster            = ($S.OperationMasterRoles -contains 'RIDMaster')
                        IsInfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster')
                        OperatingSystem        = $S.OperatingSystem
                        OperatingSystemVersion = $S.OperatingSystemVersion
                        OperatingSystemLong    = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion
                        LdapPort               = $S.LdapPort
                        SslPort                = $S.SslPort
                        DistinguishedName      = $S.ComputerObjectDN
                        NTDSSettingsObjectDN   = $S.NTDSSettingsObjectDN
                        DsaGuid                = $DSAGuid
                        DsaGuidName            = "$DSAGuid._msdcs.$($ForestInformation.RootDomain)"
                        Pingable               = $null
                        WinRM                  = $null
                        PortOpen               = $null
                        Comment                = ''
                    }
                    if ($TestAvailability) {
                        if ($Test -eq 'All' -or $Test -like 'Ping*') {
                            $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount
                        }
                        if ($Test -eq 'All' -or $Test -like '*WinRM*') {
                            $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status
                        }
                        if ($Test -eq 'All' -or '*PortOpen*') {
                            $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status
                        }
                    }
                    [PSCustomObject] $Server
                }
            }
            catch {
                [PSCustomObject]@{
                    Domain                   = $Domain
                    HostName                 = ''
                    Name                     = ''
                    Forest                   = $ForestInformation.RootDomain
                    IPV4Address              = ''
                    IPV6Address              = ''
                    IsGlobalCatalog          = ''
                    IsReadOnly               = ''
                    Site                     = ''
                    SchemaMaster             = $false
                    DomainNamingMasterMaster = $false
                    PDCEmulator              = $false
                    RIDMaster                = $false
                    InfrastructureMaster     = $false
                    LdapPort                 = ''
                    SslPort                  = ''
                    DistinguishedName        = ''
                    NTDSSettingsObjectDN     = ''
                    DsaGuid                  = ''
                    DsaGuidName              = ''
                    Pingable                 = $null
                    WinRM                    = $null
                    PortOpen                 = $null
                    Comment                  = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                }
            }
            if ($SkipRODC) {
                [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false }
            }
            else {
                [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC
            }

            if ($null -ne $Findings['DomainDomainControllers'][$Domain]) {
                [Array] $Findings['DomainDomainControllers'][$Domain]
            }
        }
        if ($Extended) {
            $Findings['DomainsExtended'] = @{ }
            $Findings['DomainsExtendedNetBIOS'] = @{ }
            foreach ($DomainEx in $Findings['Domains']) {
                try {

                    $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object {

                        [ordered] @{
                            AllowedDNSSuffixes                 = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ }                
                            ChildDomains                       = $_.ChildDomains | ForEach-Object -Process { $_ }                      
                            ComputersContainer                 = $_.ComputersContainer                 
                            DeletedObjectsContainer            = $_.DeletedObjectsContainer            
                            DistinguishedName                  = $_.DistinguishedName                  
                            DNSRoot                            = $_.DNSRoot                            
                            DomainControllersContainer         = $_.DomainControllersContainer         
                            DomainMode                         = $_.DomainMode                         
                            DomainSID                          = $_.DomainSID.Value                        
                            ForeignSecurityPrincipalsContainer = $_.ForeignSecurityPrincipalsContainer 
                            Forest                             = $_.Forest                             
                            InfrastructureMaster               = $_.InfrastructureMaster               
                            LastLogonReplicationInterval       = $_.LastLogonReplicationInterval       
                            LinkedGroupPolicyObjects           = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ }           
                            LostAndFoundContainer              = $_.LostAndFoundContainer              
                            ManagedBy                          = $_.ManagedBy                          
                            Name                               = $_.Name                               
                            NetBIOSName                        = $_.NetBIOSName                        
                            ObjectClass                        = $_.ObjectClass                        
                            ObjectGUID                         = $_.ObjectGUID                         
                            ParentDomain                       = $_.ParentDomain                       
                            PDCEmulator                        = $_.PDCEmulator                        
                            PublicKeyRequiredPasswordRolling   = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ }   
                            QuotasContainer                    = $_.QuotasContainer                    
                            ReadOnlyReplicaDirectoryServers    = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ }    
                            ReplicaDirectoryServers            = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ }           
                            RIDMaster                          = $_.RIDMaster                          
                            SubordinateReferences              = $_.SubordinateReferences | ForEach-Object -Process { $_ }            
                            SystemsContainer                   = $_.SystemsContainer                   
                            UsersContainer                     = $_.UsersContainer                     
                        }
                    }

                    $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName']
                    $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx]
                }
                catch {
                    Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)"
                    continue
                }
            }
        }

        if ($TemporaryProgress) {
            $Global:ProgressPreference = $TemporaryProgress
        }

        $Findings
    }
    else {

        $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation
        [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) {
            if ($IncludeDomains) {
                if ($_ -in $IncludeDomains) {
                    $_.ToLower()
                }

                continue
            }
            if ($_ -notin $ExcludeDomains) {
                $_.ToLower()
            }
        }

        foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) {
            if ($_ -notin $Findings.Domains) {
                $Findings.DomainDomainControllers.Remove($_)
            }
        }

        foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) {
            if ($_ -notin $Findings.Domains) {
                $Findings.DomainsExtended.Remove($_)
                $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName'
                if ($NetBiosName) {
                    $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName)
                }
            }
        }
        [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) {
            [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) {
                if ($IncludeDomainControllers.Count -gt 0) {
                    If (-not $IncludeDomainControllers[0].Contains('.')) {
                        if ($S.Name -notin $IncludeDomainControllers) {
                            continue
                        }
                    }
                    else {
                        if ($S.HostName -notin $IncludeDomainControllers) {
                            continue
                        }
                    }
                }
                if ($ExcludeDomainControllers.Count -gt 0) {
                    If (-not $ExcludeDomainControllers[0].Contains('.')) {
                        if ($S.Name -in $ExcludeDomainControllers) {
                            continue
                        }
                    }
                    else {
                        if ($S.HostName -in $ExcludeDomainControllers) {
                            continue
                        }
                    }
                }
                $S
            }
            if ($SkipRODC) {
                [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false }
            }
            else {
                [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC
            }

            [Array] $Findings['DomainDomainControllers'][$Domain]
        }
        $Findings
    }
}
Function Get-WinADForestOptions {
    <#
    .SYNOPSIS
        This Cmdlet gets Active Directory Site Options.
    .DESCRIPTION
        This Cmdlet gets Active Directory Site Options.
        We can fill out the rest of this comment-based help later.
    .LINK
        http://myotherpcisacloud.com
 
    .LINK
        https://serverfault.com/questions/543143/detecting-ad-site-options-using-powershell
 
    .NOTES
        Written by Ryan Ries, October 2013. ryanries09@gmail.com.
    #>

    [CmdletBinding()]
    Param(
        [string] $Domain = $Env:USERDNSDOMAIN
    )
    BEGIN {

        Add-Type -TypeDefinition @"
                                   [System.Flags]
                                   public enum nTDSSiteSettingsFlags {
                                   NTDSSETTINGS_OPT_IS_AUTO_TOPOLOGY_DISABLED = 0x00000001,
                                   NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED = 0x00000002,
                                   NTDSSETTINGS_OPT_IS_TOPL_MIN_HOPS_DISABLED = 0x00000004,
                                   NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008,
                                   NTDSSETTINGS_OPT_IS_INTER_SITE_AUTO_TOPOLOGY_DISABLED = 0x00000010,
                                   NTDSSETTINGS_OPT_IS_GROUP_CACHING_ENABLED = 0x00000020,
                                   NTDSSETTINGS_OPT_FORCE_KCC_WHISTLER_BEHAVIOR = 0x00000040,
                                   NTDSSETTINGS_OPT_FORCE_KCC_W2K_ELECTION = 0x00000080,
                                   NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED = 0x00000100,
                                   NTDSSETTINGS_OPT_IS_SCHEDULE_HASHING_ENABLED = 0x00000200,
                                   NTDSSETTINGS_OPT_IS_REDUNDANT_SERVER_TOPOLOGY_ENABLED = 0x00000400,
                                   NTDSSETTINGS_OPT_W2K3_IGNORE_SCHEDULES = 0x00000800,
                                   NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000 }
"@


        if ($Domain) {
            $RootDSE = Get-ADRootDSE -Server $Domain
        }
        else {
            $RootDSE = Get-ADRootDSE
        }
        $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $RootDSE.defaultNamingContext -ToDomainCN
        $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0]

        $Sites = Get-ADObject -Filter 'objectClass -eq "site"' -SearchBase ($RootDSE).ConfigurationNamingContext -Server $QueryServer
        ForEach ($Site In $Sites) {
            $SiteSettings = Get-ADObject "CN=NTDS Site Settings,$($Site.DistinguishedName)" -Properties Options -Server $QueryServer
            If (!$SiteSettings.PSObject.Properties.Match('Options').Count -OR $SiteSettings.Options -EQ 0) {

                [PSCustomObject]@{
                    SiteName          = $Site.Name
                    DistinguishedName = $Site.DistinguishedName
                    SiteOptions       = '(none)'
                }
            }
            Else {
                [PSCustomObject]@{
                    SiteName          = $Site.Name
                    DistinguishedName = $Site.DistinguishedName
                    SiteOptions       = [Enum]::Parse('nTDSSiteSettingsFlags', $SiteSettings.Options)
                }
            }
        }
    }
}
function Get-WinADOrganizationalUnitData {
    <#
    .SYNOPSIS
    Retrieves detailed information about Active Directory Organizational Units.
 
    .DESCRIPTION
    This function retrieves detailed information about the specified Active Directory Organizational Units, including properties like CanonicalName, City, Country, Description, and more.
 
    .PARAMETER OrganizationalUnit
    Specifies the Organizational Units to retrieve information for.
 
    .EXAMPLE
    Get-WinADOrganizationalUnitData -OrganizationalUnit 'OU=Users-O365,OU=Production,DC=ad,DC=evotec,DC=xyz'
    Retrieves information for the specified Organizational Unit 'Users-O365' under 'Production' in the Active Directory domain 'ad.evotec.xyz'.
 
    .NOTES
    Output of function:
        CanonicalName : ad.evotec.xyz/Production/Users-O365
        City :
        CN :
        Country : PL
        Created : 09.11.2018 17:38:32
        Description : OU for Synchronization of Users to Office 365
        DisplayName :
        DistinguishedName : OU=Users-O365,OU=Production,DC=ad,DC=evotec,DC=xyz
        LinkedGroupPolicyObjects : {cn={74D09C6F-35E9-4743-BCF7-F87D7010C60D},cn=policies,cn=system,DC=ad,DC=evotec,DC=xyz}
        ManagedBy :
        Modified : 19.11.2018 22:54:47
        Name : Users-O365
        PostalCode :
        ProtectedFromAccidentalDeletion : True
        State :
        StreetAddress :
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $OrganizationalUnit
    )
    $Output = foreach ($OU in $OrganizationalUnit) {
        $Data = Get-ADOrganizationalUnit -Identity $OU -Properties CanonicalName, City, CN, Country, Created, Description, DisplayName, DistinguishedName, ManagedBy, Modified, Name, OU, PostalCode, ProtectedFromAccidentalDeletion, State, StreetAddress

        [PsCustomobject][Ordered] @{
            CanonicalName                   = $Data.CanonicalName
            City                            = $Data.City
            CN                              = $Data.CN
            Country                         = $Data.Country
            Created                         = $Data.Created
            Description                     = $Data.Description
            DisplayName                     = $Data.DisplayName
            DistinguishedName               = $Data.DistinguishedName
            LinkedGroupPolicyObjects        = $Data.LinkedGroupPolicyObjects
            ManagedBy                       = Get-WinADUsersByDN -DistinguishedName $U.ManagedBy
            Modified                        = $Data.Modified
            Name                            = $Data.Name
            PostalCode                      = $Data.PostalCode
            ProtectedFromAccidentalDeletion = $Data.ProtectedFromAccidentalDeletion
            State                           = $Data.State
            StreetAddress                   = $Data.StreetAddress
        }
    }
    return $Output
}
function Get-WinADOrganizationalUnitFromDN {
    <#
    .SYNOPSIS
    This function extracts the Organizational Unit (OU) from a given Distinguished Name (DN).
 
    .DESCRIPTION
    This function takes a Distinguished Name (DN) as input and returns the Organizational Unit (OU) part of it.
 
    .PARAMETER DistinguishedName
    Specifies the Distinguished Name (DN) from which to extract the Organizational Unit (OU).
 
    .EXAMPLE
    Extract the Organizational Unit (OU) from a Distinguished Name.
 
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    Get-WinADOrganizationalUnitFromDN -DistinguishedName $DistinguishedName
 
    .NOTES
    This function uses regular expressions to extract the Organizational Unit (OU) from the given Distinguished Name (DN).
    #>

    [CmdletBinding()]
    param(
        $DistinguishedName
    )
    return [Regex]::Match($DistinguishedName, '(?=OU)(.*\n?)(?<=.)').Value
}
function Get-WinADUsersByDN {
    <#
    .SYNOPSIS
    Retrieves Active Directory user information based on the provided DistinguishedName(s).
 
    .DESCRIPTION
    This function retrieves Active Directory user information based on the provided DistinguishedName(s). It returns specific user properties for the given DistinguishedName(s).
 
    .PARAMETER DistinguishedName
    Specifies the DistinguishedName(s) of the user(s) to retrieve information for.
 
    .PARAMETER Field
    Specifies the specific field to return for each user. Default value is 'DisplayName'.
 
    .PARAMETER All
    Indicates whether to return all properties of the user(s).
 
    .EXAMPLE
    Get-WinADUsersByDN -DistinguishedName "CN=John Doe,OU=Users,DC=contoso,DC=com"
    Retrieves the DisplayName of the user with the specified DistinguishedName.
 
    .EXAMPLE
    Get-WinADUsersByDN -DistinguishedName "CN=Jane Smith,OU=Users,DC=contoso,DC=com" -Field "EmailAddress"
    Retrieves the EmailAddress of the user with the specified DistinguishedName.
 
    .EXAMPLE
    Get-WinADUsersByDN -DistinguishedName "CN=Admin,OU=Users,DC=contoso,DC=com" -All
    Retrieves all properties of the user with the specified DistinguishedName.
 
    #>

    param(
        [alias('DN')][string[]]$DistinguishedName,
        [string] $Field = 'DisplayName', # return field
        [switch] $All
    )
    $Properties = 'DistinguishedName', 'Enabled', 'GivenName', 'Name', 'SamAccountName', 'SID', 'Surname', 'UserPrincipalName', 'EmailAddress', 'DisplayName'

    $Users = foreach ($DN in $DistinguishedName) {
        try {
            Get-ADUser -Identity $DN -Properties $Properties
        }
        catch {
        }
    }

    if ($All) {
        return $Users 
    }
    else {
        return $Users.$Field
    }
}
function Get-WinADUsersByOU {
    <#
    .SYNOPSIS
    Retrieves Active Directory users within a specified Organizational Unit.
 
    .DESCRIPTION
    This function retrieves Active Directory users within the specified Organizational Unit.
 
    .PARAMETER OrganizationalUnit
    Specifies the Organizational Unit from which to retrieve users.
 
    .EXAMPLE
    Get-WinADUsersByOU -OrganizationalUnit "OU=Sales,DC=Contoso,DC=com"
    Retrieves all users within the Sales Organizational Unit in the Contoso domain.
 
    #>

    [CmdletBinding()]
    param (
        $OrganizationalUnit
    )
    $OU = Get-ADOrganizationalUnit $OrganizationalUnit
    if ($OU.ObjectClass -eq 'OrganizationalUnit') {
        try {
            $Users = Get-ADUser -SearchBase $OU -Filter * -Properties $Script:UserProperties
        }
        catch {
            Write-Color @Script:WriteParameters -Text '[i]', ' One or more properties are invalid - Terminating', ' Terminating' -Color Yellow, White, Red
            return
        }
    }
    return $Users
}
function Get-WinADUserSnapshot {
    <#
    .SYNOPSIS
    Retrieves a snapshot of Active Directory user information and saves it to an XML file.
 
    .DESCRIPTION
    The Get-WinADUserSnapshot function retrieves detailed information about an Active Directory user and saves it to an XML file specified by the XmlPath parameter.
 
    .PARAMETER User
    Specifies the Active Directory user object for which to retrieve the snapshot.
 
    .PARAMETER XmlPath
    Specifies the path where the XML snapshot file will be saved.
 
    .PARAMETER WhatIf
    Indicates whether the operation should only be simulated without actually saving the snapshot.
 
    .EXAMPLE
    Get-WinADUserSnapshot -User $userObject -XmlPath "C:\Snapshots" -WhatIf
    Retrieves a snapshot of the user object and simulates saving it to the specified path.
 
    .EXAMPLE
    Get-WinADUserSnapshot -User $userObject -XmlPath "C:\Snapshots"
    Retrieves a snapshot of the user object and saves it to the specified path.
 
    #>

    [CmdletBinding()]
    [alias("Get-ADUserSnapshot")]
    param (
        [parameter(Mandatory = $true)][Object] $User,
        [string] $XmlPath,
        [switch] $WhatIf
    )
    $Object = @()
    try {
        $FullData = Get-ADUser -Identity $User.DistinguishedName -Properties *
        if (($XmlPath) -and (Test-Path $XmlPath)) {
            $FullPath = [IO.Path]::Combine($XmlPath, "$($User.SamAccountName).xml") 
            if (-not $WhatIf) {
                $FullData | Export-Clixml -Path $FullPath -ErrorAction Stop
            }
            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Saved to $FullPath" }
        }
        else {
            $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = 'XmlPath Incorrect' }
        }
    }
    catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
    }
    return $Object
}
function Remove-WinADUserGroups {
    <#
    .SYNOPSIS
    Removes specified Active Directory groups from a user.
 
    .DESCRIPTION
    This function removes specified Active Directory groups from a user account. It provides options to remove groups based on category, scope, or all groups.
 
    .PARAMETER User
    The user object from which groups will be removed.
 
    .PARAMETER GroupCategory
    Specifies the category of groups to remove. Valid values are "Distribution" and "Security".
 
    .PARAMETER GroupScope
    Specifies the scope of groups to remove. Valid values are "DomainLocal", "Global", and "Universal".
 
    .PARAMETER Groups
    An array of specific group names to remove.
 
    .PARAMETER All
    If specified, removes all groups from the user.
 
    .PARAMETER WhatIf
    Shows what would happen if the command runs without actually running it.
 
    .EXAMPLE
    Remove-WinADUserGroups -User $User -All
    Removes all groups from the specified user account.
 
    .EXAMPLE
    Remove-WinADUserGroups -User $User -GroupCategory "Security" -GroupScope "Global"
    Removes all security groups with a global scope from the specified user account.
    #>

    [CmdletBinding()]
    [alias("Remove-ADUserGroups")]
    param(
        [parameter(Mandatory = $true)][Object] $User,
        [ValidateSet("Distribution", "Security")][String] $GroupCategory ,
        [ValidateSet("DomainLocal", "Global", "Universal")][String] $GroupScope,
        [string[]] $Groups,
        [switch] $All,
        [switch] $WhatIf
    )
    $Object = @()
    try {
        $ADgroups = Get-ADPrincipalGroupMembership -Identity $User.DistinguishedName -ErrorAction Stop | Where-Object { $_.Name -ne "Domain Users" }
    }
    catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
    }
    if ($ADgroups) {
        if ($All) {

            foreach ($Group in $ADgroups) {
                try {
                    if (-not $WhatIf) {
                        Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop
                    }
                    $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' }
                }
                catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                }
            }
        }
        if ($GroupCategory) {
            $ADGroupsByCategory = $ADgroups | Where-Object { $_.GroupCategory -eq $GroupCategory }
            if ($ADGroupsByCategory) {

                foreach ($Group in $ADGroupsByCategory) {
                    try {
                        if (-not $WhatIf) {
                            Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop
                        }
                        $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' }
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                    }
                }
            }
        }
        if ($GroupScope) {
            $ADGroupsByScope = $ADgroups | Where-Object { $_.GroupScope -eq $GroupScope }
            if ($ADGroupsByScope) {

                foreach ($Group in $ADGroupsByScope) {
                    try {
                        if (-not $WhatIf) {
                            Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop
                        }
                        $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' }
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                    }
                }
            }
        }
        if ($Groups) {
            foreach ($Group in $Groups) {
                $ADGroupsByName = $ADgroups | Where-Object { $_.Name -like $Group }
                if ($ADGroupsByName) {

                    try {
                        if (-not $WhatIf) {
                            Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $ADGroupsByName -Confirm:$false -ErrorAction Stop
                        }
                        $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' }
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                    }
                }
                else {
                    $Object += @{ Status = $false; Output = $Group.Name; Extended = 'Not available on user.' }
                }
            }
        }
    }
    return $Object
}

function Set-WinADGroupSynchronization {
    <#
    .SYNOPSIS
    Sets up synchronization between two Active Directory groups.
 
    .DESCRIPTION
    This function sets up synchronization between two Active Directory groups by copying members from one group to another based on specified criteria.
 
    .PARAMETER GroupFrom
    The name of the source Active Directory group from which members will be synchronized.
 
    .PARAMETER GroupTo
    The name of the target Active Directory group to which members will be synchronized.
 
    .PARAMETER Type
    Specifies the type of members to synchronize. Valid values are 'User', 'Group', or 'All'. Default is 'User'.
 
    .PARAMETER Recursive
    Specifies the type of synchronization to perform. Valid values are 'None', 'RecursiveFrom', 'RecursiveBoth', or 'RecursiveTo'. Default is 'None'.
 
    .PARAMETER WhatIf
    Shows what would happen if the synchronization is performed without actually performing it.
 
    .EXAMPLE
    Set-WinADGroupSynchronization -GroupFrom 'GDS-TestGroup1' -GroupTo 'GDS-TestGroup2' -Type 'All' -Recursive None
    Synchronizes all members from 'GDS-TestGroup1' to 'GDS-TestGroup2' without recursion.
 
    .EXAMPLE
    Set-WinADGroupSynchronization -GroupFrom 'GDS-TestGroup1' -GroupTo 'GDS-TestGroup2' -Type 'User' -Recursive RecursiveBoth
    Synchronizes only user members from 'GDS-TestGroup1' to 'GDS-TestGroup2' with recursion in both groups.
 
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true)][string] $GroupFrom,
        [parameter(Mandatory = $true)][string] $GroupTo,
        [parameter(Mandatory = $false)][ValidateSet("User", "Group", "All")][string] $Type = 'User',
        [parameter(Mandatory = $false)][ValidateSet("None", "RecursiveFrom", "RecursiveBoth", "RecursiveTo")] $Recursive = 'None',
        [switch] $WhatIf
    )
    Begin {
        $Object = @()
        if ($Recursive -eq 'None') {
            $GroupFromRecursive = $false
            $GroupToRecursive = $false
        }
        elseif ($Recursive -eq 'RecursiveFrom') {
            $GroupFromRecursive = $true
            $GroupToRecursive = $false
        }
        elseif ($Recursive -eq 'RecursiveBoth') {
            $GroupFromRecursive = $true
            $GroupToRecursive = $true
        }
        else {
            $GroupFromRecursive = $false
            $GroupToRecursive = $true
        }
    }
    Process {
        try {

            $GroupMembersFrom = Get-ADGroupMember -Identity $GroupFrom -Recursive:$GroupFromRecursive | Select-Object Name, ObjectClass, SamAccountName, UserPrincipalName
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
        }
        try {
            $GroupMembersTo = Get-ADGroupMember -Identity $GroupTo -Recursive:$GroupToRecursive | Select-Object Name, ObjectClass, SamAccountName, UserPrincipalName
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
        }
        if ($Object.Count -gt 0) {

            return $Object
        }

        foreach ($User in $GroupMembersFrom) {
            if ($User.ObjectClass -eq "user") {
                if ($Type -eq 'User' -or $Type -eq 'All') {
                    if ($GroupMembersTo.SamAccountName -notcontains $User.SamAccountName) {

                        try {
                            if (-not $WhatIf) {
                                Add-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Added to group $GroupTo" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                        }
                    }
                }
            }
            else {
                if ($Type -eq 'Group' -or $Type -eq 'All') {
                    if ($GroupMembersTo.SamAccountName -notcontains $User.SamAccountName) {

                        try {
                            if (-not $WhatIf) {
                                Add-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Added to group $GroupTo" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                        }
                    }
                }
            }
        }
        foreach ($User in $GroupMembersTo) {
            if ($User.ObjectClass -eq "user") {
                if ($Type -eq 'User' -or $Type -eq 'All') {
                    if ($GroupMembersFrom.SamAccountName -notcontains $User.SamAccountName) {
                        Write-Color "Not a member of $GroupFrom - requires removal from $GroupTo ", $User.SamAccountName -Color Red -LogFile $LogFile
                        try {
                            if (-not $WhatIf) {
                                Remove-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName -Confirm:$false
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Removed from group $GroupTo" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                        }
                    }
                }
            }
            else {
                if ($Type -eq 'Group' -or $Type -eq 'All') {
                    if ($GroupMembersFrom.SamAccountName -notcontains $User.SamAccountName) {
                        Write-Color "Not a member of $GroupFrom - requires removal from $GroupTo ", $User.SamAccountName -Color Red -LogFile $LogFile
                        try {
                            if (-not $WhatIf) {
                                Remove-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName -Confirm:$false
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Removed from group $GroupTo" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage }
                        }
                    }
                }
            }
        }
    }
    End {
        return $object
    }
}
function Set-WinADUserFields {
    <#
    .SYNOPSIS
    Sets specified fields for an Active Directory user with additional options.
 
    .DESCRIPTION
    This function allows setting specified fields for an Active Directory user with the option to add text before or after the existing field value.
 
    .PARAMETER User
    Specifies the Active Directory user object to modify.
 
    .PARAMETER Option
    Specifies whether to add the text before or after the existing field value. Valid values are 'Before' and 'After'.
 
    .PARAMETER TextToAdd
    Specifies the text to add before or after the existing field value.
 
    .PARAMETER TextToRemove
    Specifies the text to remove from the existing field value.
 
    .PARAMETER Fields
    Specifies an array of fields to modify for the user.
 
    .PARAMETER WhatIf
    Indicates that the cmdlet should display what changes would occur without actually making any changes.
 
    .EXAMPLE
    Set-WinADUserFields -User $user -Option 'After' -TextToAdd 'New' -Fields 'Name'
    Adds the text 'New' after the existing 'Name' field value for the specified user.
 
    .EXAMPLE
    Set-WinADUserFields -User $user -Option 'Before' -TextToAdd 'Old' -Fields 'Description'
    Adds the text 'Old' before the existing 'Description' field value for the specified user.
    #>

    [CmdletBinding()]
    [alias("Set-ADUserName")]
    param (
        [parameter(Mandatory = $true)][Object] $User,
        [parameter(Mandatory = $false)][ValidateSet("Before", "After")][String] $Option,
        [string] $TextToAdd,
        [string] $TextToRemove,
        [string[]] $Fields,
        [switch] $WhatIf
    )
    $Object = @()
    if ($TextToAdd) {
        foreach ($Field in $Fields) {
            if ($User.$Field -notlike "*$TextToAdd*") {

                if ($Option -eq 'After') {
                    $NewName = "$($User.$Field)$TextToAdd"
                }
                elseif ($Option -eq 'Before') {
                    $NewName = "$TextToAdd$($User."$Field")"
                }
                if ($NewName -ne $User.$Field) {
                    if ($Field -eq 'Name') {
                        try {
                            if (-not $WhatIf) {
                                Rename-ADObject -Identity $User.DistinguishedName -NewName $NewName 
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed account '$Field' to '$NewName'" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
                        }
                    }
                    else {
                        $Splat = @{
                            Identity = $User.DistinguishedName
                            "$Field" = $NewName
                        }
                        try {
                            if (-not $WhatIf) {
                                Set-ADUser @Splat
                            }
                            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed field '$Field' to '$NewName'" }
                        }
                        catch {
                            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                            $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
                        }
                    }
                }
            }
        }
    }
    if ($TextToRemove) {
        foreach ($Field in $Fields) {
            if ($User.$Field -like "*$TextToRemove*") {
                $NewName = $($User.$Field).Replace($TextToRemove, '')
                if ($Field -eq 'Name') {
                    try {
                        if (-not $WhatIf) {
                            Rename-ADObject -Identity $User.DistinguishedName -NewName $NewName 
                        }
                        $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed account '$Field' to '$NewName'" }
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = "Field: '$Field' Error: '$ErrorMessage'" }
                    }
                }
                else {
                    $Splat = @{
                        Identity = $User.DistinguishedName
                        "$Field" = $NewName
                    }
                    try {
                        if (-not $WhatIf) {
                            Set-ADUser @Splat 
                        }
                        $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed field $Field to $NewName" }
                    }
                    catch {
                        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                        $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = "Field: $Field Error: $ErrorMessage" }
                    }
                }
            }
        }
    }
    return $Object
}
Function Set-WinADUserSettingGAL {
    <#
    .SYNOPSIS
    Sets the Exchange Global Address List (GAL) visibility for a specified user.
 
    .DESCRIPTION
    This function allows you to hide or show a user in the Global Address List (GAL) of Exchange.
    It updates the msExchHideFromAddressLists attribute of the user object in Active Directory.
 
    .PARAMETER User
    Specifies the user object for which the GAL visibility needs to be set.
 
    .PARAMETER Option
    Specifies whether to 'Hide' or 'Show' the user in the GAL.
 
    .PARAMETER WhatIf
    Displays what would happen if the command runs without actually running the command.
 
    .EXAMPLE
    Set-WinADUserSettingGAL -User "JohnDoe" -Option "Hide"
    Hides the user "JohnDoe" from the Global Address List.
 
    .EXAMPLE
    Set-WinADUserSettingGAL -User "JaneSmith" -Option "Show"
    Shows the user "JaneSmith" in the Global Address List.
 
    #>

    [CmdletBinding()]
    [alias("Set-ADUserSettingGAL")]
    param (
        [parameter(Mandatory = $true)][Object] $User,
        [parameter(Mandatory = $true)][ValidateSet("Hide", "Show")][String]$Option,
        [switch] $WhatIf
    )
    $Object = @()
    if ($User) {
        if ($Option -eq 'Hide') {
            if (-not $User.msExchHideFromAddressLists) {

                try {
                    if (-not $WhatIf) {
                        Set-ADObject -Identity $User.DistinguishedName -Replace @{msExchHideFromAddressLists = $true }
                    }
                    $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Hidden from GAL.' }
                }
                catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
                }
            }
        }
        elseif ($Option -eq 'Show') {
            if ($User.msExchHideFromAddressLists) {

                try {
                    if ($WhatIf) {
                        Set-ADObject -Identity $User.DistinguishedName -Clear msExchHideFromAddressLists
                    }
                    $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Unhidden in GAL.' }
                }
                catch {
                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
                }
            }
        }
    }
    return $Object
}
function Set-WinADUserStatus {
    <#
    .SYNOPSIS
    Enables or disables a user account in Active Directory.
 
    .DESCRIPTION
    The Set-WinADUserStatus function enables or disables a specified user account in Active Directory based on the provided option. It also provides an option to simulate the action using the WhatIf switch.
 
    .PARAMETER User
    Specifies the user account to enable or disable.
 
    .PARAMETER Option
    Specifies whether to enable or disable the user account. Valid values are "Enable" or "Disable".
 
    .PARAMETER WhatIf
    Indicates that the cmdlet should display what would happen if it were to run, without actually performing any action.
 
    .EXAMPLE
    Set-WinADUserStatus -User $user -Option "Enable"
    Enables the user account specified by $user in Active Directory.
 
    .EXAMPLE
    Set-WinADUserStatus -User $user -Option "Disable" -WhatIf
    Displays what would happen if the user account specified by $user were to be disabled in Active Directory, without actually disabling it.
 
    #>

    [CmdletBinding()]
    [alias("Set-ADUserStatus")]
    param (
        [parameter(Mandatory = $true)][Object] $User,
        [parameter(Mandatory = $true)][ValidateSet("Enable", "Disable")][String] $Option,
        [switch] $WhatIf
        # $WriteParameters
    )
    $Object = @()
    if ($Option -eq 'Enable' -and $User.Enabled -eq $false) {

        try {
            if (-not $WhatIf) {
                Set-ADUser -Identity $User.DistinguishedName -Enabled $true
            }
            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Enabled user.' }
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
        }
    }
    elseif ($Option -eq 'Disable' -and $User.Enabled -eq $true) {

        try {
            if (-not $WhatIf) {
                Set-ADUser -Identity $User.DistinguishedName -Enabled $false
            }
            $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Disabled user.' }
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage }
        }
    }
    return $Object
}
function Get-CimData {
    <#
    .SYNOPSIS
    Helper function for retreiving CIM data from local and remote computers
 
    .DESCRIPTION
    Helper function for retreiving CIM data from local and remote computers
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM).
 
    .PARAMETER Protocol
    Specifies the protocol to use. The acceptable values for this parametDer are: DCOM, Default, or Wsman.
 
    .PARAMETER Class
    Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names.
 
    .PARAMETER Properties
    Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated.
 
    .PARAMETER NameSpace
    Specifies the namespace for the CIM operation. The default namespace is root\cimv2. You can use tab completion to browse the list of namespaces, because PowerShell gets a list of namespaces from the local WMI server to provide a list of namespaces.
 
    .PARAMETER Credential
    Specifies a user account that has permission to perform this action. The default is the current user.
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN
 
    .EXAMPLE
    Get-CimData -Class 'win32_bios'
 
    .EXAMPLE
    Get-CimClass to get all classes
 
    .NOTES
    General notes
    #>


    [CmdletBinding()]
    param(
        [parameter(Mandatory)][string] $Class,
        [string] $NameSpace = 'root\cimv2',
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [pscredential] $Credential,
        [string[]] $Properties = '*'
    )
    $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName'

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $CimObject = @(

        [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' }

        $Computers = $ComputersSplit[1]
        if ($Computers.Count -gt 0) {
            if ($Protocol -eq 'Default' -and $null -eq $Credential) {
                Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
            }
            else {
                $Option = New-CimSessionOption -Protocol $Protocol
                $newCimSessionSplat = @{
                    ComputerName  = $Computers
                    SessionOption = $Option
                    ErrorAction   = 'SilentlyContinue'
                }
                if ($Credential) {
                    $newCimSessionSplat['Credential'] = $Credential
                }
                $Session = New-CimSession @newCimSessionSplat -Verbose:$false
                if ($Session) {
                    Try {
                        $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction Stop -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                    }
                    catch {
                        Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)"
                    }
                    try {
                        $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue
                    }
                    catch {
                        Write-Warning -Message "Get-CimData - Failed to remove CimSession $($Session). Failed with errror: $($E.Exception.Message)"
                    }
                    $Info
                }
                else {
                    Write-Warning -Message "Get-CimData - Failed to create CimSession for $($Computers). Problem with credentials?"
                }
            }
            foreach ($E in $ErrorsToProcess) {
                Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)"
            }
        }
        else {

            $Computers = $ComputersSplit[0]
            if ($Computers.Count -gt 0) {
                $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties
                $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force
                $Info
            }
            foreach ($E in $ErrorsLocal) {
                Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)"
            }
        }
    )
    $CimObject
}
function Get-Computer {
    <#
    .SYNOPSIS
    Retrieves various information about a computer or server based on specified types.
 
    .DESCRIPTION
    This function retrieves information about a computer or server based on the specified types. It can gather details about applications, BIOS, CPU, RAM, Disk, Logical Disk, Network, Network Firewall, Operating System, Services, System, Startup, Time, and Windows Updates.
 
    .PARAMETER ComputerName
    Specifies the name of the computer or server to retrieve information from. Defaults to the local computer.
 
    .PARAMETER Type
    Specifies the types of information to retrieve. Valid values include 'Application', 'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical', 'Network', 'NetworkFirewall', 'OperatingSystem', 'Services', 'System', 'Startup', 'Time', and 'WindowsUpdates'. If not specified, retrieves all available types.
 
    .PARAMETER AsHashtable
    Indicates whether to return the output as a hashtable.
 
    .EXAMPLE
    Get-Computer -ComputerName "Server01" -Type "CPU", "RAM"
    Retrieves CPU and RAM information from a remote server named Server01.
 
    .EXAMPLE
    Get-Computer -ComputerName "Workstation01" -Type "Application" -AsHashtable
    Retrieves application information from a workstation named Workstation01 and returns the output as a hashtable.
 
    #>

    [cmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Application',
            'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical',
            'Network', 'NetworkFirewall',
            'OperatingSystem', 'Services', 'System', 'Startup', 'Time', 'WindowsUpdates'
        )][string[]] $Type,
        [switch] $AsHashtable
    )
    Begin {
    }
    Process {
        foreach ($Computer in $ComputerName) {
            $OutputObject = [ordered] @{}
            if ($Type -contains 'Application' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Application for $Computer"
                $Application = Get-ComputerApplication -ComputerName $Computer
                $OutputObject['Application'] = $Application
            }
            if ($Type -contains 'BIOS' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing BIOS for $Computer"
                $BIOS = Get-ComputerBios -ComputerName $Computer
                $OutputObject['BIOS'] = $BIOS
            }
            if ($Type -contains 'CPU' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing CPU for $Computer"
                $CPU = Get-ComputerCPU -ComputerName $Computer
                $OutputObject['CPU'] = $CPU
            }
            if ($Type -contains 'RAM' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing RAM for $Computer"
                $RAM = Get-ComputerRAM -ComputerName $Computer
                $OutputObject['RAM'] = $RAM
            }
            if ($Type -contains 'Disk' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Disk for $Computer"
                $Disk = Get-ComputerDisk -ComputerName $Computer
                $OutputObject['Disk'] = $Disk
            }
            if ($Type -contains 'DiskLogical' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing DiskLogical for $Computer"
                $DiskLogical = Get-ComputerDiskLogical -ComputerName $Computer
                $OutputObject['DiskLogical'] = $DiskLogical
            }
            if ($Type -contains 'OperatingSystem' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing OperatingSystem for $Computer"
                $OperatingSystem = Get-ComputerOperatingSystem -ComputerName $Computer
                $OutputObject['OperatingSystem'] = $OperatingSystem
            }
            if ($Type -contains 'Network' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Network for $Computer"
                $Network = Get-ComputerNetwork -ComputerName $Computer
                $OutputObject['Network'] = $Network
            }
            if ($Type -contains 'NetworkFirewall' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing NetworkFirewall for $Computer"
                $NetworkFirewall = Get-ComputerNetwork -ComputerName $Computer -NetworkFirewallOnly
                $OutputObject['NetworkFirewall'] = $NetworkFirewall
            }
            if ($Type -contains 'RDP' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing RDP for $Computer"
                $RDP = Get-ComputerRDP -ComputerName $Computer
                $OutputObject['RDP'] = $RDP
            }
            if ($Type -contains 'Services' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Services for $Computer"
                $Services = Get-ComputerService -ComputerName $Computer
                $OutputObject['Services'] = $Services
            }
            if ($Type -contains 'System' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing System for $Computer"
                $System = Get-ComputerSystem -ComputerName $Computer
                $OutputObject['System'] = $System
            }
            if ($Type -contains 'Startup' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Startup for $Computer"
                $Startup = Get-ComputerStartup -ComputerName $Computer
                $OutputObject['Startup'] = $Startup
            }
            if ($Type -contains 'Time' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Time for $Computer"
                $Time = Get-ComputerTime -TimeTarget $Computer
                $OutputObject['Time'] = $Time
            }
            if ($Type -contains 'Tasks' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing Tasks for $Computer"
                $Tasks = Get-ComputerTask -ComputerName $Computer
                $OutputObject['Tasks'] = $Tasks
            }
            if ($Type -contains 'WindowsUpdates' -or $null -eq $Type) {
                Write-Verbose "Get-Computer - Processing WindowsUpdates for $Computer"
                $WindowsUpdates = Get-ComputerWindowsUpdates -ComputerName $Computer
                $OutputObject['WindowsUpdates'] = $WindowsUpdates
            }
            if ($AsHashtable) {
                $OutputObject
            }
            else {
                [PSCustomObject] $OutputObject
            }
        }
    }
}
function Get-ComputerApplication {
    <#
    .SYNOPSIS
    Get software installed on computer or server
 
    .DESCRIPTION
    Get software installed on computer or server
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the operation.
 
    .EXAMPLE
    Get-ComputerApplications -Verbose | Format-Table
 
    .EXAMPLE
    Get-ComputerApplications -Verbose -ComputerName AD1, AD2 | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-ComputerApplications')]
    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME
    )
    $ScriptBlock = {
        $objapp1 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
        $objapp2 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*

        $app1 = $objapp1 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x64' }; Label = 'WindowsType' }
        $app2 = $objapp2 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x86' }; Label = 'WindowsType' } | Where-Object { -NOT (([string]$_.displayname).contains('Security Update for Microsoft') -or ([string]$_.displayname).contains('Update for Microsoft')) }
        $app = $app1 + $app2 
        $app | Where-Object { $null -ne $_.Displayname } 
    }
    foreach ($Computer in $ComputerName) {
        try {
            $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName
        }
        catch {
            $LocalComputerDNSName = $Computer
        }

        if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) {
            $Parameters = @{
                ScriptBlock = $ScriptBlock
            }
        }
        else {
            $Parameters = @{
                ComputerName = $Computer
                ScriptBlock  = $ScriptBlock
            }
        }
        try {
            $Data = Invoke-Command @Parameters
        }
        catch {
            Write-Warning "Get-ComputerApplication - No data for computer $Computer"
            continue
        }
        foreach ($Information in $Data) {
            if ($Information.Installdate) {
                try {
                    $InstallDate = [datetime]::ParseExact($Information.Installdate, 'yyyyMMdd', $null)
                }
                catch {
                    Write-Verbose "Get-ComputerApplication - InstallDate $($Information.Installdate) couldn't be converted."
                    $InstallDate = $null
                }
            }
            else {
                $InstallDate = $null
            }
            [PSCustomObject] @{
                DisplayName  = $Information.DisplayName
                Version      = $Information.DisplayVersion
                Publisher    = $Information.Publisher
                Installdate  = $InstallDate
                Type         = $Information.WindowsType
                ComputerName = $Computer
            }
        }
    }
}
function Get-ComputerBios {
    <#
    .SYNOPSIS
    Retrieves BIOS information from a remote or local computer.
 
    .DESCRIPTION
    This function retrieves BIOS information from a specified computer using CIM/WMI.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve BIOS information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for communication. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'.
 
    .PARAMETER All
    Switch parameter to retrieve all available BIOS properties.
 
    .EXAMPLE
    Get-ComputerBios -ComputerName "RemoteComputer" -Protocol Wsman
    Retrieves BIOS information from a remote computer using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerBios -All
    Retrieves all available BIOS information from the local computer.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_bios'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'PSComputerName', 'Status', 'Version', 'PrimaryBIOS', 'Manufacturer', 'ReleaseDate', 'SerialNumber', 'SMBIOSBIOSVersion', 'SMBIOSMajorVersion', 'SMBIOSMinorVersion', 'SystemBiosMajorVersion', 'SystemBiosMinorVersion'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Status       = $Data.Status
                    Version      = $Data.Version
                    VersionBIOS  = -join ($Data.SMBIOSMajorVersion, ".", $Data.SMBIOSMinorVersion, ".", $Data.SystemBiosMajorVersion, ".", $Data.SystemBiosMinorVersion)
                    PrimaryBIOS  = $Data.PrimaryBIOS
                    Manufacturer = $Data.Manufacturer
                    ReleaseDate  = $Data.ReleaseDate
                }
            }
        }
    }
}
function Get-ComputerCPU {
    <#
    .SYNOPSIS
    Retrieves CPU information from specified computers.
 
    .DESCRIPTION
    This function retrieves CPU information from the specified computers. It provides details such as Name, DeviceID, Caption, SystemName, CurrentClockSpeed, MaxClockSpeed, ProcessorID, ThreadCount, Architecture, Status, LoadPercentage, L3CacheSize, Manufacturer, NumberOfCores, NumberOfEnabledCore, and NumberOfLogicalProcessors.
 
    .PARAMETER ComputerName
    Specifies the names of the computers for which to retrieve CPU information.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving CPU information. Valid values are 'Default', 'Dcom', and 'Wsman'.
 
    .PARAMETER All
    Indicates whether to retrieve all available CPU information.
 
    .EXAMPLE
    Get-ComputerCPU -ComputerName Server01, Server02 -Protocol Wsman -All
    Retrieves all available CPU information from remote computers Server01 and Server02 using Wsman protocol.
 
    .EXAMPLE
    Get-ComputerCPU -ComputerName "Workstation01" -Protocol Default
    Retrieves CPU information from a single remote computer named Workstation01 using the default protocol.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_processor'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'PSComputerName', 'Name', 'DeviceID', 'Caption', 'SystemName', 'CurrentClockSpeed', 'MaxClockSpeed', 'ProcessorID', 'ThreadCount', 'Architecture', 'Status', 'LoadPercentage', 'L3CacheSize', 'Manufacturer', 'VirtualizationFirmwareEnabled', 'NumberOfCores', 'NumberOfEnabledCore', 'NumberOfLogicalProcessors'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName              = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Name                      = $Data.Name
                    DeviceID                  = $Data.DeviceID
                    Caption                   = $Data.Caption
                    CurrentClockSpeed         = $Data.CurrentClockSpeed
                    MaxClockSpeed             = $Data.MaxClockSpeed
                    ProcessorID               = $Data.ProcessorID
                    ThreadCount               = $Data.ThreadCount
                    Architecture              = $Data.Architecture
                    Status                    = $Data.Status
                    LoadPercentage            = $Data.LoadPercentage
                    Manufacturer              = $Data.Manufacturer

                    NumberOfCores             = $Data.NumberOfCores
                    NumberOfEnabledCore       = $Data.NumberOfEnabledCore
                    NumberOfLogicalProcessors = $Data.NumberOfLogicalProcessors    
                }
            }
        }
    }
}

function Get-ComputerCulture {
    <#
    .SYNOPSIS
    Retrieves culture information from a specified computer.
 
    .DESCRIPTION
    This function retrieves culture information from the specified computer. It provides details such as KeyboardLayoutId, DisplayName, and Windows Language.
 
    .PARAMETER ComputerName
    Specifies the name of the computer from which to retrieve culture information. Defaults to the local computer.
 
    .EXAMPLE
    Get-ComputerCulture
    Retrieves culture information from the local computer.
 
    .EXAMPLE
    Get-ComputerCulture -ComputerName "Server01"
    Retrieves culture information from a remote computer named Server01.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME
    )
    $ScriptBlock = {
        Get-Culture | Select-Object KeyboardLayoutId, DisplayName, @{Expression = { $_.ThreeLetterWindowsLanguageName }; Label = "Windows Language" }
    }
    if ($ComputerName -eq $Env:COMPUTERNAME) {
        $Data8 = Invoke-Command -ScriptBlock $ScriptBlock
    }
    else {
        $Data8 = Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock
    }
    return $Data8
}
function Get-ComputerDevice {
    <#
    .SYNOPSIS
    Retrieves information about computer devices.
 
    .DESCRIPTION
    This function retrieves information about computer devices using WMI.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to query. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for the query. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'.
 
    .PARAMETER All
    Retrieves all properties of the computer devices.
 
    .PARAMETER Extended
    Retrieves extended properties of the computer devices.
 
    .EXAMPLE
    Get-ComputerDevice -ComputerName "Computer01" -Protocol "Wsman" -All
    Retrieves all properties of computer devices from a remote computer using Wsman protocol.
 
    .EXAMPLE
    Get-ComputerDevice -ComputerName "Computer02" -Extended
    Retrieves extended properties of computer devices from a remote computer.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All,
        [switch] $Extended
    )
    [string] $Class = 'win32_pnpentity'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = @(

            'PNPClass'
            'Name'
            'Status'

            'ConfigManagerErrorCode'

            'DeviceID'
            'ErrorCleared'
            'ErrorDescription'
            'LastErrorCode'

            'StatusInfo'

            'ClassGuid'
            'CompatibleID'
            'HardwareID'
            'Manufacturer'

            'PSComputerName'
        )
    }

    $ConfigManagerErrorCode = @{
        '0'  = "This device is working properly."
        '1'  = 'This device is not configured correctly.'
        '2'  = 'Windows cannot load the driver for this device.'
        '3'  = "The driver for this device might be corrupted, or your system may be running low on memory or other resources."
        '4'  = "This device is not working properly. One of its drivers or your registry might be corrupted."
        '5'  = "The driver for this device needs a resource that Windows cannot manage."
        '6'  = "The boot configuration for this device conflicts with other devices."
        '7'  = "Cannot filter."
        '8'  = "The driver loader for the device is missing."
        '9'  = "This device is not working properly because the controlling firmware is reporting the resources for the device incorrectly."
        '10' = "This device cannot start."
        '11' = "This device failed."
        '12' = "This device cannot find enough free resources that it can use."
        '13' = "Windows cannot verify this device's resources."
        '14' = "This device cannot work properly until you restart your computer."
        '15' = "This device is not working properly because there is probably a re-enumeration problem."
        '16' = "Windows cannot identify all the resources this device uses."
        '17' = "This device is asking for an unknown resource type."
        '18' = "Reinstall the drivers for this device."
        '19' = "Failure using the VxD loader."
        '20' = "Your registry might be corrupted."
        '21' = "System failure: Try changing the driver for this device. If that does not work, see your hardware documentation. Windows is removing this device."
        '22' = "This device is disabled."
        '23' = "System failure: Try changing the driver for this device. If that doesn't work, see your hardware documentation."
        '24' = "This device is not present, is not working properly, or does not have all its drivers installed."
        '25' = "Windows is still setting up this device."
        '26' = "Windows is still setting up this device."
        '27' = "This device does not have valid log configuration."
        '28' = "The drivers for this device are not installed."
        '29' = "This device is disabled because the firmware of the device did not give it the required resources."
        '30' = "This device is using an Interrupt Request (IRQ) resource that another device is using."
        '31' = "This device is not working properly because Windows cannot load the drivers required for this device."
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                $Device = [ordered]@{
                    ComputerName  = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }

                    'DeviceClass' = $Data.PNPClass
                    'Name'        = $Data.Name
                    'Status'      = $Data.Status

                    'ErrorCode'   = $ConfigManagerErrorCode["$($Data.ConfigManagerErrorCode)"]
                    'DeviceID'    = $Data.DeviceID
                }
                if ($Extended) {
                    $DeviceUpgrade = [ordered]@{

                        'ErrorCleared'     = $Data.ErrorCleared
                        'ErrorDescription' = $Data.ErrorDescription
                        'LastErrorCode'    = $Data.LastErrorCode

                        'StatusInfo'       = $Data.StatusInfo

                        'ClassGuid'        = $Data.ClassGuid
                        'CompatibleID'     = $Data.CompatibleID
                        'HardwareID'       = $Data.HardwareID
                        'Manufacturer'     = if ($Data.Manufacturer) {
                            $Data.Manufacturer.Replace('(', '').Replace(')', '') 
                        }
                        else { 
                        }
                    }
                    [PSCustomObject] ($Device + $DeviceUpgrade)
                }
                else {
                    [PSCustomObject] $Device
                }
            }
        }
    }
}

function Get-ComputerDisk {
    <#
    .SYNOPSIS
    Retrieves disk information from remote computers.
 
    .DESCRIPTION
    This function retrieves disk information from remote computers specified by the ComputerName parameter. It provides details such as Index, Model, Caption, SerialNumber, Description, MediaType, FirmwareRevision, Partitions, SizeGB, and PNPDeviceID.
 
    .PARAMETER ComputerName
    Specifies the names of the remote computers from which to retrieve disk information.
 
    .PARAMETER Protocol
    Specifies the protocol to be used for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'.
 
    .PARAMETER All
    Indicates whether to retrieve all available disk information.
 
    .EXAMPLE
    Get-ComputerDisk -ComputerName AD1, AD2, EVO1, AD2019 | Format-Table -AutoSize *
 
    Output:
    WARNING: Get-ComputerSystem - No data for computer AD2019. Most likely an error on receiving side.
 
    ComputerName Index Model Caption SerialNumber Description MediaType FirmwareRevision Partitions SizeGB PNPDeviceID
    ------------ ----- ----- ------- ------------ ----------- --------- ---------------- ---------- ------ -----------
    AD1 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000
    AD2 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000
    EVO1 0 WDC WD30EFRX-68AX9N0 WDC WD30EFRX-68AX9N0 WD-WMC1T2351095 Disk drive Fixed hard disk media 80.00A80 1 2795 SCSI\DISK&VEN_WDC&PROD_WD30EFRX-68AX9N0\4&191557A4&0&000000
    EVO1 2 Samsung SSD 950 PRO 512GB Samsung SSD 950 PRO 512GB 0025_3857_61B0_0EF2. Disk drive Fixed hard disk media 2B0Q 3 477 SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_950\5&35365596&0&000000
    EVO1 1 Samsung SSD 860 EVO 500GB Samsung SSD 860 EVO 500GB S3Z2NB0K176976A Disk drive Fixed hard disk media RVT01B6Q 1 466 SCSI\DISK&VEN_SAMSUNG&PROD_SSD\4&191557A4&0&000100
 
    .NOTES
    This function uses the Get-CimData cmdlet to retrieve disk information from remote computers.
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_diskdrive'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'Index', 'Model', 'Caption', 'SerialNumber', 'Description', 'MediaType', 'FirmwareRevision', 'Partitions', 'Size', 'PNPDeviceID', 'PSComputerName'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName     = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Index            = $Data.Index
                    Model            = $Data.Model
                    Caption          = $Data.Caption
                    SerialNumber     = if ($Data.SerialNumber) {
                        $Data.SerialNumber.Trim() 
                    }
                    else {
                        '' 
                    }
                    Description      = $Data.Description
                    MediaType        = $Data.MediaType
                    FirmwareRevision = $Data.FirmwareRevision
                    Partitions       = $Data.Partitions
                    SizeGB           = $Data.Size / 1Gb -as [int]
                    PNPDeviceID      = $Data.PNPDeviceID
                }
            }
        }
    }
}
function Get-ComputerDiskLogical {
    <#
    .SYNOPSIS
    Retrieves logical disk information for specified computers.
 
    .DESCRIPTION
    This function retrieves logical disk information for the specified computers. It provides details such as DeviceID, DriveType, ProviderName, FreeSpace, UsedSpace, TotalSpace, FreePercent, UsedPercent, and VolumeName.
 
    .PARAMETER ComputerName
    Specifies the names of the computers for which to retrieve disk information.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'.
 
    .PARAMETER RoundingPlace
    Specifies the number of decimal places to round the disk space values to.
 
    .PARAMETER OnlyLocalDisk
    Indicates that only local disks should be included in the output.
 
    .PARAMETER All
    Indicates that information for all disks should be retrieved.
 
    .EXAMPLE
    Get-ComputerDiskLogical -ComputerName AD1, AD2, EVOWIN -OnlyLocalDisk | ft -AutoSize
 
    Output:
 
    ComputerName DeviceID DriveType ProviderName FreeSpace UsedSpace TotalSpace FreePercent UsedPercent VolumeName
    ------------ -------- --------- ------------ --------- --------- ---------- ----------- ----------- ----------
    AD2 C: Local Disk 96,96 29,49 126,45 76,68 23,32
    AD1 C: Local Disk 103,17 23,28 126,45 81,59 18,41
    EVOWIN C: Local Disk 133,31 343,03 476,34 27,99 72,01
    EVOWIN D: Local Disk 2433 361,4 2794,39 87,07 12,93 Media
    EVOWIN E: Local Disk 66,05 399,7 465,75 14,18 85,82 Testing Environment
 
    .NOTES
    Additional notes about the function.
    #>


    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [string][ValidateSet('GB', 'TB', 'MB')] $Size = 'GB',
        [int] $RoundingPlace = 2,
        [int] $RoundingPlacePercent = 2,
        [switch] $OnlyLocalDisk,
        [switch] $All
    )

    if (-Not ('Pinvoke.Win32Utils' -as [type])) {
        Add-Type -TypeDefinition @'
        using System.Runtime.InteropServices;
        using System.Text;
        namespace pinvoke {
            public static class Win32Utils {
                [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
                public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax);
            }
        }
'@

    }

    [string] $Class = 'win32_logicalDisk'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'DeviceID', 'DriveType', 'ProviderName', 'FreeSpace', 'Size', 'VolumeName', 'PSComputerName'
    }

    $DriveType = @{
        '0' = 'Unknown'
        '1' = 'No Root Directory'
        '2' = 'Removable Disk'
        '3' = 'Local Disk'
        '4' = 'Network Drive'
        '5' = 'Compact Disc'
        '6' = 'RAM Disk'
    }

    $Divider = "1$Size"

    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        $Output = foreach ($Info in $Information) {
            foreach ($Data in $Info) {
                if ($Info.ComputerName -eq $Env:COMPUTERNAME) {
                    $DiskPartitionName = [System.Text.StringBuilder]::new(1024)
                    if ($Data.DeviceID) {
                        [void][PInvoke.Win32Utils]::QueryDosDevice(($Data.DeviceID), $DiskPartitionName, $DiskPartitionName.Capacity)
                    }
                    $DiskPartitionNumber = $DiskPartitionName.ToString()
                }
                else {
                    $DiskPartitionNumber = ''
                }

                [PSCustomObject] @{
                    ComputerName  = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    DeviceID      = $Data.DeviceID
                    DriveType     = $DriveType["$($Data.DriveType)"]
                    ProviderName  = $Data.ProviderName

                    FreeSpace     = [Math]::Round($Data.FreeSpace / $Divider, $RoundingPlace)
                    UsedSpace     = [Math]::Round(($Data.Size - $Data.FreeSpace) / $Divider, $RoundingPlace)
                    TotalSpace    = [Math]::Round($Data.Size / $Divider, $RoundingPlace)

                    FreePercent   = if ($Data.Size -gt 0 ) {
                        [Math]::round(($Data.FreeSpace / $Data.Size) * 100, $RoundingPlacePercent) 
                    }
                    else {
                        '0' 
                    }
                    UsedPercent   = if ($Data.Size -gt 0 ) {
                        [Math]::round((($Data.Size - $Data.FreeSpace) / $Data.Size) * 100, $RoundingPlacePercent) 
                    }
                    else {
                        '0' 
                    }
                    VolumeName    = $Data.VolumeName
                    DiskPartition = $DiskPartitionNumber
                }
            }
        }
        if ($OnlyLocalDisk) {
            $Output | Where-Object { $_.DriveType -eq 'Local Disk' }
        }
        else {
            $Output
        }
    }
}
function Get-ComputerFirewall {
    <#
    .SYNOPSIS
    Short description
 
    .DESCRIPTION
    Long description
 
    .EXAMPLE
    $Data = Get-ComputerFirewall
    $Data | Format-Table -AutoSize
 
    .EXAMPLE
    $Data = Get-ComputerFirewall
    $Data | Out-HtmlView -ScrollX -Filtering -Title "Firewall Rules" -DataStore JavaScript
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName
    )

    $CimData = [ordered] @{
        CimSession    = $ComputerName
        ErrorAction   = 'SilentlyContinue'
        ErrorVariable = 'ErrorVar'
    }
    Remove-EmptyValue -Hashtable $CimData

    if ($ComputerName) {
        $ComputerNameDisplay = $ComputerName
    }
    else {
        $ComputerNameDisplay = 'Localhost'
    }
    Write-Verbose -Message "Get-ComputerFirewall - Getting firewall rules ($ComputerNameDisplay)"
    $Allrules = Get-NetFirewallRule @CimData
    if ($ErrorVar) {
        Write-Warning -Message "Get-ComputerFirewall - Error getting firewall rules. Try running as admin? $($ErrorVar)"
    }
    Write-Verbose -Message "Get-ComputerFirewall - Getting firewall filters ($ComputerNameDisplay)"
    $Allports = Get-NetFirewallPortFilter @CimData
    if ($ErrorVar) {
        Write-Warning -Message "Get-ComputerFirewall - Error getting firewall ports. Try running as admin? $($ErrorVar)"
    }
    Write-Verbose -Message "Get-ComputerFirewall - Getting firewall filters ($ComputerNameDisplay)"
    $AllFilters = Get-NetFirewallApplicationFilter @CimData | Select-Object Program, AppPath, InstanceID, Description, Package
    if ($ErrorVar) {
        Write-Warning -Message "Get-ComputerFirewall - Error getting firewall filters. Try running as admin? $($ErrorVar)"
    }

    Write-Verbose -Message "Get-ComputerFirewall - Matching rules, ports and filters"
    $FiltersCache = @{}
    $RulesCache = @{}
    $PortCache = @{}
    foreach ($Rule in $Allrules) {
        $RulesCache[$Rule.Name] = $Rule
    }
    foreach ($Port in $Allports) {
        $PortCache[$Port.InstanceID] = $Port
    }
    foreach ($Filter in $AllFilters) {
        $FiltersCache[$Filter.InstanceID] = $Filter
    }

    foreach ($Rule in $RulesCache.Values) {
        [PSCustomObject]@{

            Name                          = $Rule.Name                          
            ID                            = $Rule.ID                            
            DisplayName                   = $Rule.DisplayName                   
            Group                         = $Rule.Group                         
            Enabled                       = $Rule.Enabled                       
            Profile                       = $Rule.Profile                       
            Platform                      = $Rule.Platform                      
            Direction                     = $Rule.Direction                     
            Action                        = $Rule.Action                        
            EdgeTraversalPolicy           = $Rule.EdgeTraversalPolicy           
            LSM                           = $Rule.LSM                           
            PrimaryStatus                 = $Rule.PrimaryStatus                 
            Status                        = $Rule.Status                        
            EnforcementStatus             = $Rule.EnforcementStatus             
            PolicyStoreSourceType         = $Rule.PolicyStoreSourceType         
            Caption                       = $Rule.Caption                       
            Description                   = $Rule.Description                   
            ElementName                   = $Rule.ElementName                   
            InstanceID                    = $Rule.InstanceID                    
            CommonName                    = $Rule.CommonName                    
            PolicyKeywords                = $Rule.PolicyKeywords                
            PolicyDecisionStrategy        = $Rule.PolicyDecisionStrategy        
            PolicyRoles                   = $Rule.PolicyRoles                   
            ConditionListType             = $Rule.ConditionListType             
            CreationClassName             = $Rule.CreationClassName             
            ExecutionStrategy             = $Rule.ExecutionStrategy             
            Mandatory                     = $Rule.Mandatory                     
            PolicyRuleName                = $Rule.PolicyRuleName                
            Priority                      = $Rule.Priority                      
            RuleUsage                     = $Rule.RuleUsage                     
            SequencedActions              = $Rule.SequencedActions              
            SystemCreationClassName       = $Rule.SystemCreationClassName       
            SystemName                    = $Rule.SystemName                    
            DisplayGroup                  = $Rule.DisplayGroup                  
            LocalOnlyMapping              = $Rule.LocalOnlyMapping              
            LooseSourceMapping            = $Rule.LooseSourceMapping            
            Owner                         = $Rule.Owner                         
            PackageFamilyName             = $Rule.PackageFamilyName             
            Platforms                     = $Rule.Platforms                     
            PolicyAppId                   = $Rule.PolicyAppId                   
            PolicyStoreSource             = $Rule.PolicyStoreSource             
            Profiles                      = $Rule.Profiles                      
            RemoteDynamicKeywordAddresses = $Rule.RemoteDynamicKeywordAddresses 
            RuleGroup                     = $Rule.RuleGroup                     
            StatusCode                    = $Rule.StatusCode                    
            Program                       = $FiltersCache[$Rule.Name].Program
            AppPath                       = $FiltersCache[$Rule.Name].AppPath
            Protocol                      = $PortCache[$Rule.Name].Protocol      
            LocalPort                     = $PortCache[$Rule.Name].LocalPort     
            RemotePort                    = $PortCache[$Rule.Name].RemotePort    
            IcmpType                      = $PortCache[$Rule.Name].IcmpType      
            DynamicTarget                 = $PortCache[$Rule.Name].DynamicTarget 
        }
    }
}
function Get-ComputerInstalledUpdates {
    <#
    .SYNOPSIS
    This function retrieves the history of updates installed on a computer or multiple computers.
 
    .DESCRIPTION
    The `Get-ComputerInstalledUpdates` function uses the Windows Update Agent API to query the history of updates installed on a computer. It returns a list of updates with details such as the KB number, installation date, update type, version, and more.
 
    .PARAMETER ComputerName
    An array of computer names. The default value is the name of the current computer.
 
    .PARAMETER IncludeType
    An array of update types to include in the results. Valid values are 'Antivirus', 'CumulativeUpdate', 'WindowsUpdate', 'ServicingStackUpdate', and 'Other'.
 
    .PARAMETER ExcludeType
    An array of update types to exclude from the results. Valid values are the same as for `IncludeType`.
 
    .PARAMETER SearchKB
    A string to search for in the KB numbers of the updates.
 
    .PARAMETER Credential
    A PSCredential object to use when connecting to the computers.
 
    .EXAMPLE
    # Get all updates installed on the current computer
    Get-ComputerInstalledUpdates
 
    .EXAMPLE
    # Get all 'Windows Update' type updates installed on the computer 'Computer1'
    Get-ComputerInstalledUpdates -ComputerName 'Computer1' -IncludeType 'WindowsUpdate'
 
    .EXAMPLE
    # Get all updates except 'Antivirus' type updates installed on the computers 'Computer1' and 'Computer2'
    Get-ComputerInstalledUpdates -ComputerName 'Computer1', 'Computer2' -ExcludeType 'Antivirus'
 
    .EXAMPLE
    # Search for a specific KB number on the current computer
    Get-ComputerInstalledUpdates -SearchKB 'KB123456'
 
    .NOTES
    This function uses the COM interface of the Windows Update Agent API, which requires administrative privileges. If you run this function without administrative privileges, it may not return all updates.
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet(
            'Antivirus',
            'CumulativeUpdate',
            'WindowsUpdate',
            'ServicingStackUpdate',
            'Other'
        )][string[]] $IncludeType,
        [ValidateSet(
            'Antivirus',
            'CumulativeUpdate',
            'WindowsUpdate',
            'ServicingStackUpdate',
            'Other'
        )][string[]] $ExcludeType,
        [string] $SearchKB,
        [pscredential] $Credential
    )

    $Properties = @(
        'ComputerName', 'InstalledOn', 'KB', 'Categories', 'Type', 'Version', 'Title', 'ClientApplicationID', 'InstalledFrom',
        'Description', 'Operation', 'Status', 'SupportUrl', 'UpdateID', 'RevisionNumber',
        'UnmappedResultCode', 'HResult', 'ServiceID', 'UninstallationSteps', 'UninstallationNotes'
    )
    $ScriptBlock = {

        $Session = New-Object -ComObject Microsoft.Update.Session
        $Searcher = $Session.CreateUpdateSearcher()
        $historyCount = $Searcher.GetTotalHistoryCount()
        $kbRegex = '(KB\d+)'
        $versionRegex = 'Version (\d+\.\d+\.\d+\.\d+)'

        $QueryHistory = $Searcher.QueryHistory(0, $historyCount)
        $QueryHistory | Where-Object date -GT (Get-Date "1/1/2000") | ForEach-Object {
            $KbMatch = Select-String $kbRegex -InputObject $_.Title
            $Kb = if ($kbMatch) {
                $kbMatch.matches.groups[0].value 
            }
            else {
                $null 
            }

            $VersionMatch = Select-String $VersionRegex -InputObject $_.Title
            $Version = if ($VersionMatch) {
                $VersionMatch.matches.groups[1].value 
            }
            else {
                $null 
            }

            $Categories = $_.Categories | ForEach-Object { $_.Name }

            if ($_.Title -like "*Security Intelligence Update*" -or $_.Title -like "*Antivirus*" -or $_.Title -like "*Malicious Software*") {
                $Type = "Antivirus"
            }
            elseif ($_.Title -like "*Cumulative Update*") {
                $Type = "CumulativeUpdate"
            }
            elseif ($_.Title -like "*Update for Windows*" -or $_.Title -like "*Security Update*" -or $_.Title -like "*Update for Microsoft*" -or $_.Title -like "*Update for Internet Explorer*") {
                $Type = "WindowsUpdate"
            }
            elseif ($_.Title -like "*Servicing Stack Update*") {
                $Type = "ServicingStackUpdate"
            }
            else {
                $Type = "Other"
            }

            [PSCustomObject]@{
                "ComputerName"        = $Env:COMPUTERNAME
                "InstalledOn"         = $_.Date
                "KB"                  = $Kb
                "Categories"          = $Categories 
                "Type"                = $Type
                "Version"             = $Version
                "ClientApplicationID" = $_.ClientApplicationID
                "InstalledFrom"       = switch ($_.ServerSelection) {
                    0 {
                        "Default" 
                    }; 1 {
                        "Managed Server" 
                    }; 2 {
                        "Windows Update" 
                    }; 3 {
                        "Others" 
                    } 
                }
                "Title"               = $_.Title
                "Description"         = $_.Description
                "Operation"           = switch ($_.operation) {
                    1 {
                        "Installation" 
                    }; 2 {
                        "Uninstallation" 
                    }; 3 {
                        "Other" 
                    } 
                }
                "Status"              = switch ($_.resultcode) {
                    1 {
                        "In Progress" 
                    }; 2 {
                        "Succeeded" 
                    }; 3 {
                        "Succeeded With Errors" 
                    }; 4 {
                        "Failed" 
                    }; 5 {
                        "Aborted" 
                    } 
                }
                "SupportUrl"          = $_.SupportUrl
                "UpdateID"            = $_.UpdateIdentity.UpdateID
                "RevisionNumber"      = $_.UpdateIdentity.RevisionNumber
                "UnmappedResultCode"  = $_.UnmappedResultCode
                "HResult"             = $_.HResult
                "ServiceID"           = $_.ServiceID
                "UninstallationSteps" = $_.UninstallationSteps.Name
                "UninstallationNotes" = $_.UninstallationNotes
            }
        }
    }

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $Computers = $ComputersSplit[1]
    $Data = if ($Computers.Count -gt 0) {
        foreach ($Computer in $Computers) {
            try {
                $invokeCommandSplat = @{
                    ComputerName = $Computer
                    ScriptBlock  = $ScriptBlock
                    ErrorAction  = 'Stop'
                }
                if ($Credential) {
                    $invokeCommandSplat.Credential = $Credential
                }
                Invoke-Command @invokeCommandSplat | Select-Object -Property $Properties
            }
            catch {
                Write-Warning -Message "Get-ComputerInstalledUpdates - No data for computer $Computer. Failed with error: $($_.Exception.Message)"
            }
        }
    }
    else {
        try {
            $invokeCommandSplat = @{
                ScriptBlock = $ScriptBlock
                ErrorAction = 'Stop'
            }
            if ($Credential) {
                $invokeCommandSplat.Credential = $Credential
            }
            Invoke-Command @invokeCommandSplat
        }
        catch {
            Write-Warning -Message "Get-ComputerInstalledUpdates - No data for computer $($Env:COMPUTERNAME). Failed with error: $($_.Exception.Message)"
        }
    }
    if ($SearchKB) {
        foreach ($D in $Data) {
            if ($D.KB -like "*$SearchKB*") {
                $D
            }
        }
    }
    else {
        foreach ($Update in $Data) {
            if ($IncludeType -and $IncludeType -notcontains $Update.Type) {
                continue
            }
            if ($ExcludeType -and $ExcludeType -contains $Update.Type) {
                continue
            }
            $Update
        }
    }
}
function Get-ComputerMemory {
    <#
    .SYNOPSIS
    Retrieves memory information from specified computers.
 
    .DESCRIPTION
    This function retrieves memory information from specified computers, including details about physical memory usage, virtual memory usage, and memory percentages.
 
    .PARAMETER ComputerName
    Specifies the name of the computer(s) to retrieve memory information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving memory information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'.
 
    .PARAMETER All
    Switch parameter to retrieve all available memory properties.
 
    .EXAMPLE
    Get-ComputerMemory -ComputerName "Server01"
    Retrieves memory information from a remote computer named Server01.
 
    .EXAMPLE
    Get-ComputerMemory -ComputerName "WorkstationA", "WorkstationB" -Protocol Wsman -All
    Retrieves all available memory properties from multiple remote computers using the Wsman protocol.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_operatingsystem'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string] $Properties = "*"
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Data in $Information) {
            $FreeMB = [math]::Round($Data.FreePhysicalMemory / 1024, 2)
            $TotalMB = [math]::Round($Data.TotalVisibleMemorySize / 1024, 2)
            $UsedMB = $TotalMB - $FreeMB
            $UsedPercent = [math]::Round(($UsedMB / $TotalMB) * 100, 2)
            $FreeVirtualMB = [math]::Round($Data.FreeVirtualMemory / 1024, 2)
            $TotalVirtualMB = [math]::Round($Data.TotalVirtualMemorySize / 1024, 2)
            $UsedVirtualMB = $TotalVirtualMB - $FreeVirtualMB
            $UsedVirtualPercent = [math]::Round(($UsedVirtualMB / $TotalVirtualMB) * 100, 2)

            [PSCustomObject] @{
                ComputerName                = if ($Data.PSComputerName) {
                    $Data.PSComputerName 
                }
                else {
                    $Env:COMPUTERNAME 
                }
                MemoryUsed                  = $UsedMB
                MemoryFree                  = $FreeMB
                MemoryTotal                 = $TotalMB
                MemoryUsedPercentage        = $UsedPercent
                VirtualMemoryUsed           = $UsedVirtualMB
                VirtualMemoryUsedPercentage = $UsedVirtualPercent
                VirtualMemoryFree           = $FreeVirtualMB
                VirtualMemoryTotal          = $TotalVirtualMB
            }
        }
    }
}

function Get-ComputerMissingDrivers {
    <#
    .SYNOPSIS
    Retrieves information about missing drivers on a specified computer.
 
    .DESCRIPTION
    This function retrieves information about missing drivers on a specified computer by querying the Win32_PNPEntity WMI class.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to query. Defaults to the local computer.
 
    .EXAMPLE
    Get-ComputerMissingDrivers -ComputerName "Computer01"
    Retrieves information about missing drivers on a computer named "Computer01".
 
    .EXAMPLE
    Get-ComputerMissingDrivers
    Retrieves information about missing drivers on the local computer.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME
    )
    $Data = Get-WmiObject Win32_PNPEntity -ComputerName $ComputerName | Where-Object { $_.Configmanagererrorcode -ne 0 } | Select-Object Caption, ConfigmanagererrorCode, Description, DeviceId, HardwareId, PNPDeviceID
    return $Data
}

function Get-ComputerNetFramework {
    <#
    .SYNOPSIS
    Get the installed .NET Framework version on a computer
 
    .DESCRIPTION
    Get the installed .NET Framework version on a computer
 
    .PARAMETER ComputerName
    The name of the computer to check the .NET Framework version on
 
    .EXAMPLE
    $DCs = Get-ADDomainController -Filter * -Server 'ad.evotec.xyz'
    Get-ComputerNetFramework -ComputerName $Dcs.HostName | Format-Table *
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $env:COMPUTERNAME
    )
    foreach ($Computer in $ComputerName) {
        $Output1 = Get-PSRegistry -ComputerName $Computer -RegistryPath 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client'

        if ($Output1.PSError -eq $false) {
            [PSCustomObject] @{
                ComputerName        = $Computer
                NetFrameworkVersion = $Output1.Version
                NetFrameworkRelease = $Output1.Release
                Message             = ""
            }
        }
        else {
            [PSCustomObject] @{
                ComputerName        = $Computer
                NetFrameworkVersion = 'Not Installed'
                NetFrameworkRelease = 'Not Installed'
                Message             = $Output1.PSErrorMessage
            }
        }
    }
}
function Get-ComputerNetwork {
    <#
    .SYNOPSIS
    Retrieves network information for specified computers.
 
    .DESCRIPTION
    This function retrieves network information for the specified computers, including details about network cards, firewall profiles, and connectivity status.
 
    .PARAMETER ComputerName
    Specifies the name of the computer(s) for which to retrieve network information.
 
    .PARAMETER NetworkFirewallOnly
    Indicates whether to retrieve only firewall information for the specified computers.
 
    .PARAMETER NetworkFirewallSummaryOnly
    Indicates whether to retrieve a summary of firewall information for the specified computers.
 
    .EXAMPLE
    Get-ComputerNetworkCard -ComputerName AD1, AD2, AD3
 
    Output
 
    Name NetworkCardName NetworkCardIndex FirewallProfile FirewallStatus IPv4Connectivity IPv6Connectivity Caption Description ElementName DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogFileName LogMaxSizeKilobytes LogAllowed LogBlo
                                                                                                                                                                                                                                                                                                                                                                                                                                                                        cked
    ---- --------------- ---------------- --------------- -------------- ---------------- ---------------- ------- ----------- ----------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ----------- ------------------- ---------- ------
    ad.evotec.xyz vEthernet (External Switch) 13 DomainAuthenticated True Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network 2 Ethernet 2 2 Private True Internet NoTraffic Block Allow NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network Ethernet 2 Private True LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    ad.evotec.xyz Ethernet 5 3 DomainAuthenticated False Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
    Network 2 Ethernet 4 12 Private False LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False
 
    .EXAMPLE
    Get-ComputerNetworkCard -ComputerName EVOWIN -NetworkFirewallOnly
 
    PSComputerName Profile Enabled DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogMaxSizeKilobytes LogAllowed LogBlocked LogIgnored Caption Description ElementName InstanceID DisabledInterfaceAliases LogFileName Name CimClass
    -------------- ------- ------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ------------------- ---------- ---------- ---------- ------- ----------- ----------- ---------- ------------------------ ----------- ---- --------
    EVOWIN Domain True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Domain {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Domain root/stand...
    EVOWIN Private True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Private {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Private root/stand...
    EVOWIN Public True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Public {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Public root/stand...
 
    .NOTES
    General notes
    #>

    [alias('Get-ComputerNetworkCard')]
    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [switch] $NetworkFirewallOnly,
        [switch] $NetworkFirewallSummaryOnly,
        [alias('Joiner')][string] $Splitter
    )
    [Array] $CollectionComputers = $ComputerName.Where( { $_ -eq $Env:COMPUTERNAME }, 'Split')

    $Firewall = @{ }
    $NetworkFirewall = @(
        if ($CollectionComputers[0].Count -gt 0) {
            $Firewall[$Env:COMPUTERNAME] = @{ }
            $Output = Get-NetFirewallProfile
            foreach ($_ in $Output) {
                Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force
                $_
                if ($_.Name -eq 'Domain') {
                    $Firewall[$Env:COMPUTERNAME]['DomainAuthenticated'] = $_
                }
                else {
                    $Firewall[$Env:COMPUTERNAME][$($_.Name)] = $_
                }
            }
        }
        if ($CollectionComputers[1].Count -gt 0) {
            foreach ($_ in $CollectionComputers[1]) {
                $Firewall[$_] = @{ }
            }
            $Output = Get-NetFirewallProfile -CimSession $CollectionComputers[1]
            foreach ($_ in $Output) {
                if ($_.Name -eq 'Domain') {
                    $Firewall[$_.PSComputerName]['DomainAuthenticated'] = $_
                }
                else {
                    $Firewall[$_.PSComputerName][$($_.Name)] = $_
                }
            }
        }
    )
    if ($NetworkFirewallOnly) {
        return $NetworkFirewall
    }
    if ($NetworkFirewallSummaryOnly) {

        return $Firewall
    }
    $NetworkCards = @(
        if ($CollectionComputers[0].Count -gt 0) {
            $Output = Get-NetConnectionProfile
            foreach ($_ in $Output) {
                Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force
                $_
            }
        }
        if ($CollectionComputers[1].Count -gt 0) {
            Get-NetConnectionProfile -CimSession $CollectionComputers[1]
        }
    )
    foreach ($_ in $NetworkCards) {

        $NetworkCardsConfiguration = Get-CimData -ComputerName $ComputerName -Class 'Win32_NetworkAdapterConfiguration'
        $CurrentCard = foreach ($Configuration in $NetworkCardsConfiguration) {
            if ($_.PSComputerName -eq $Configuration.PSComputerName) {
                if ($Configuration.InterfaceIndex -eq $_.InterfaceIndex) {
                    $Configuration
                }
            }
        }

        $NetbiosTCPIP = @{
            '0' = 'Default'
            '1' = 'Enabled'
            '2' = 'Disabled'
        }

        [PSCustomObject] @{
            Name                            = $_.Name
            NetworkCardName                 = $_.InterfaceAlias
            NetworkCardIndex                = $_.InterfaceIndex
            FirewallProfile                 = $_.NetworkCategory
            FirewallStatus                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].'Enabled'

            IPAddress                       = $CurrentCard.IPAddress
            IPGateway                       = $CurrentCard.DefaultIPGateway
            IPSubnet                        = $CurrentCard.IPSubnet
            IPv4Connectivity                = $_.IPv4Connectivity
            IPv6Connectivity                = $_.IPv6Connectivity
            DNSServerSearchOrder            = $CurrentCard.DNSServerSearchOrder
            DNSDomainSuffixSearchOrder      = $CurrentCard.DNSDomainSuffixSearchOrder
            FullDNSRegistrationEnabled      = $CurrentCard.FullDNSRegistrationEnabled
            DHCPEnabled                     = $CurrentCard.DHCPEnabled
            DHCPServer                      = $CurrentCard.DHCPServer
            DHCPLeaseObtained               = $CurrentCard.DHCPLeaseObtained
            NetBIOSOverTCPIP                = $NetBiosTCPIP["$($CurrentCard.TcpipNetbiosOptions)"]
            Caption                         = $_.Caption
            Description                     = $_.Description
            ElementName                     = $_.ElementName

            DefaultInboundAction            = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultInboundAction
            DefaultOutboundAction           = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultOutboundAction
            AllowInboundRules               = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowInboundRules
            AllowLocalFirewallRules         = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalFirewallRules
            AllowLocalIPsecRules            = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalIPsecRules
            AllowUserApps                   = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserApps
            AllowUserPorts                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserPorts
            AllowUnicastResponseToMulticast = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUnicastResponseToMulticast
            NotifyOnListen                  = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].NotifyOnListen
            EnableStealthModeForIPsec       = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].EnableStealthModeForIPsec
            LogFileName                     = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogFileName
            LogMaxSizeKilobytes             = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogMaxSizeKilobytes
            LogAllowed                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogAllowed
            LogBlocked                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogBlocked
            LogIgnored                      = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogIgnored
            ComputerName                    = $_.PSComputerName
        }
    }
}

function Get-ComputerOemInformation {
    <#
    .SYNOPSIS
    Retrieves OEM information from a specified computer.
 
    .DESCRIPTION
    This function retrieves OEM information such as Model, Manufacturer, Logo, Support Phone, Support URL, and Support Hours from the specified computer.
 
    .PARAMETER ComputerName
    Specifies the name of the computer from which to retrieve the OEM information. If not specified, the local computer name is used.
 
    .EXAMPLE
    Get-ComputerOemInformation
    Retrieves OEM information from the local computer.
 
    .EXAMPLE
    Get-ComputerOemInformation -ComputerName "Computer01"
    Retrieves OEM information from a remote computer named "Computer01".
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME
    )
    $ScriptBlock = { Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation | Select-Object Model, Manufacturer, Logo, SupportPhone, SupportURL, SupportHours }
    if ($ComputerName -eq $Env:COMPUTERNAME) {
        $Data = Invoke-Command -ScriptBlock $ScriptBlock
    }
    else {
        $Data = Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock
    }
    return $Data
}
function Get-ComputerOperatingSystem {
    <#
    .SYNOPSIS
    Retrieves operating system information from remote computers.
 
    .DESCRIPTION
    This function retrieves operating system information from remote computers using CIM/WMI queries. It provides details such as the operating system name, version, manufacturer, architecture, language, product suite, installation date, last boot-up time, and more.
 
    .PARAMETER ComputerName
    Specifies the name of the remote computer(s) to retrieve the operating system information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for the connection (Default, Dcom, or Wsman). Default is 'Default'.
 
    .PARAMETER All
    Switch parameter to retrieve all available properties of the operating system.
 
    .EXAMPLE
    Get-ComputerOperatingSystem -ComputerName "Server01" -Protocol Wsman
    Retrieves operating system information from a single remote computer named "Server01" using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerOperatingSystem -ComputerName "Server01", "Server02" -All
    Retrieves all available operating system properties from multiple remote computers named "Server01" and "Server02".
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_operatingsystem'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'Caption', 'Manufacturer', 'InstallDate', 'OSArchitecture', 'Version', 'SerialNumber', 'BootDevice', 'WindowsDirectory', 'CountryCode', 'OSLanguage', 'OSProductSuite', 'PSComputerName', 'LastBootUpTime', 'LocalDateTime'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Data in $Information) {

            [PSCustomObject] @{
                ComputerName           = if ($Data.PSComputerName) {
                    $Data.PSComputerName 
                }
                else {
                    $Env:COMPUTERNAME 
                }
                OperatingSystem        = $Data.Caption
                OperatingSystemVersion = ConvertTo-OperatingSystem -OperatingSystem $Data.Caption -OperatingSystemVersion $Data.Version
                OperatingSystemBuild   = $Data.Version
                Manufacturer           = $Data.Manufacturer
                OSArchitecture         = $Data.OSArchitecture
                OSLanguage             = ConvertFrom-LanguageCode -LanguageCode $Data.OSLanguage
                OSProductSuite         = [Microsoft.PowerShell.Commands.OSProductSuite] $($Data.OSProductSuite)
                InstallDate            = $Data.InstallDate
                LastBootUpTime         = $Data.LastBootUpTime
                LocalDateTime          = $Data.LocalDateTime
                SerialNumber           = $Data.SerialNumber
                BootDevice             = $Data.BootDevice
                WindowsDirectory       = $Data.WindowsDirectory
                CountryCode            = $Data.CountryCode
            }
        }
    }
}

function Get-ComputerRAM {
    <#
    .SYNOPSIS
    Retrieves information about the RAM of a specified computer.
 
    .DESCRIPTION
    This function retrieves detailed information about the RAM of a specified computer. It provides various properties such as Manufacturer, Model, Capacity, Speed, and more.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve RAM information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for retrieving RAM information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'.
 
    .PARAMETER All
    Indicates whether to retrieve all available properties of the RAM. If specified, all properties will be retrieved.
 
    .PARAMETER Extended
    Indicates whether to retrieve extended properties of the RAM. If specified, additional properties will be retrieved.
 
    .EXAMPLE
    Get-ComputerRAM -ComputerName "Server01" -Protocol Wsman
    Retrieves RAM information from a remote computer named Server01 using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerRAM -ComputerName "WorkstationA" -All
    Retrieves all available RAM properties from a computer named WorkstationA.
 
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All,
        [switch] $Extended
    )
    [string] $Class = 'Win32_physicalmemory '
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = @(
            'InstallDate'

            'Manufacturer'
            'Model'
            'OtherIdentifyingInfo'
            'PartNumber'
            'PoweredOn'
            'SerialNumber'
            'SKU'
            'Tag'
            'Version'
            'HotSwappable'
            'Removable'
            'Replaceable'
            'FormFactor'
            'BankLabel'
            'Capacity'

            'InterleavePosition'
            'MemoryType'

            'Speed'

            'ConfiguredClockSpeed'
            'ConfiguredVoltage'
            'DeviceLocator'

            'MaxVoltage'
            'MinVoltage'
            'SMBIOSMemoryType'
            'TypeDetail'
            'PSComputerName'
        )
    }
    $FormFactor = @{
        '0'  = 'Unknown'
        '1'  = 'Other'
        '2'  = 'SIP'
        '3'  = 'DIP'
        '4'  = 'ZIP'
        '5'  = 'SOJ'
        '6'  = 'Proprietary'
        '7'  = 'SIMM'
        '8'  = 'DIMM'
        '9'  = 'TSOP'
        '10' = 'PGA'
        '11' = 'RIMM'
        '12' = 'SODIMM'
        '13' = 'SRIMM'
        '14' = 'SMD'
        '15' = 'SSMP'
        '16' = 'QFP'
        '17' = 'TQFP'
        '18' = 'SOIC'
        '19' = 'LCC'
        '20' = 'PLCC'
        '21' = 'BGA'
        '22' = 'FPBGA'
        '23' = 'LGA'
    }
    $TypeDetails = @{
        '1'    = 'Reserved'
        '2'    = 'Other'
        '4'    = 'Unknown'
        '8'    = 'Fast-paged'
        '16'   = 'Static column'
        '32'   = 'Pseudo-static'
        '64'   = 'RAMBUS'
        '128'  = 'Synchronous'
        '256'  = 'CMOS'
        '512'  = 'EDO'
        '1024' = 'Window DRAM'
        '2048' = 'Cache DRAM'
        '4096' = 'Non-volatile'
    }
    $InterleavePosition = @{
        '0' = "Non-Interleaved"
        '1' = "First Position"
        '2' = "Second Position"
    }
    $MemoryType = @{
        '0'  = "Unknown"
        '1'  = "Other"
        '2'  = "DRAM"
        '3'  = "Synchronous DRAM"
        '4'  = "Cache DRAM"
        '5'  = "EDO"
        '6'  = "EDRAM"
        '7'  = "VRAM"
        '8'  = "SRAM"
        '9'  = "ROM"
        '10' = "ROM"
        '11' = "FLASH"
        '12' = "EEPROM"
        '13' = "FEPROM"
        '14' = "EPROM"
        '15' = "CDRAM"
        '16' = "3DRAM"
        '17' = "SDRAM"
        '18' = "SGRAM"
        '19' = "RDRAM"
        '20' = "DDR"
    }
    $MemoryTypeSMBIOS = @{
        '0'  = 'Unknown'
        '1'  = 'Other'
        '2'  = 'DRAM'
        '3'  = 'Synchronous DRAM'
        '4'  = 'Cache DRAM'
        '5'  = 'EDO'
        '6'  = 'EDRAM'
        '7'  = 'VRAM' 
        '8'  = 'SRAM' 
        '9'  = 'RAM' 
        '10' = 'ROM'    
        '11' = 'Flash' 
        '12' = 'EEPROM' 
        '13' = 'FEPROM' 
        '14' = 'EPROM' 
        '15' = 'CDRAM' 
        '16' = '3DRAM' 
        '17' = 'SDRAM' 
        '18' = 'SGRAM' 
        '19' = 'RDRAM' 
        '20' = 'DDR' 
        '21' = 'DDR2' 
        '22' = 'DDR2 FB-DIMM' 
        '24' = 'DDR3' 
        '25' = 'FBD2'
        '26' = 'DDR4'
    }

    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                $Ram = [ordered] @{
                    ComputerName       = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Manufacturer       = $Data.Manufacturer          
                    FormFactor         = $FormFactor["$($Data.FormFactor)"]            
                    SMBIOSMemoryType   = $MemoryTypeSMBIOS["$($Data.SMBIOSMemoryType)"]      
                    Size               = [math]::round($Data.Capacity / 1GB, 2)              
                    Speed              = $Data.Speed                 
                    InterleavePosition = $InterleavePosition["$($Data.InterleavePosition)"]    
                    MemoryType         = $MemoryType["$($Data.MemoryType)"]            
                    TypeDetail         = $TypeDetails["$($Data.TypeDetail)"]            
                    PartNumber         = $Data.PartNumber            
                    DeviceLocator      = $Data.DeviceLocator         
                }
                if ($Extended) {
                    $RamExtended = [ordered] @{
                        InstallDate          = $Data.InstallDate

                        Model                = $Data.Model                 
                        OtherIdentifyingInfo = $Data.OtherIdentifyingInfo  

                        PoweredOn            = $Data.PoweredOn             
                        SerialNumber         = $Data.SerialNumber          
                        SKU                  = $Data.SKU                   
                        Tag                  = $Data.Tag                   
                        Version              = $Data.Version               
                        HotSwappable         = $Data.HotSwappable          
                        Removable            = $Data.Removable             
                        Replaceable          = $Data.Replaceable           
                        BankLabel            = $Data.BankLabel             

                        ConfiguredClockSpeed = $Data.ConfiguredClockSpeed  
                        ConfiguredVoltage    = $Data.ConfiguredVoltage     

                        MaxVoltage           = $Data.MaxVoltage            
                        MinVoltage           = $Data.MinVoltage            
                    }
                    [PSCustomObject] ($Ram + $RamExtended)
                }
                else {
                    [PSCustomObject] $Ram
                }
            }
        }
    }
}

function Get-ComputerRDP {
    <#
    .SYNOPSIS
    Retrieves Remote Desktop Protocol (RDP) settings for a specified computer.
 
    .DESCRIPTION
    This function retrieves RDP settings for a specified computer using the Win32_TSGeneralSetting class.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve RDP settings for.
 
    .EXAMPLE
    Get-ComputerRDP -ComputerName "Computer01"
    Retrieves RDP settings for a computer named "Computer01".
 
    .EXAMPLE
    Get-ComputerRDP -ComputerName "Computer02", "Computer03"
    Retrieves RDP settings for multiple computers named "Computer02" and "Computer03".
    #>

    [alias('Get-RDPSecurity')]
    [cmdletbinding()]
    param(
        [string[]] $ComputerName
    )

    $Output = Get-CimData -Class 'Win32_TSGeneralSetting' -NameSpace 'root\cimv2\terminalservices' -ComputerName $ComputerName
    foreach ($_ in $Output) {

        $EncryptionLevels = @{
            '1' = 'Low'
            '2' = 'Medium / Client Compatible'
            '3' = 'High'
            '4' = 'FIPS Compliant'
        }

        $PolicyConfiguredBy = @{
            '0' = 'Server'
            '1' = 'Group policy'
            '2' = 'Default'
        }

        $SecurityLayers = @{
            '1' = 'RDP Security Layer' 
            '2' = 'Negotiate' 
            '3' = 'SSL' 
            '4' = 'NEWTBD' 
        }

        $HashType = @{
            '0' = 'Not valid'
            '1' = 'Self-signed'
            '2' = 'Custom'
        }

        $Connectivity = Test-ComputerPort -ComputerName $_.PSComputerName -PortTCP 3389 -WarningAction SilentlyContinue

        [PSCustomObject] @{
            ComputerName                           = $_.PSComputerName
            Name                                   = $_.TerminalName
            Connectivity                           = $Connectivity.Status
            ConnectivitySummary                    = $Connectivity.Summary
            SecurityLayer                          = $SecurityLayers["$($_.SecurityLayer)"]
            MinimalEncryptionLevel                 = $EncryptionLevels["$($_.MinEncryptionLevel)"]
            MinimalEncryptionLevelValue            = $_.MinEncryptionLevel
            PolicySourceUserAuthenticationRequired = $PolicyConfiguredBy["$($_.PolicySourceUserAuthenticationRequired)"]
            PolicySourceMinimalEncryptionLevel     = $PolicyConfiguredBy["$($_.PolicySourceMinEncryptionLevel)"]
            PolicySourceSecurityLayer              = $PolicyConfiguredBy["$($_.PolicySourceSecurityLayer)"]
            CertificateName                        = $_.CertificateName
            CertificateThumbprint                  = $_.SSLCertificateSHA1Hash
            CertificateType                        = $HashType["$($_.SSLCertificateSHA1HashType)"]
            Transport                              = $_.Transport
            Protocol                               = $_.TerminalProtocol

            UserAuthenticationRequired             = [bool] $_.UserAuthenticationRequired

            WindowsAuthentication                  = [bool] $_.WindowsAuthentication
        }
    }
}

function Get-ComputerRoles {
    <#
    .SYNOPSIS
    Get Computer/Server Roles
 
    .DESCRIPTION
    Get Computer/Server Roles
 
    .PARAMETER ComputerName
    Parameter description
 
    .PARAMETER FeatureType
    Display all or limited types. Choices are Role, Role Service and Feature.
 
    .PARAMETER EnabledOnly
    Display only enabled/installed features or roles
 
    .EXAMPLE
    Get-ComputerRoles -ComputerName AD1 -EnabledOnly -FeatureType Role | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-ServerRoles')]
    [CmdletBinding()]
    param (
        [string[]] $ComputerName = $env:COMPUTERNAME,
        [ValidateSet('Role', 'Role Service', 'Feature')] $FeatureType,
        [switch] $EnabledOnly
    )
    if ($Global:ProgressPreference -ne 'SilentlyContinue') {
        $TemporaryProgress = $Global:ProgressPreference
        $Global:ProgressPreference = 'SilentlyContinue'
    }
    foreach ($Computer in $ComputerName) {
        try {
            $Output = Get-WindowsFeature -ComputerName $Computer -ErrorAction Stop
        }

        catch [System.Exception] {
            if ($_.FullyQualifiedErrorId -like 'UnSupportedTargetDevice,*') {
                $output = Invoke-Command -ComputerName $computer {
                    Import-Module ServerManager
                    Get-WindowsFeature
                }
            }
        }

        foreach ($Data in $Output) {
            if ($EnabledOnly -and $Data.Installed -eq $false) {
                continue
            }
            if ($FeatureType) {
                if ($Data.FeatureType -notin $FeatureType) {
                    continue
                }
            }
            [PSCustomObject] @{
                ComputerName = $Computer
                Name         = $Data.Name
                DisplayName  = $Data.DisplayName
                FeatureType  = $Data.FeatureType
                Installed    = $Data.Installed

                Description  = $Data.Description
            }
        }
    }

    if ($TemporaryProgress) {
        $Global:ProgressPreference = $TemporaryProgress
    }
}
function Get-ComputerService {
    <#
    .SYNOPSIS
    Retrieves information about services running on specified computers.
 
    .DESCRIPTION
    This function retrieves information about services running on one or more specified computers. It returns details such as ComputerName, Name, Displayname, Status, and StartType of the services.
 
    .EXAMPLE
    Get-ComputerServices -ComputerName "Computer01"
    Retrieves information about services running on a single computer named "Computer01".
 
    .EXAMPLE
    Get-ComputerServices -ComputerName "Computer01", "Computer02"
    Retrieves information about services running on multiple computers named "Computer01" and "Computer02".
 
    #>

    [alias('Get-ComputerServices')]
    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME
    )
    Process {
        foreach ($Computer in $ComputerName) {
            $Services = Get-PSService -ComputerName $Computer | Select-Object ComputerName, Name, Displayname, Status, StartType
            $Services
        }
    }
}
function Get-ComputerSMB {
    <#
    .SYNOPSIS
    Retrieves SMB server configuration details for a specified computer.
 
    .DESCRIPTION
    This function retrieves the SMB server configuration details for a specified computer.
 
    .PARAMETER ComputerName
    Specifies the name of the computer for which to retrieve SMB server configuration details.
 
    .EXAMPLE
    Get-ComputerSMB -ComputerName "Server01"
    Retrieves the SMB server configuration details for the computer named "Server01".
 
    .NOTES
    This function requires administrative privileges to retrieve SMB server configuration details.
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName
    )

    [Array] $CollectionComputers = $ComputerName.Where( { $_ -eq $Env:COMPUTERNAME }, 'Split')
    $SMB = @(
        if ($CollectionComputers[0].Count -gt 0) {
            $Output = Get-SmbServerConfiguration
            foreach ($_ in $Output) {
                [PSCustomObject] @{
                    ComputerName                    = $Env:COMPUTERNAME
                    AnnounceComment                 = $_.AnnounceComment
                    AnnounceServer                  = $_.AnnounceServer
                    AsynchronousCredits             = $_.AsynchronousCredits
                    AuditSmb1Access                 = $_.AuditSmb1Access
                    AutoDisconnectTimeout           = $_.AutoDisconnectTimeout
                    AutoShareServer                 = $_.AutoShareServer
                    AutoShareWorkstation            = $_.AutoShareWorkstation
                    CachedOpenLimit                 = $_.CachedOpenLimit
                    DurableHandleV2TimeoutInSeconds = $_.DurableHandleV2TimeoutInSeconds
                    EnableAuthenticateUserSharing   = $_.EnableAuthenticateUserSharing
                    EnableDownlevelTimewarp         = $_.EnableDownlevelTimewarp
                    EnableForcedLogoff              = $_.EnableForcedLogoff
                    EnableLeasing                   = $_.EnableLeasing
                    EnableMultiChannel              = $_.EnableMultiChannel
                    EnableOplocks                   = $_.EnableOplocks
                    EnableSecuritySignature         = $_.EnableSecuritySignature
                    EnableSMB1Protocol              = $_.EnableSMB1Protocol
                    EnableSMB2Protocol              = $_.EnableSMB2Protocol
                    EnableStrictNameChecking        = $_.EnableStrictNameChecking
                    EncryptData                     = $_.EncryptData
                    IrpStackSize                    = $_.IrpStackSize
                    KeepAliveTime                   = $_.KeepAliveTime
                    MaxChannelPerSession            = $_.MaxChannelPerSession
                    MaxMpxCount                     = $_.MaxMpxCount
                    MaxSessionPerConnection         = $_.MaxSessionPerConnection
                    MaxThreadsPerQueue              = $_.MaxThreadsPerQueue
                    MaxWorkItems                    = $_.MaxWorkItems
                    NullSessionPipes                = $_.NullSessionPipes
                    NullSessionShares               = $_.NullSessionShares
                    OplockBreakWait                 = $_.OplockBreakWait
                    PendingClientTimeoutInSeconds   = $_.PendingClientTimeoutInSeconds
                    RejectUnencryptedAccess         = $_.RejectUnencryptedAccess
                    RequireSecuritySignature        = $_.RequireSecuritySignature
                    ServerHidden                    = $_.ServerHidden
                    Smb2CreditsMax                  = $_.Smb2CreditsMax
                    Smb2CreditsMin                  = $_.Smb2CreditsMin
                    SmbServerNameHardeningLevel     = $_.SmbServerNameHardeningLevel
                    TreatHostAsStableStorage        = $_.TreatHostAsStableStorage
                    ValidateAliasNotCircular        = $_.ValidateAliasNotCircular
                    ValidateShareScope              = $_.ValidateShareScope
                    ValidateShareScopeNotAliased    = $_.ValidateShareScopeNotAliased
                    ValidateTargetName              = $_.ValidateTargetName
                }
            }
        }
        if ($CollectionComputers[1].Count -gt 0) {
            $Output = Get-SmbServerConfiguration -CimSession $CollectionComputers[1]
            foreach ($_ in $Output) {
                [PSCustomObject] @{
                    ComputerName                    = $_.PSComputerName
                    AnnounceComment                 = $_.AnnounceComment
                    AnnounceServer                  = $_.AnnounceServer
                    AsynchronousCredits             = $_.AsynchronousCredits
                    AuditSmb1Access                 = $_.AuditSmb1Access
                    AutoDisconnectTimeout           = $_.AutoDisconnectTimeout
                    AutoShareServer                 = $_.AutoShareServer
                    AutoShareWorkstation            = $_.AutoShareWorkstation
                    CachedOpenLimit                 = $_.CachedOpenLimit
                    DurableHandleV2TimeoutInSeconds = $_.DurableHandleV2TimeoutInSeconds
                    EnableAuthenticateUserSharing   = $_.EnableAuthenticateUserSharing
                    EnableDownlevelTimewarp         = $_.EnableDownlevelTimewarp
                    EnableForcedLogoff              = $_.EnableForcedLogoff
                    EnableLeasing                   = $_.EnableLeasing
                    EnableMultiChannel              = $_.EnableMultiChannel
                    EnableOplocks                   = $_.EnableOplocks
                    EnableSecuritySignature         = $_.EnableSecuritySignature
                    EnableSMB1Protocol              = $_.EnableSMB1Protocol
                    EnableSMB2Protocol              = $_.EnableSMB2Protocol
                    EnableStrictNameChecking        = $_.EnableStrictNameChecking
                    EncryptData                     = $_.EncryptData
                    IrpStackSize                    = $_.IrpStackSize
                    KeepAliveTime                   = $_.KeepAliveTime
                    MaxChannelPerSession            = $_.MaxChannelPerSession
                    MaxMpxCount                     = $_.MaxMpxCount
                    MaxSessionPerConnection         = $_.MaxSessionPerConnection
                    MaxThreadsPerQueue              = $_.MaxThreadsPerQueue
                    MaxWorkItems                    = $_.MaxWorkItems
                    NullSessionPipes                = $_.NullSessionPipes
                    NullSessionShares               = $_.NullSessionShares
                    OplockBreakWait                 = $_.OplockBreakWait
                    PendingClientTimeoutInSeconds   = $_.PendingClientTimeoutInSeconds
                    RejectUnencryptedAccess         = $_.RejectUnencryptedAccess
                    RequireSecuritySignature        = $_.RequireSecuritySignature
                    ServerHidden                    = $_.ServerHidden
                    Smb2CreditsMax                  = $_.Smb2CreditsMax
                    Smb2CreditsMin                  = $_.Smb2CreditsMin
                    SmbServerNameHardeningLevel     = $_.SmbServerNameHardeningLevel
                    TreatHostAsStableStorage        = $_.TreatHostAsStableStorage
                    ValidateAliasNotCircular        = $_.ValidateAliasNotCircular
                    ValidateShareScope              = $_.ValidateShareScope
                    ValidateShareScopeNotAliased    = $_.ValidateShareScopeNotAliased
                    ValidateTargetName              = $_.ValidateTargetName
                }
            }
        }
    )
    $SMB
}
function Get-ComputerSMBShare {
    <#
    .SYNOPSIS
    Retrieves SMB shares information from specified computers.
 
    .DESCRIPTION
    The Get-ComputerSMBShare function retrieves SMB share information from the specified computers. It can return basic share details or detailed information based on the 'Translated' switch.
 
    .PARAMETER ComputerName
    Specifies the names of the computers from which to retrieve SMB share information.
 
    .PARAMETER Translated
    Indicates whether to return detailed translated information about the SMB shares.
 
    .EXAMPLE
    Get-ComputerSMBShare -ComputerName "Server01" -Translated
    Retrieves detailed translated information about SMB shares from Server01.
 
    .EXAMPLE
    Get-ComputerSMBShare -ComputerName "Server01", "Server02"
    Retrieves basic SMB share information from Server01 and Server02.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName,
        [switch] $Translated
    )
    [Array] $CollectionComputers = Get-ComputerSplit -ComputerName $ComputerName

    if ($CollectionComputers[0].Count -gt 0) {
        $Output = Get-SmbShare
        foreach ($O in $Output) {
            if (-not $Translated) {
                Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -MemberType NoteProperty -Force
                $O
            }
            else {
                [PSCustomObject] @{
                    Name                  = $O.Name                   
                    ScopeName             = $O.ScopeName              
                    Path                  = $O.Path                   
                    Description           = $O.Description            
                    ComputerName          = $O.PSComputerName         
                    PresetPathAcl         = $O.PresetPathAcl          
                    ShareState            = $O.ShareState.ToString()             
                    AvailabilityType      = $O.AvailabilityType.ToString()       
                    ShareType             = $O.ShareType.ToString()              
                    FolderEnumerationMode = $O.FolderEnumerationMode.ToString()  
                    CachingMode           = $O.CachingMode.ToString()            
                    LeasingMode           = $O.LeasingMode.ToString()            
                    QoSFlowScope          = $O.QoSFlowScope           
                    SmbInstance           = $O.SmbInstance.ToString()            
                    CATimeout             = $O.CATimeout              
                    ConcurrentUserLimit   = $O.ConcurrentUserLimit    
                    ContinuouslyAvailable = $O.ContinuouslyAvailable  
                    CurrentUsers          = $O.CurrentUsers           
                    EncryptData           = $O.EncryptData            
                    Scoped                = $O.Scoped                 
                    SecurityDescriptor    = $O.SecurityDescriptor     
                    ShadowCopy            = $O.ShadowCopy             
                    Special               = $O.Special                
                    Temporary             = $O.Temporary              
                    Volume                = $O.Volume                 
                }
            }
        }
    }
    if ($CollectionComputers[1].Count -gt 0) {
        $Output = Get-SmbShare -CimSession $CollectionComputers[1]
        foreach ($O in $Output) {
            if (-not $Translated) {
                $O
            }
            else {
                [PSCustomObject] @{
                    Name                  = $O.Name                   
                    ScopeName             = $O.ScopeName              
                    Path                  = $O.Path                   
                    Description           = $O.Description            
                    ComputerName          = $O.PSComputerName         
                    PresetPathAcl         = $O.PresetPathAcl          
                    ShareState            = $O.ShareState.ToString()             
                    AvailabilityType      = $O.AvailabilityType.ToString()       
                    ShareType             = $O.ShareType.ToString()              
                    FolderEnumerationMode = $O.FolderEnumerationMode.ToString()  
                    CachingMode           = $O.CachingMode.ToString()            
                    LeasingMode           = $O.LeasingMode 
                    QoSFlowScope          = $O.QoSFlowScope           
                    SmbInstance           = $O.SmbInstance.ToString()            
                    CATimeout             = $O.CATimeout              
                    ConcurrentUserLimit   = $O.ConcurrentUserLimit    
                    ContinuouslyAvailable = $O.ContinuouslyAvailable  
                    CurrentUsers          = $O.CurrentUsers           
                    EncryptData           = $O.EncryptData            
                    Scoped                = $O.Scoped                 
                    SecurityDescriptor    = $O.SecurityDescriptor     
                    ShadowCopy            = $O.ShadowCopy             
                    Special               = $O.Special                
                    Temporary             = $O.Temporary              
                    Volume                = $O.Volume                 
                }
            }
        }
    }
}

function Get-ComputerSMBShareList {
    <#
    .SYNOPSIS
    Enumerate shares on a remote or local host and returns the name, type, and special remark for those shares.
 
    .DESCRIPTION
    Enumerate shares on a remote or local host and returns the name, type, and special remark for those shares.
    Doesnt return the permissions on the share, or logging to given computer
    Similar to 'net view /All \\ComputerName'
 
    .PARAMETER ComputerName
    The host to enumerate the shares for. Can be accepted as pipeline input by value.
 
    .PARAMETER Name
    The name of the share to filter on. Can be accepted as pipeline input by value.
 
    .OUTPUTS
    [PSCustomObject]@{
        ComputerName = [String]'The computer the share relates to'
        Name = [String]'The name of the share'
        Path = [string]'\\ComputerName\Name\'
        Type = [Win32Share.ShareType] An flag enum of the share properties, can be
            Disk = Disk drive share
            PrintQueue = Print queue share
            CommunicationDevice = Communication device share
            Ipc = Interprocess communication share
            Temporary = A temporary share
            Special = Typically a special/admin share like IPC$, C$, ADMIN$
        Remark = [String]'More info on the share'
        TotalBytes = [System.Nullable[int]]
        TotalFreeBytes = [System.Nullable[int]]
        FreeBytesAvailableToUser = [System.Nullable[int]]
    }
    .LINK
    https://gist.github.com/jborean93/017d3d890ae8d33276a08d3f5cc7eb45
 
    .EXAMPLE
    Get-ComputerSMBShareList -ComputerName some-host
 
    .EXAMPLE
    Get-ComputerSMBShareList -ComputerName "DC1" | ft -AutoSize
 
    .NOTES
    Original author: Jordan Borean (@jborean93)
 
    Modified by: Matt Cargile (@mattcargile)
    Modified by: Przemyslaw Klys
 
    #>

    [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'ComputerName', Position = 0)]
        [string[]] $ComputerName,

        [Parameter(ValueFromPipeline, ParameterSetName = 'Pipeline')]

        [string] $InputObject,
        [Parameter(ParameterSetName = 'ComputerName', Position = 1)]
        [Parameter(ParameterSetName = 'Pipeline')]
        [SupportsWildcards()][Alias('ShareName')][string[]] $Name,

        [switch] $SkipDiskSpace
    )

    begin {

        if (-not ('Win32Share.NativeMethods' -as [type])) {
            Add-Type -ErrorAction 'Stop' -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
namespace Win32Share
{
    public class NativeHelpers
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct SHARE_INFO_1
        {
            [MarshalAs(UnmanagedType.LPWStr)] public string shi1_netname;
            public ShareType shi1_type;
            [MarshalAs(UnmanagedType.LPWStr)] public string shi1_remark;
        }
    }
    public class NativeMethods
    {
        [DllImport("Netapi32.dll")]
        public static extern UInt32 NetApiBufferFree(
            IntPtr Buffer);
        [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern Int32 NetShareEnum(
            string servername,
            UInt32 level,
            ref IntPtr bufptr,
            UInt32 prefmaxlen,
            ref UInt32 entriesread,
            ref UInt32 totalentries,
            ref UInt32 resume_handle);
        [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool GetDiskFreeSpaceEx(
            string lpDirectoryName,
            ref UInt64 lpFreeBytesAvailableToCaller,
            ref UInt64 lptotalNumberOfBytes,
            ref UInt64 lpTotalNumberOfFreeBytes
        );
    }
    [Flags]
    public enum ShareType : uint
    {
        Disk = 0,
        PrintQueue = 1,
        CommunicationDevice = 2,
        Ipc = 3,
        Temporary = 0x40000000,
        Special = 0x80000000,
    }
}
'@

        }
    }

    process {

        foreach ($compNm in $ComputerName) {
            Write-Verbose -Message "Get-ComputerSMBShareList - Enumerating shares on '$compNm'"
            $PSBoundParameters['ComputerName'] = $compNm
            Get-ComputerSMBInfo @PSBoundParameters
        }
    }
}
function Get-ComputerSMBSharePermissions {
    <#
    .SYNOPSIS
    Retrieves SMB share permissions for specified computers and shares.
 
    .DESCRIPTION
    This function retrieves SMB share permissions for the specified computers and shares. It provides the option to translate the permissions into a more readable format.
 
    .PARAMETER ComputerName
    Specifies the names of the computers to retrieve SMB share permissions from.
 
    .PARAMETER ShareName
    Specifies the names of the shares to retrieve permissions for.
 
    .PARAMETER Translated
    Indicates whether to translate the permissions into a more readable format.
 
    .EXAMPLE
    Get-ComputerSMBSharePermissions -ComputerName "Server1" -ShareName "Share1" -Translated
    Retrieves SMB share permissions for Server1 and Share1 in a translated format.
 
    .EXAMPLE
    Get-ComputerSMBSharePermissions -ComputerName "Server1", "Server2" -ShareName "Share1", "Share2"
    Retrieves SMB share permissions for multiple servers and shares.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName,
        [Parameter(Mandatory = $true)][alias('Name')][string[]] $ShareName,
        [switch] $Translated
    )
    [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName
    if ($Computers[0].Count -gt 0) {
        foreach ($Share in $ShareName) {
            try {
                $Output = Get-SmbShareAccess -Name $Share -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message
                Write-Warning -Message "Get-ComputerSMBSharePermissions - Computer $Env:COMPUTERNAME, Share $Share, Error: $ErrorMessage"
            }
            foreach ($O in $Output) {
                if (-not $Translated) {
                    $O | Add-Member -Name 'PSComputerName' -Value $Env:COMPUTERNAME -MemberType NoteProperty -Force
                    $O
                }
                else {
                    $Identity = Convert-Identity -Identity $O.AccountName
                    [PSCustomObject] @{
                        Name              = $O.Name              
                        ScopeName         = $O.ScopeName         
                        AccountName       = $Identity.Name
                        AccountDomain     = $Identity.Domain
                        AccountSID        = $Identity.SID
                        AccountType       = $Identity.Type
                        AccountError      = $Identity.Error
                        AccessControlType = $O.AccessControlType.ToString() 
                        AccessRight       = $O.AccessRight.ToString()       
                        ComputerName      = $Env:COMPUTERNAME    
                    }
                }
            }
        }
    }
    if ($Computers[1].Count -gt 0) {
        foreach ($Share in $ShareName) {
            try {
                $Output = Get-SmbShareAccess -CimSession $Computers[1] -Name $Share -ErrorAction Stop
            }
            catch {
                $ErrorMessage = $_.Exception.Message
                Write-Warning -Message "Get-ComputerSMBSharePermissions - Computer $($Computers[1]), Share $Share, Error: $ErrorMessage"
            }
            foreach ($O in $Output) {
                if (-not $Translated) {
                    $O
                }
                else {
                    $Identity = Convert-Identity -Identity $O.AccountName
                    [PSCustomObject] @{
                        Name              = $O.Name              
                        ScopeName         = $O.ScopeName         
                        AccountName       = $Identity.Name
                        AccountDomain     = $Identity.Domain
                        AccountSID        = $Identity.SID
                        AccountType       = $Identity.Type
                        AccountError      = $Identity.Error
                        AccessControlType = $O.AccessControlType.ToString() 
                        AccessRight       = $O.AccessRight.ToString()       
                        ComputerName      = $O.PSComputerName    
                    }
                }
            }
        }
    }
}
function Get-ComputerStartup {
    <#
    .SYNOPSIS
    Retrieves information about startup programs on a remote computer.
 
    .DESCRIPTION
    The Get-ComputerStartup function retrieves information about startup programs on a specified computer using CIM/WMI.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to retrieve startup information from. Defaults to the local computer.
 
    .PARAMETER Protocol
    Specifies the protocol to use for the connection. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'.
 
    .PARAMETER All
    Indicates whether to retrieve all properties of the startup programs.
 
    .EXAMPLE
    Get-ComputerStartup -ComputerName "RemoteComputer" -Protocol Wsman
    Retrieves startup program information from a remote computer using the Wsman protocol.
 
    .EXAMPLE
    Get-ComputerStartup -All
    Retrieves all startup program information from the local computer.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'win32_startupCommand'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'Caption', 'Description', 'Command', 'Location', 'Name', 'User', 'UserSID', 'PSComputerName' 
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Caption      = $Data.Caption
                    Description  = $Data.Description

                    Command      = $Data.Command
                    Location     = $Data.Location
                    Name         = $Data.Name
                    User         = $Data.User
                    UserSID      = $Data.UserSID
                }
            }
        }
    }
}
function Get-ComputerSystem {
    <#
    .SYNOPSIS
    Retrieves computer system information from remote computers.
 
    .DESCRIPTION
    This function retrieves computer system information from remote computers using CIM/WMI queries.
 
    .PARAMETER ComputerName
    Specifies the names of the remote computers to retrieve system information from.
 
    .PARAMETER Protocol
    Specifies the protocol to use for the remote connection. Valid values are 'Default', 'Dcom', or 'Wsman'.
 
    .PARAMETER All
    Indicates whether to retrieve all available properties of the computer system.
 
    .EXAMPLE
    Get-ComputerSystem -ComputerName AD1, AD2, EVO1, ADFFS | ft -a *
 
    Retrieves computer system information for the specified computers and displays it in a table format.
 
    .NOTES
    This function uses CIM/WMI queries to gather system information from remote computers.
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $All
    )
    [string] $Class = 'Win32_ComputerSystem'
    if ($All) {
        $Properties = '*'
    }
    else {
        $Properties = 'PSComputerName', 'Name', 'Manufacturer' , 'Domain', 'Model' , 'Systemtype', 'PrimaryOwnerName', 'PCSystemType', 'PartOfDomain', 'CurrentTimeZone', 'BootupState', 'Roles', 'SystemFamily'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                [PSCustomObject] @{
                    ComputerName     = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Name             = $Data.Name
                    Manufacturer     = $Data.Manufacturer
                    Domain           = $Data.Domain
                    Model            = $Data.Model
                    Systemtype       = $Data.Systemtype
                    PrimaryOwnerName = $Data.PrimaryOwnerName
                    PCSystemType     = [Microsoft.PowerShell.Commands.PCSystemType] $Data.PCSystemType
                    PartOfDomain     = $Data.PartOfDomain
                    CurrentTimeZone  = $Data.CurrentTimeZone
                    BootupState      = $Data.BootupState
                    SystemFamily     = $Data.SystemFamily
                    Roles            = $Data.Roles -join ', '
                }
            }
        }
    }
}
function Get-ComputerTask {
    <#
    .SYNOPSIS
    Get Task Schedule information
 
    .DESCRIPTION
    Get Task Schedule information
 
    .PARAMETER ComputerName
    Specifies computer on which you want to run the operation.
 
    .EXAMPLE
    Get-ComputerTask | Format-Table
 
    .NOTES
    General notes
    #>

    [alias('Get-ComputerTasks')]
    [cmdletbinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME
    )
    foreach ($Computer in $ComputerName) {

        try {
            $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName
        }
        catch {
            $LocalComputerDNSName = $Computer
        }

        if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) {
            $TaskParameters = @{}
        }
        else {
            $TaskParameters = @{
                CimSession = $Computer
            }
        }

        $Tasks = Get-ScheduledTask @TaskParameters
        foreach ($Task in $Tasks) {
            $Info = $Task | Get-ScheduledTaskInfo @TaskParameters

            $Actions = foreach ($_ in $Task.Actions) {
                -join ($_.Execute, $_.Arguments)
            }

            [PSCustomObject] @{
                ComputerName                            = $Computer
                TaskName                                = $Task.TaskName
                TaskPath                                = $Task.TaskPath
                State                                   = $Task.State
                Actions                                 = $Actions
                Author                                  = $Task.Author
                Date                                    = $Task.Date
                Description                             = $Task.Description
                Documentation                           = $Task.Documentation
                PrincipalDisplayName                    = $Task.Principal.DisplayName
                PrincipalUserID                         = $Task.Principal.UserID
                PrincipalGroupID                        = $Task.Principal.GroupID
                PrincipalLogonType                      = $Task.Principal.LogonType
                PrincipalRunLevel                       = $Task.Principal.RunLevel
                PrincipalProcessTokenSidType            = $Task.Principal.ProcessTokenSidType
                PrincipalRequiredPrivilege              = $Task.Principal.RequiredPrivilege

                SettingsAllowDemandStart                = $Task.Settings.AllowDemandStart
                SettingsAllowHardTerminate              = $Task.Settings.AllowHardTerminate
                SettingsCompatibility                   = $Task.Settings.Compatibility
                SettingsDeleteExpiredTaskAfter          = $Task.Settings.DeleteExpiredTaskAfter
                SettingsDisallowStartIfOnBatteries      = $Task.Settings.DisallowStartIfOnBatteries
                SettingsEnabled                         = $Task.Settings.Enabled
                SettingsExecutionTimeLimit              = $Task.Settings.ExecutionTimeLimit
                SettingsHidden                          = $Task.Settings.Hidden
                SettingsIdleSettings                    = $Task.Settings.IdleSettings
                SettingsMultipleInstances               = $Task.Settings.MultipleInstances
                SettingsNetworkSettings                 = $Task.Settings.NetworkSettings
                SettingsPriority                        = $Task.Settings.Priority
                SettingsRestartCount                    = $Task.Settings.RestartCount
                SettingsRestartInterval                 = $Task.Settings.RestartInterval
                SettingsRunOnlyIfIdle                   = $Task.Settings.RunOnlyIfIdle
                SettingsRunOnlyIfNetworkAvailable       = $Task.Settings.RunOnlyIfNetworkAvailable
                SettingsStartWhenAvailable              = $Task.Settings.StartWhenAvailable
                SettingsStopIfGoingOnBatteries          = $Task.Settings.StopIfGoingOnBatteries
                SettingsWakeToRun                       = $Task.Settings.WakeToRun
                SettingsDisallowStartOnRemoteAppSession = $Task.Settings.DisallowStartOnRemoteAppSession
                SettingsUseUnifiedSchedulingEngine      = $Task.Settings.UseUnifiedSchedulingEngine
                SettingsMaintenanceSettings             = $Task.Settings.MaintenanceSettings
                SettingsVolatile                        = $Task.Settings.volatile
                Source                                  = $Task.Source

                URI                                     = $Task.URI
                Version                                 = $Task.Version
                LastRunTime                             = $Info.LastRunTime
                LastTaskResult                          = $Info.LastTaskResult
                NextRunTime                             = $Info.NextRunTime
                NumberOfMissedRuns                      = $Info.NumberOfMissedRuns
            }
        }
    }
}

function Get-ComputerTime {
    <#
    .SYNOPSIS
    Gets time difference between computers and time source including boot time
 
    .DESCRIPTION
    Gets time difference between computers and time source including boot time
 
    .PARAMETER TimeSource
    Parameter description
 
    .PARAMETER Domain
    Parameter description
 
    .PARAMETER TimeTarget
    Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM).
 
    .PARAMETER Credential
    Specifies a user account that has permission to perform this action. The default is the current user.
 
    .PARAMETER ForceCIM
 
    .PARAMETER ToLocal
 
    .EXAMPLE
    Get-ComputerTime -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
 
    Output
 
    Name LocalDateTime RemoteDateTime InstallTime LastBootUpTime TimeDifferenceMinutes TimeDifferenceSeconds TimeDifferenceMilliseconds TimeSourceName
    ---- ------------- -------------- ----------- -------------- --------------------- --------------------- -------------------------- --------------
    AD2 13.08.2019 23:40:26 13.08.2019 23:40:26 30.05.2018 18:30:48 09.08.2019 18:40:31 8,33333333333333E-05 0,005 5 AD1.ad.evotec.xyz
    AD3 13.08.2019 23:40:26 13.08.2019 17:40:26 26.05.2019 17:30:17 09.08.2019 18:40:30 0,000266666666666667 0,016 16 AD1.ad.evotec.xyz
    EVOWin 13.08.2019 23:40:26 13.08.2019 23:40:26 24.05.2019 22:46:45 09.08.2019 18:40:06 6,66666666666667E-05 0,004 4 AD1.ad.evotec.xyz
 
    .EXAMPLE
    Get-ComputerTime -TimeSource AD1 -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
 
    .EXAMPLE
    Get-ComputerTime -TimeSource 'pool.ntp.org' -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize
 
    .NOTES
    General notes
    #>


    [CmdletBinding()]
    param(
        [string] $TimeSource,
        [string] $Domain = $Env:USERDNSDOMAIN,
        [alias('ComputerName')][string[]] $TimeTarget = $ENV:COMPUTERNAME,
        [pscredential] $Credential,
        [switch] $ForceCIM
    )
    if (-not $TimeSource) {
        $TimeSource = (Get-ADDomainController -Discover -Service PrimaryDC -DomainName $Domain).HostName
    }

    if ($ForceCIM) {
        $TimeSourceInformation = Get-CimData -ComputerName $TimeSource -Class 'win32_operatingsystem' -Credential $Credential
        if ($TimeSourceInformation.LocalDateTime) {
            $TimeSourceInformation = $TimeSourceInformation.LocalDateTime
        }
        else {
            $TimeSourceInformation = $null
        }
    }
    else {
        $TimeSourceInformation = Get-ComputerTimeNtp -Server $TimeSource -ToLocal
    }

    $TimeTargetInformationCache = @{ }
    $TimeTargetInformation = Get-CimData -ComputerName $TimeTarget -Class 'win32_operatingsystem' -Credential $Credential
    foreach ($_ in $TimeTargetInformation) {
        $TimeTargetInformationCache[$_.PSComputerName] = $_
    }
    $TimeLocalCache = @{ }
    $TimeLocal = Get-CimData -ComputerName $TimeTarget -Class 'Win32_LocalTime' -Credential $Credential
    foreach ($_ in $TimeLocal) {
        $TimeLocalCache[$_.PSComputerName] = $_
    }

    $AllResults = foreach ($Computer in $TimeTarget) {
        $WMIComputerTime = $TimeLocalCache[$Computer]
        $WMIComputerTarget = $TimeTargetInformationCache[$Computer]

        if ($WMIComputerTime -and $WMIComputerTime.Year -and $WMIComputerTime.Month) {
            $RemoteDateTime = Get-Date -Year $WMIComputerTime.Year -Month $WMIComputerTime.Month -Day $WMIComputerTime.Day -Hour $WMIComputerTime.Hour -Minute $WMIComputerTime.Minute -Second $WMIComputerTime.Second
        }
        else {
            $RemoteDateTIme = ''
        }

        if ($WMIComputerTarget.LocalDateTime -and $TimeSourceInformation) {
            $Result = New-TimeSpan -Start $TimeSourceInformation -End $WMIComputerTarget.LocalDateTime
            $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime

            [PSCustomObject] @{
                Name                       = $Computer
                LocalDateTime              = $WMIComputerTarget.LocalDateTime
                RemoteDateTime             = $RemoteDateTime
                InstallTime                = $WMIComputerTarget.InstallDate
                LastBootUpTime             = $WMIComputerTarget.LastBootUpTime
                LastBootUpTimeInDays       = if ($null -ne $ResultFromBoot.TotalDays) {
                    [math]::Round($ResultFromBoot.TotalDays, 2) 
                }
                else {
                    $null 
                }
                TimeDifferenceMinutes      = if ($Result.TotalMinutes -lt 0) {
 ($Result.TotalMinutes * -1) 
                }
                else {
                    $Result.TotalMinutes 
                }
                TimeDifferenceSeconds      = if ($Result.TotalSeconds -lt 0) {
 ($Result.TotalSeconds * -1) 
                }
                else {
                    $Result.TotalSeconds 
                }
                TimeDifferenceMilliseconds = if ($Result.TotalMilliseconds -lt 0) {
 ($Result.TotalMilliseconds * -1) 
                }
                else {
                    $Result.TotalMilliseconds 
                }
                TimeSourceName             = $TimeSource
                Status                     = ''
            }
        }
        else {
            if ($WMIComputerTarget.LastBootUpTime) {
                $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime
            }
            else {
                $ResultFromBoot = $null
            }
            [PSCustomObject] @{
                Name                       = $Computer
                LocalDateTime              = $WMIComputerTarget.LocalDateTime
                RemoteDateTime             = $RemoteDateTime
                InstallTime                = $WMIComputerTarget.InstallDate
                LastBootUpTime             = $WMIComputerTarget.LastBootUpTime
                LastBootUpTimeInDays       = if ($ResultFromBoot) {
                    [math]::Round($ResultFromBoot.TotalDays, 2) 
                }
                else {
                    $null 
                }
                TimeDifferenceMinutes      = $null
                TimeDifferenceSeconds      = $null
                TimeDifferenceMilliseconds = $null
                TimeSourceName             = $TimeSource
                Status                     = 'Unable to get time difference.'
            }
        }
    }
    $AllResults
}
function Get-ComputerTimeNtp {
    <#
    .Synopsis
    Gets (Simple) Network Time Protocol time (SNTP/NTP, rfc-1305, rfc-2030) from a specified server
 
    .DESCRIPTION
    This function connects to an NTP server on UDP port 123 and retrieves the current NTP time.
    Selected components of the returned time information are decoded and returned in a PSObject.
 
    .PARAMETER Server
    The NTP Server to contact. Uses pool.ntp.org by default.
 
    .EXAMPLE
    Get-NtpTime uk.pool.ntp.org
    Gets time from the specified server.
 
    .EXAMPLE
    Get-NtpTime | fl *
    Get time from default server (pool.ntp.org) and displays all output object attributes.
 
    .FUNCTIONALITY
    Gets NTP time from a specified server.
 
    .NOTES
    Author https://github.com/ChrisWarwick/PowerShell-NTP-Time
    Slightly simplified for different usage scenarios
    #>

    [CmdletBinding()]
    Param (
        [String]$Server = 'pool.ntp.org',
        [switch]$ToLocal
    )

    $StartOfEpoch = New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc)

    [Byte[]]$NtpData = , 0 * 48
    $NtpData[0] = 0x1B    

    $Socket = [Net.Sockets.Socket]::new([Net.Sockets.AddressFamily]::InterNetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp)
    $Socket.SendTimeOut = 2000  
    $Socket.ReceiveTimeOut = 2000   

    Try {
        $Socket.Connect($Server, 123)
    }
    Catch {
        $_.Error
        Write-Warning "Get-ComputerTimeNtp - Failed to connect to server $Server"
        return
    }

    $t1 = Get-Date    

    Try {
        [Void]$Socket.Send($NtpData)
        [Void]$Socket.Receive($NtpData)
    }
    Catch {
        Write-Warning "Get-ComputerTimeNtp - Failed to communicate with server $Server"
        return
    }

    $t4 = Get-Date    

    $Socket.Shutdown("Both")
    $Socket.Close()

    $LI = ($NtpData[0] -band 0xC0) -shr 6    
    If ($LI -eq 3) {
        Write-Warning 'Get-ComputerTimeNtp - Alarm condition from server (clock not synchronized)'
        return
    }

    $IntPart = [BitConverter]::ToUInt32($NtpData[43..40], 0)
    $FracPart = [BitConverter]::ToUInt32($NtpData[47..44], 0)

    $t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)

    $IntPart = [BitConverter]::ToUInt32($NtpData[35..32], 0)
    $FracPart = [BitConverter]::ToUInt32($NtpData[39..36], 0)
    $t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000)

    $t1ms = ([TimeZoneInfo]::ConvertTimeToUtc($t1) - $StartOfEpoch).TotalMilliseconds
    $t4ms = ([TimeZoneInfo]::ConvertTimeToUtc($t4) - $StartOfEpoch).TotalMilliseconds

    $Offset = (($t2ms - $t1ms) + ($t3ms - $t4ms)) / 2

    [DateTime] $NTPDateTime = $StartOfEpoch.AddMilliseconds($t4ms + $Offset)

    if ($ToLocal) {
        $NTPDateTime.ToLocalTime()
    }
    else {
        $NTPDateTime
    }
}
function Get-ComputerWindowsFeatures {
    <#
    .SYNOPSIS
    Get Windows Features status on one or more computers/servers
 
    .DESCRIPTION
    Get Windows Features status on one or more computers/servers
 
    .PARAMETER ComputerName
    ComputerName to provide when executing query remotly. By default current computer name is used.
 
    .PARAMETER Protocol
    Protocol to use when gathering data. Choices are Default, Dcom, WSMan
 
    .PARAMETER EnabledOnly
    Returns only data if Windows Feature is enabled
 
    .PARAMETER All
    Gets all properties without any preprocessing
 
    .EXAMPLE
    Get-ComputerWindowsFeatures -EnabledOnly | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [switch] $EnabledOnly,
        [switch] $All
    )
    [string] $Class = 'Win32_OptionalFeature'
    if ($All) {
        [string] $Properties = '*'
    }
    else {
        [string[]] $Properties = 'Name', 'Caption' , 'Status', 'InstallState', 'InstallDate', 'PSComputerName'
    }

    $State = @{
        '1' = 'Enabled'
        '2' = 'Disabled'
        '3' = 'Absent'
        '4' = 'Unknown'
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        $Information
    }
    else {
        foreach ($Info in $Information) {
            foreach ($Data in $Info) {

                $InstallState = $State["$($Data.InstallState)"]
                if ($EnabledOnly -and $InstallState -ne 'Enabled') {
                    continue
                }
                [PSCustomObject] @{
                    ComputerName = if ($Data.PSComputerName) {
                        $Data.PSComputerName 
                    }
                    else {
                        $Env:COMPUTERNAME 
                    }
                    Name         = $Data.Name
                    Caption      = $Data.Caption
                    InstallState = $InstallState
                }
            }
        }
    }
}
function Get-ComputerWindowsUpdates {
    <#
    .SYNOPSIS
    Retrieves information about Windows updates installed on specified computers.
 
    .DESCRIPTION
    This function retrieves details about Windows updates installed on one or more computers specified by the ComputerName parameter.
 
    .PARAMETER ComputerName
    Specifies the name of the computer(s) to retrieve Windows update information for.
 
    .EXAMPLE
    Get-ComputerWindowsUpdates -ComputerName "EVOWIN", "AD1"
    Retrieves Windows update information for computers named "EVOWIN" and "AD1".
 
    .NOTES
    This function uses the Get-HotFix cmdlet to gather information about Windows updates.
    #>

    [CmdletBinding()]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME
    )
    foreach ($Computer in $ComputerName) {
        try {
            $Data = Get-HotFix -ComputerName $Computer
            $Output = foreach ($Update in $Data) {
                [PSCustomObject] @{
                    ComputerName = $Computer
                    InstalledOn  = $Update.InstalledOn
                    Description  = $Update.Description
                    KB           = $Update.HotFixId
                    InstalledBy  = $Update.InstalledBy
                    Caption      = $Update.Caption
                }
            }
            $Output | Sort-Object -Descending InstalledOn
        }
        catch {
            Write-Warning -Message "Get-ComputerWindowsUpdates - No data for computer $($Computer). Failed with errror: $($_.Exception.Message)"
        }
    }
}
function Get-OperatingSystem {
    <#
    .SYNOPSIS
    Retrieves information about Windows operating systems.
 
    .DESCRIPTION
    This function returns details about various versions of Windows operating systems, including their names, version numbers, code names, marketing names, build numbers, release dates, and support end dates.
 
    .PARAMETER Version
    Specifies the version number of the Windows operating system to retrieve information for.
 
    .EXAMPLE
    Get-OperatingSystem -Version '10.0 (19042)'
    Retrieves information about Windows 10 20H2.
 
    .EXAMPLE
    Get-OperatingSystem
    Retrieves information about all available Windows operating systems.
 
    #>

    [cmdletbinding()]
    param(
        [string] $Version
    )

    $ListOperatingSystems = [ordered] @{
        '10.0 (19043)' = [PSCustomObject] @{ Name = 'Windows 10 21H1'; Version = '10.0 (19043)'; CodeName = '21H1'; MarketingName = 'May 2021 Update'; BuildNumber = '19043';
            ReleaseDate = (Get-Date -Year 2021 -Month 5 -Day 18 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2022 -Month 12 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2022 -Month 12 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (19042)' = [PSCustomObject] @{ Name = 'Windows 10 20H2'; Version = '10.0 (19042)'; CodeName = '20H2'; MarketingName = 'October 2020 Update'; BuildNumber = '19042';
            ReleaseDate = (Get-Date -Year 2020 -Month 9 -Day 20 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2022 -Month 5 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2023 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (19041)' = [PSCustomObject] @{ Name = 'Windows 10 2004'; Version = '10.0 (19041)'; CodeName = '20H1'; MarketingName = 'May 2020 Update'; BuildNumber = '19041';
            ReleaseDate = (Get-Date -Year 2020 -Month 5 -Day 27 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2021 -Month 12 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 12 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (18363)' = [PSCustomObject] @{ Name = "Windows 10 1909"; Version = '10.0 (18363)'; CodeName = '19H2'; MarketingName = 'November 2019 Update'; BuildNumber = '18363';
            ReleaseDate = (Get-Date -Year 2019 -Month 11 -Day 12 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2022 -Month 5 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (18362)' = [PSCustomObject] @{ Name = "Windows 10 1903"; Version = '10.0 (18362)'; CodeName = '19H1'; MarketingName = 'May 2019 Update'; BuildNumber = '18362';
            ReleaseDate = (Get-Date -Year 2019 -Month 5 -Day 21 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 12 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2020 -Month 12 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (17763)' = [PSCustomObject] @{ Name = "Windows 10 1809"; Version = '10.0 (17763)'; CodeName = 'Redstone 5'; MarketingName = 'October 2018 Update'; BuildNumber = '17763';
            ReleaseDate = (Get-Date -Year 2018 -Month 11 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 11 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2029 -Month 1 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1)
        }
        '10.0 (17134)' = [PSCustomObject] @{ Name = "Windows 10 1803"; Version = '10.0 (17134)'; CodeName = 'Redstone 4'; MarketingName = 'April 2018 Update'; BuildNumber = '17134';
            ReleaseDate = (Get-Date -Year 2018 -Month 4 -Day 30 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 11 -Day 12 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (16299)' = [PSCustomObject] @{ Name = "Windows 10 1709"; Version = '10.0 (16299)'; CodeName = 'Redstone 3'; MarketingName = 'Fall Creators Update'; BuildNumber = '16299';
            ReleaseDate = (Get-Date -Year 2017 -Month 9 -Day 17 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2019 -Month 4 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2020 -Month 10 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); ; LTSC = $null
        }
        '10.0 (15063)' = [PSCustomObject] @{ Name = "Windows 10 1703"; Version = '10.0 (15063)'; CodeName = 'Redstone 2'; MarketingName = 'Creators Update'; BuildNumber = '15063';
            ReleaseDate = (Get-Date -Year 2017 -Month 4 -Day 5 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2018 -Month 10 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2019 -Month 10 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); ; LTSC = $null
        }
        '10.0 (14393)' = [PSCustomObject] @{ Name = "Windows 10 1607"; Version = '10.0 (14393)'; CodeName = 'Redstone 1'; MarketingName = 'Anniversary Update'; BuildNumber = '14393';
            ReleaseDate = (Get-Date -Year 2016 -Month 8 -Day 2 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2018 -Month 4 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2019 -Month 4 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2026 -Month 10 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1)
        }
        '10.0 (10586)' = [PSCustomObject] @{ Name = "Windows 10 1511"; Version = '10.0 (10586)'; CodeName = 'Threshold 2'; MarketingName = 'November Update'; BuildNumber = '10586';
            ReleaseDate = (Get-Date -Year 2015 -Month 11 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2017 -Month 10 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2018 -Month 4 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null
        }
        '10.0 (10240)' = [PSCustomObject] @{ Name = "Windows 10 1507"; Version = '10.0 (10240)' ; CodeName = 'Threshold 1'; MarketingName = 'N/A'; BuildNumber = '10240';
            ReleaseDate = (Get-Date -Year 2015 -Month 7 -Day 29 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2017 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2017 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2025 -Month 10 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1)
        }
    }
    if ($Version) {
        $ListOperatingSystems[$Version]
    }
    else {
        $ListOperatingSystems.Values
    }
}
function Get-IPAddressInformation {
    <#
    .SYNOPSIS
    Retrieves detailed information about an IP address using the ip-api.com service.
 
    .DESCRIPTION
    This function retrieves detailed information about the specified IP address using the ip-api.com service. It provides details such as country, region, city, ISP, and more.
 
    .PARAMETER IP
    Specifies the IP address for which information needs to be retrieved.
 
    .EXAMPLE
    Get-IpAddressInformation -IP "8.8.8.8"
    Retrieves information about the IP address "8.8.8.8" using the ip-api.com service.
 
    .NOTES
    This function requires an active internet connection to retrieve IP address information from the ip-api.com service.
    #>

    [cmdletbinding()]
    param(
        [string] $IP
    )
    try {
        $Information = Invoke-RestMethod -Method get -Uri "http://ip-api.com/json/$ip"
    }
    catch {
        $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
        Write-Warning "Get-IPAddressInformation - Error occured on IP $IP`: $ErrorMessage"
    }
    return $Information
}
function Get-MyIpAddress {
    <#
    .SYNOPSIS
    Retrieves the public IP address of the current machine using OpenDNS.
 
    .DESCRIPTION
    This function retrieves the public IP address of the current machine by querying OpenDNS servers. It returns the IP address as a string.
 
    .EXAMPLE
    Get-MyIpAddress
    Retrieves the public IP address of the current machine.
 
    .NOTES
    Author: Your Name
    Date: Current Date
    #>

    [alias('Get-MyIP')]
    [CmdletBinding()]
    param()
    $DNSParam = @{
        Name    = 'myip.opendns.com'
        Server  = 'resolver1.opendns.com'
        DnsOnly = $true
    }
    return Resolve-DnsName @DNSParam | ForEach-Object IPAddress
}
function Set-PasswordRemotely {
    <#
    .SYNOPSIS
    Set-PasswordRemotely function changes a user's password on a remote domain controller.
 
    .DESCRIPTION
    The Set-PasswordRemotely function allows changing a user's password securely on a remote domain controller. It requires the username, old password, new password, and optionally the domain controller's DNS name or IP address.
 
    .PARAMETER UserName
    Specifies the username of the account for which the password will be changed.
 
    .PARAMETER OldPassword
    Specifies the old password of the user account.
 
    .PARAMETER NewPassword
    Specifies the new password to set for the user account.
 
    .PARAMETER DomainController
    Specifies the domain controller's DNS name or IP address. If not provided, the function will prompt for it or automatically determine it if the machine is joined to a domain.
 
    .EXAMPLE
    Set-PasswordRemotely -UserName "JohnDoe" -OldPassword $SecureOldPassword -NewPassword $SecureNewPassword -DomainController "DC01"
 
    Description:
    Changes the password for the user account "JohnDoe" on the domain controller "DC01" using the provided old and new passwords.
 
    .EXAMPLE
    Set-PasswordRemotely -UserName "JaneSmith" -OldPassword $SecureOldPassword -NewPassword $SecureNewPassword
 
    Description:
    Changes the password for the user account "JaneSmith" on the domain controller determined automatically, using the provided old and new passwords.
    #>

    [CmdletBinding(DefaultParameterSetName = 'Secure')]
    param(
        [Parameter(ParameterSetName = 'Secure', Mandatory)][string] $UserName,
        [Parameter(ParameterSetName = 'Secure', Mandatory)][securestring] $OldPassword,
        [Parameter(ParameterSetName = 'Secure', Mandatory)][securestring] $NewPassword,
        [Parameter(ParameterSetName = 'Secure')][alias('DC', 'Server', 'ComputerName')][string] $DomainController
    )
    Begin {
        $DllImport = @'
[DllImport("netapi32.dll", CharSet = CharSet.Unicode)]
public static extern bool NetUserChangePassword(string domain, string username, string oldpassword, string newpassword);
'@

        $NetApi32 = Add-Type -MemberDefinition $DllImport -Name 'NetApi32' -Namespace 'Win32' -PassThru

        if (-not $DomainController) {
            if ($env:computername -eq $env:userdomain) {

                $DomainController = Read-Host -Prompt 'Domain Controller DNS name or IP Address'
            }
            else {
                $Domain = $Env:USERDNSDOMAIN
                $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $Domain)
                $DomainController = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($Context)).Name
            }
        }
    }
    Process {
        if ($DomainController -and $OldPassword -and $NewPassword -and $UserName) {
            $OldPasswordPlain = [System.Net.NetworkCredential]::new([string]::Empty, $OldPassword).Password
            $NewPasswordPlain = [System.Net.NetworkCredential]::new([string]::Empty, $NewPassword).Password

            $result = $NetApi32::NetUserChangePassword($DomainController, $UserName, $OldPasswordPlain, $NewPasswordPlain)
            if ($result) {
                Write-Host -Object "Set-PasswordRemotely - Password change for account $UserName failed on $DomainController. Please try again." -ForegroundColor Red
            }
            else {
                Write-Host -Object "Set-PasswordRemotely - Password change for account $UserName succeeded on $DomainController." -ForegroundColor Cyan
            }
        }
        else {
            Write-Warning "Set-PasswordRemotely - Password change for account failed. All parameters are required. "
        }
    }
    End {
        $OldPassword = $null
        $NewPassword = $null
        $OldPasswordPlain = $null
        $NewPasswordPlain = $null
        [System.GC]::Collect()
    }
}
function Convert-BinaryToHex {
    <#
    .SYNOPSIS
    Converts an array of binary numbers to hexadecimal format.
 
    .DESCRIPTION
    This function takes an array of binary numbers and converts them to hexadecimal format.
 
    .PARAMETER Binary
    Specifies the array of binary numbers to be converted to hexadecimal.
 
    .EXAMPLE
    Convert-BinaryToHex -Binary 1101 1010
    Converts the binary numbers 1101 and 1010 to hexadecimal format.
 
    .EXAMPLE
    1101 1010 | Convert-BinaryToHex
    Converts the binary numbers 1101 and 1010 piped into the function to hexadecimal format.
    #>

    param(
        [alias('Bin')]
        [Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)]
        [Byte[]]$Binary
    )
    if ($null -eq $Binary) {
        return
    }

    if ($Binary.Length -eq 1) {
        $Binary = @($input)
    }
    $Return = -join ($Binary |  ForEach-Object { "{0:X2}" -f $_ })
    $Return
}
function Convert-BinaryToString {
    <#
    .SYNOPSIS
    Converts an array of binary numbers to a string.
 
    .DESCRIPTION
    This function takes an array of binary numbers and converts them to a string using Unicode encoding.
 
    .PARAMETER Binary
    Specifies the array of binary numbers to be converted to a string.
 
    .EXAMPLE
    Convert-BinaryToString -Binary 01001000 01100101 01101100 01101100 01101111
    Converts the binary numbers to the string "Hello".
 
    .EXAMPLE
    01001000 01100101 01101100 01101100 01101111 | Convert-BinaryToString
    Converts the binary numbers piped into the function to the string "Hello".
    #>

    param(
        [alias('Bin')]
        [Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)]
        [Byte[]]$Binary
    )
    if ($null -ne $Binary) {
        [System.Text.Encoding]::Unicode.GetString($Binary)
    }
}
function Convert-Color {
    <#
    .Synopsis
    This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX)
    .Description
    This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX). Use it to convert your colors and prepare your graphics and HTML web pages.
    .Parameter RBG
    Enter the Red Green Blue value comma separated. Red: 51 Green: 51 Blue: 204 for example needs to be entered as 51,51,204
    .Parameter HEX
    Enter the Hex value to be converted. Do not use the '#' symbol. (Ex: 3333CC converts to Red: 51 Green: 51 Blue: 204)
    .Example
    .\convert-color -hex FFFFFF
    Converts hex value FFFFFF to RGB
 
    .Example
    .\convert-color -RGB 123,200,255
    Converts Red = 123 Green = 200 Blue = 255 to Hex value
 
    #>

    param(
        [Parameter(ParameterSetName = "RGB", Position = 0)]
        [ValidateScript( { $_ -match '^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$' })]
        $RGB,
        [Parameter(ParameterSetName = "HEX", Position = 0)]
        [ValidateScript( { $_ -match '[A-Fa-f0-9]{6}' })]
        [string]
        $HEX
    )
    switch ($PsCmdlet.ParameterSetName) {
        "RGB" {
            if ($null -eq $RGB[2]) {
                Write-Error "Value missing. Please enter all three values seperated by comma."
            }
            $red = [convert]::Tostring($RGB[0], 16)
            $green = [convert]::Tostring($RGB[1], 16)
            $blue = [convert]::Tostring($RGB[2], 16)
            if ($red.Length -eq 1) {
                $red = '0' + $red
            }
            if ($green.Length -eq 1) {
                $green = '0' + $green
            }
            if ($blue.Length -eq 1) {
                $blue = '0' + $blue
            }
            Write-Output $red$green$blue
        }
        "HEX" {
            $red = $HEX.Remove(2, 4)
            $Green = $HEX.Remove(4, 2)
            $Green = $Green.remove(0, 2)
            $Blue = $hex.Remove(0, 4)
            $Red = [convert]::ToInt32($red, 16)
            $Green = [convert]::ToInt32($green, 16)
            $Blue = [convert]::ToInt32($blue, 16)
            Write-Output $red, $Green, $blue
        }
    }
}
function Convert-CountryCodeToCountry {
    <#
    .SYNOPSIS
    Converts a country code to a country name, or when used with a switch to full culture information
 
    .DESCRIPTION
    Converts a country code to a country name, or when used with a switch to full culture information
 
    .PARAMETER CountryCode
    Country code
 
    .PARAMETER All
    Provide full culture information rather than just the country name
 
    .EXAMPLE
    Convert-CountryCodeToCountry -CountryCode 'PL'
 
    .EXAMPLE
    Convert-CountryCodeToCountry -CountryCode 'PL' -All
 
    .EXAMPLE
    $Test = Convert-CountryCodeToCountry
    $Test['PL']['Culture'] | fl
    $Test['PL']['RegionInformation']
 
    .EXAMPLE
    Convert-CountryCodeToCountry -CountryCode 'PL'
    Convert-CountryCodeToCountry -CountryCode 'POL'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [string] $CountryCode,
        [switch] $All
    )
    if ($Script:QuickSearch) {
        if ($PSBoundParameters.ContainsKey('CountryCode')) {
            if ($All) {
                $Script:QuickSearch[$CountryCode]
            }
            else {
                $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName
            }
        }
        else {
            $Script:QuickSearch
        }
    }
    else {
        $Script:QuickSearch = [ordered] @{}
        $AllCultures = [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures)
        foreach ($Culture in $AllCultures) {

            $RegionInformation = [System.Globalization.RegionInfo]::new($Culture)
            $Script:QuickSearch[$RegionInformation.TwoLetterISORegionName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
            $Script:QuickSearch[$RegionInformation.ThreeLetterISORegionName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
        }
        if ($PSBoundParameters.ContainsKey('CountryCode')) {
            if ($All) {
                $Script:QuickSearch[$CountryCode]
            }
            else {
                $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName
            }
        }
        else {
            $Script:QuickSearch
        }
    }
}
function Convert-CountryToContinent {
    <#
    .SYNOPSIS
    Convert country to continent
 
    .DESCRIPTION
    Convert country to continent or return a hashtable of countries and their corresponding continent.
    If the country is not found (for example empty), it will return "Unknown"
 
    .PARAMETER Country
    Country to convert. If country is not given it will return a hashtable of countries and their corresponding continent.
 
    .EXAMPLE
    Convert-CountryToContinent -Country "Poland"
 
    .EXAMPLE
    Convert-CountryToContinent
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $Country,
        [switch] $ReturnHashTable
    )
    $CountryToContinent = [ordered] @{
        "Afghanistan"                       = "Asia"
        "Albania"                           = "Europe"
        "Algeria"                           = "Africa"
        "Andorra"                           = "Europe"
        "Angola"                            = "Africa"
        "Antigua and Barbuda"               = "North America"
        "Argentina"                         = "South America"
        "Armenia"                           = "Asia"
        "Australia"                         = "Australia/Oceania"
        "Austria"                           = "Europe"
        "Azerbaijan"                        = "Asia"
        "Bahamas"                           = "North America"
        "Bahrain"                           = "Asia"
        "Bangladesh"                        = "Asia"
        "Barbados"                          = "North America"
        "Belarus"                           = "Europe"
        "Belgium"                           = "Europe"
        "Belize"                            = "North America"
        "Benin"                             = "Africa"
        "Bhutan"                            = "Asia"
        "Bolivia"                           = "South America"
        "Bosnia and Herzegovina"            = "Europe"
        "Botswana"                          = "Africa"
        "Brazil"                            = "South America"
        "Brunei"                            = "Asia"
        "Bulgaria"                          = "Europe"
        "Burkina Faso"                      = "Africa"
        "Burundi"                           = "Africa"
        "Cabo Verde"                        = "Africa"
        "Cambodia"                          = "Asia"
        "Cameroon"                          = "Africa"
        "Canada"                            = "North America"
        "Central African Republic"          = "Africa"
        "Chad"                              = "Africa"
        "Chile"                             = "South America"
        "China"                             = "Asia"
        "Colombia"                          = "South America"
        "Comoros"                           = "Africa"
        "Congo, Democratic Republic of the" = "Africa"
        "Congo, Republic of the"            = "Africa"
        "Costa Rica"                        = "North America"
        "Cote d'Ivoire"                     = "Africa"
        "Croatia"                           = "Europe"
        "Cuba"                              = "North America"
        "Cyprus"                            = "Asia"
        "Czechia"                           = "Europe"
        "Denmark"                           = "Europe"
        "Djibouti"                          = "Africa"
        "Dominica"                          = "North America"
        "Dominican Republic"                = "North America"
        "Ecuador"                           = "South America"
        "Egypt"                             = "Africa"
        "El Salvador"                       = "North America"
        "Equatorial Guinea"                 = "Africa"
        "Eritrea"                           = "Africa"
        "Estonia"                           = "Europe"
        "Eswatini"                          = "Africa"
        "Ethiopia"                          = "Africa"
        "Fiji"                              = "Australia/Oceania"
        "Finland"                           = "Europe"
        "France"                            = "Europe"
        "Gabon"                             = "Africa"
        "Gambia"                            = "Africa"
        "Georgia"                           = "Asia"
        "Germany"                           = "Europe"
        "Ghana"                             = "Africa"
        "Greece"                            = "Europe"
        "Grenada"                           = "North America"
        "Guatemala"                         = "North America"
        "Guinea"                            = "Africa"
        "Guinea-Bissau"                     = "Africa"
        "Guyana"                            = "South America"
        "Haiti"                             = "North America"
        "Honduras"                          = "North America"
        "Hungary"                           = "Europe"
        "Iceland"                           = "Europe"
        "India"                             = "Asia"
        "Indonesia"                         = "Asia"
        "Iran"                              = "Asia"
        "Iraq"                              = "Asia"
        "Ireland"                           = "Europe"
        "Israel"                            = "Asia"
        "Italy"                             = "Europe"
        "Jamaica"                           = "North America"
        "Japan"                             = "Asia"
        "Jordan"                            = "Asia"
        "Kazakhstan"                        = "Asia"
        "Kenya"                             = "Africa"
        "Kiribati"                          = "Australia/Oceania"
        "Kosovo"                            = "Europe"
        "Kuwait"                            = "Asia"
        "Kyrgyzstan"                        = "Asia"
        "Laos"                              = "Asia"
        "Latvia"                            = "Europe"
        "Lebanon"                           = "Asia"
        "Lesotho"                           = "Africa"
        "Liberia"                           = "Africa"
        "Libya"                             = "Africa"
        "Liechtenstein"                     = "Europe"
        "Lithuania"                         = "Europe"
        "Luxembourg"                        = "Europe"
        "Madagascar"                        = "Africa"
        "Malawi"                            = "Africa"
        "Malaysia"                          = "Asia"
        "Maldives"                          = "Asia"
        "Mali"                              = "Africa"
        "Malta"                             = "Europe"
        "Marshall Islands"                  = "Australia/Oceania"
        "Mauritania"                        = "Africa"
        "Mauritius"                         = "Africa"
        "Mexico"                            = "North America"
        "Micronesia"                        = "Australia/Oceania"
        "Moldova"                           = "Europe"
        "Monaco"                            = "Europe"
        "Mongolia"                          = "Asia"
        "Montenegro"                        = "Europe"
        "Morocco"                           = "Africa"
        "Mozambique"                        = "Africa"
        "Myanmar"                           = "Asia"
        "Namibia"                           = "Africa"
        "Nauru"                             = "Australia/Oceania"
        "Nepal"                             = "Asia"
        "Netherlands"                       = "Europe"
        "New Zealand"                       = "Australia/Oceania"
        "Nicaragua"                         = "North America"
        "Niger"                             = "Africa"
        "Nigeria"                           = "Africa"
        "North Korea"                       = "Asia"
        "North Macedonia"                   = "Europe"
        "Norway"                            = "Europe"
        "Oman"                              = "Asia"
        "Pakistan"                          = "Asia"
        "Palau"                             = "Australia/Oceania"
        "Panama"                            = "North America"
        "Papua New Guinea"                  = "Australia/Oceania"
        "Paraguay"                          = "South America"
        "Peru"                              = "South America"
        "Philippines"                       = "Asia"
        "Poland"                            = "Europe"
        "Portugal"                          = "Europe"
        "Qatar"                             = "Asia"
        "Romania"                           = "Europe"
        "Russia"                            = "Asia"
        "Rwanda"                            = "Africa"
        "Saint Kitts and Nevis"             = "North America"
        "Saint Lucia"                       = "North America"
        "Saint Vincent and the Grenadines"  = "North America"
        "Samoa"                             = "Australia/Oceania"
        "San Marino"                        = "Europe"
        "Sao Tome and Principe"             = "Africa"
        "Saudi Arabia"                      = "Asia"
        "Senegal"                           = "Africa"
        "Serbia"                            = "Europe"
        "Seychelles"                        = "Africa"
        "Sierra Leone"                      = "Africa"
        "Singapore"                         = "Asia"
        "Slovakia"                          = "Europe"
        "Slovenia"                          = "Europe"
        "Solomon Islands"                   = "Australia/Oceania"
        "Somalia"                           = "Africa"
        "South Africa"                      = "Africa"
        "South Korea"                       = "Asia"
        "South Sudan"                       = "Africa"
        "Spain"                             = "Europe"
        "Sri Lanka"                         = "Asia"
        "Sudan"                             = "Africa"
        "Suriname"                          = "South America"
        "Sweden"                            = "Europe"
        "Switzerland"                       = "Europe"
        "Syria"                             = "Asia"
        "Taiwan"                            = "Asia"
        "Tajikistan"                        = "Asia"
        "Tanzania"                          = "Africa"
        "Thailand"                          = "Asia"
        "Timor-Leste"                       = "Asia"
        "Togo"                              = "Africa"
        "Tonga"                             = "Australia/Oceania"
        "Trinidad and Tobago"               = "North America"
        "Tunisia"                           = "Africa"
        "Turkey"                            = "Asia"
        "Turkmenistan"                      = "Asia"
        "Tuvalu"                            = "Australia/Oceania"
        "Uganda"                            = "Africa"
        "Ukraine"                           = "Europe"
        "United Arab Emirates"              = "Asia"
        "United Kingdom"                    = "Europe"
        "United States of America"          = "North America"
        "Uruguay"                           = "South America"
        "Uzbekistan"                        = "Asia"
        "Vanuatu"                           = "Australia/Oceania"
        "Vatican City (Holy See)"           = "Europe"
        "Venezuela"                         = "South America"
        "Vietnam"                           = "Asia"
        "Yemen"                             = "Asia"
        "Zambia"                            = "Africa"
        "Zimbabwe"                          = "Africa"
    }
    if ($PSBoundParameters.ContainsKey('Country')) {
        if ($CountryToContinent[$Country]) {
            $CountryToContinent[$Country]
        }
        else {
            "Unknown"
        }
    }
    else {
        $CountryToContinent
    }
}
function Convert-CountryToCountryCode {
    <#
    .SYNOPSIS
    Converts a country name to a country code, or when used with a switch to full culture information
 
    .DESCRIPTION
    Converts a country name to a country code, or when used with a switch to full culture information
 
    .PARAMETER CountryName
    Country name in it's english name
 
    .PARAMETER All
    Provide full culture information rather than just the country code
 
    .EXAMPLE
    Convert-CountryToCountryCode -CountryName 'Poland'
 
    .EXAMPLE
    Convert-CountryToCountryCode -CountryName 'Poland' -All
 
    .EXAMPLE
    $Test = Convert-CountryToCountryCode
    $Test['India']['Culture']
    $Test['India']['RegionInformation']
 
    .EXAMPLE
    $Test = Convert-CountryToCountryCode
    $Test['Poland']['Culture']
    $Test['Poland']['RegionInformation']
 
    .EXAMPLE
    Convert-CountryToCountryCode -CountryName 'Polska'
    Convert-CountryToCountryCode -CountryName 'Poland'
    Convert-CountryToCountryCode -CountryName 'CZECH REPUBLIC'
    Convert-CountryToCountryCode -CountryName 'USA'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [string] $CountryName,
        [switch] $All
    )
    if ($Script:QuickSearchCountries) {
        if ($CountryName) {
            if ($All) {
                $Script:QuickSearchCountries[$CountryName]
            }
            else {
                if ($Script:QuickSearchCountries[$CountryName]) {
                    $Script:QuickSearchCountries[$CountryName].RegionInformation.TwoLetterISORegionName.ToUpper()
                }
                else {
                    if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                        throw "Country $CountryName not found"
                    }
                    else {
                        Write-Warning -Message "Convert-CountryToCountryCode - Country $CountryName name not found"
                    }
                }
            }
        }
        else {
            $Script:QuickSearchCountries
        }
    }
    else {
        $AllCultures = [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures)
        $Script:QuickSearchCountries = [ordered] @{

            'Czech Republic'     = @{
                'Culture'           = [cultureinfo] 'CZ'
                'RegionInformation' = [System.Globalization.RegionInfo] 'CZ'
            }
            'Korea, REPUBLIC OF' = @{
                'Culture'           = [cultureinfo] 'KR'
                'RegionInformation' = [System.Globalization.RegionInfo] 'KR'
            }
            'VIET NAM'           = @{
                'Culture'           = [cultureinfo] 'VN'
                'RegionInformation' = [System.Globalization.RegionInfo] 'VN'
            }
        }
        foreach ($Culture in $AllCultures) {
            $RegionInformation = [System.Globalization.RegionInfo]::new($Culture)
            $Script:QuickSearchCountries[$RegionInformation.EnglishName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
            $Script:QuickSearchCountries[$RegionInformation.DisplayName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
            $Script:QuickSearchCountries[$RegionInformation.NativeName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
            $Script:QuickSearchCountries[$RegionInformation.ThreeLetterISORegionName] = @{
                'Culture'           = $Culture
                'RegionInformation' = $RegionInformation
            }
        }
        if ($CountryName) {
            if ($All) {
                $Script:QuickSearchCountries[$CountryName]
            }
            else {
                if ($Script:QuickSearchCountries[$CountryName]) {
                    $Script:QuickSearchCountries[$CountryName].RegionInformation.TwoLetterISORegionName.ToUpper()
                }
                else {
                    if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                        throw "Country $CountryName not found"
                    }
                    else {
                        Write-Warning -Message "Convert-CountryToCountryCode - Country $CountryName name not found"
                    }
                }
            }
        }
        else {
            $Script:QuickSearchCountries
        }
    }
}
Function Convert-DomainFqdnToNetBIOS {
    <#
    .SYNOPSIS
    Converts FQDN to NetBIOS name for Active Directory Domain
 
    .DESCRIPTION
    Converts FQDN to NetBIOS name for Active Directory Domain
 
    .PARAMETER DomainName
    DomainName for current forest or trusted forest
 
    .EXAMPLE
    Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.xyz'
 
    .EXAMPLE
    Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.pl'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param (
        [string] $DomainName
    )
    if (-not $Script:CacheFQDN) {
        $Script:CacheFQDN = @{}
    }
    if ($Script:CacheFQDN[$DomainName]) {
        $Script:CacheFQDN[$DomainName]
    }
    else {
        $objRootDSE = [System.DirectoryServices.DirectoryEntry] "LDAP://$DomainName/RootDSE"
        $ConfigurationNC = $objRootDSE.configurationNamingContext
        $Searcher = [System.DirectoryServices.DirectorySearcher] @{
            SearchScope = "subtree"
            SearchRoot  = "LDAP://cn=Partitions,$ConfigurationNC"
            Filter      = "(&(objectcategory=Crossref)(dnsRoot=$DomainName)(netbiosname=*))"
        }
        $null = $Searcher.PropertiesToLoad.Add("netbiosname")
        $Script:CacheFQDN[$DomainName] = ($Searcher.FindOne()).Properties.Item("netbiosname")
        $Script:CacheFQDN[$DomainName]
    }
}
function Convert-DomainToSid {
    <#
    .SYNOPSIS
    Converts Domain Name to SID
 
    .DESCRIPTION
    Converts Domain Name to SID
 
    .PARAMETER DomainName
    DomainName for current forest or trusted forest
 
    .EXAMPLE
    Convert-DomainToSid -DomainName 'test.evotec.pl'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [parameter(Mandatory)][string] $DomainName
    )
    try {
        $BinarySID = ([ADSI]"LDAP://$DomainName").objectsid
        $DomainSidValue = [System.Security.Principal.SecurityIdentifier]::new($BinarySID.Value, 0).Value
        $DomainSidValue
    }
    catch {
        Write-Warning -Message "Convert-DomainToSid - Failed conversion with error $($_.Exception.Message)"
    }
}
function Convert-ExchangeEmail {
    <#
    .SYNOPSIS
    Converts a list of Exchange email addresses into a readable and exportable format.
 
    .DESCRIPTION
    This function takes a list of Exchange email addresses and processes them to make them more readable and suitable for export.
 
    .PARAMETER Emails
    List of email addresses in Exchange or Exchange Online format, also known as proxy addresses.
 
    .PARAMETER Separator
    The separator to use between each processed email address. Default is ', '.
 
    .PARAMETER RemoveDuplicates
    Switch to remove duplicate email addresses from the list.
 
    .PARAMETER RemovePrefix
    Switch to remove any prefixes like 'SMTP:', 'SIP:', 'spo:', etc. from the email addresses.
 
    .PARAMETER AddSeparator
    Switch to join the processed email addresses using the specified separator.
 
    .EXAMPLE
    $Emails = @()
    $Emails += 'SIP:test@email.com'
    $Emails += 'SMTP:elo@maiu.com'
    $Emails += 'sip:elo@maiu.com'
    $Emails += 'Spo:dfte@sdsd.com'
    $Emails += 'SPO:myothertest@sco.com'
 
    Convert-ExchangeEmail -Emails $Emails -RemovePrefix -RemoveDuplicates -AddSeparator
    #>

    #>

    [CmdletBinding()]
    param(
        [string[]] $Emails,
        [string] $Separator = ', ',
        [switch] $RemoveDuplicates,
        [switch] $RemovePrefix,
        [switch] $AddSeparator
    )

    if ($RemovePrefix) {

        $Emails = $Emails -replace 'smtp:', '' -replace 'sip:', '' -replace 'spo:', ''
    }
    if ($RemoveDuplicates) {
        $Emails = $Emails | Sort-Object -Unique
    }
    if ($AddSeparator) {
        $Emails = $Emails -join $Separator
    }
    return $Emails
}
function Convert-ExchangeItems {
    <#
    .SYNOPSIS
    Converts the count of Exchange items to a specified default value if the count is null.
 
    .DESCRIPTION
    This function takes the count of Exchange items and returns the count if it is not null. If the count is null, it returns the specified default value.
 
    .PARAMETER Count
    The count of Exchange items to be processed.
 
    .PARAMETER Default
    The default value to return if the count is null. Default is 'N/A'.
 
    .EXAMPLE
    Convert-ExchangeItems -Count 10 -Default 'No items'
    # Returns 10
 
    .EXAMPLE
    Convert-ExchangeItems -Count $null -Default 'No items'
    # Returns 'No items'
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [int] $Count,
        [string] $Default = 'N/A'
    )
    if ($null -eq $Count) {
        return $Default
    }
    else {
        return $Count
    }
}

function Convert-ExchangeRecipient {
    <#
    .SYNOPSIS
    Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name
 
    .DESCRIPTION
    Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name
 
    .PARAMETER RecipientTypeDetails
    RecipientTypeDetails to convert
 
    .PARAMETER RecipientType
    RecipientType to convert
 
    .PARAMETER RemoteRecipientType
    Parameter description
 
    .EXAMPLE
    $Users = Get-ADUser -Filter * -Properties Mail, ProxyAddresses, msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails, MailNickName
    $UsersModified = foreach ($User in $Users) {
        [PSCUstomObject] @{
            Name = $User.Name
            Mail = $User.Mail
            MailNickName = $User.MailNickName
            msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType
            msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType
            msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails
            ProxyAddresses = Convert-ExchangeEmail -AddSeparator -RemovePrefix -RemoveDuplicates -Separator ',' -Emails $User.ProxyAddresses
        }
    }
    $UsersModified | Out-HtmlView -Filtering -ScrollX
 
    .EXAMPLE
    Convert-ExchangeRecipient -msExchRemoteRecipientType 17
    Convert-ExchangeRecipient -msExchRecipientDisplayType 17
    Convert-ExchangeRecipient -msExchRecipientTypeDetails 17
 
    .NOTES
    Based on:
    - https://granikos.eu/exchange-recipient-type-values/
    - https://answers.microsoft.com/en-us/msoffice/forum/all/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651
    - https://www.undocumented-features.com/2020/05/06/every-last-msexchrecipientdisplaytype-and-msexchrecipienttypedetails-value/
    #>

    [alias('Convert-ExchangeRecipientDetails')]
    [cmdletbinding(DefaultParameterSetName = 'msExchRecipientTypeDetails')]
    param(
        [parameter(ParameterSetName = 'msExchRecipientTypeDetails')][alias('RecipientTypeDetails')][string] $msExchRecipientTypeDetails,
        [parameter(ParameterSetName = 'msExchRecipientDisplayType')][alias('RecipientType')][string] $msExchRecipientDisplayType,
        [parameter(ParameterSetName = 'msExchRemoteRecipientType')][alias('RemoteRecipientType')][string] $msExchRemoteRecipientType,

        [parameter(ParameterSetName = 'msExchRecipientTypeDetails')]
        [parameter(ParameterSetName = 'msExchRecipientDisplayType')]
        [parameter(ParameterSetName = 'msExchRemoteRecipientType')]
        [switch] $All
    )

    if ($PSBoundParameters.ContainsKey('msExchRecipientTypeDetails')) {
        $ListMsExchRecipientTypeDetails = [ordered] @{
            '0'               = 'None'
            '1'               = 'UserMailbox'
            '2'               = 'LinkedMailbox'
            '4'               = 'SharedMailbox'
            '8'               = 'LegacyMailbox'
            '16'              = 'RoomMailbox'
            '32'              = 'EquipmentMailbox'
            '64'              = 'MailContact'
            '128'             = 'MailUser'
            '256'             = 'MailUniversalDistributionGroup'
            '512'             = 'MailNonUniversalGroup'
            '1024'            = 'MailUniversalSecurityGroup'
            '2048'            = 'DynamicDistributionGroup'
            '4096'            = 'PublicFolder'
            '8192'            = 'SystemAttendantMailbox'
            '16384'           = 'SystemMailbox'
            '32768'           = 'MailForestContact'
            '65536'           = 'User'
            '131072'          = 'Contact'
            '262144'          = 'UniversalDistributionGroup'
            '524288'          = 'UniversalSecurityGroup'
            '1048576'         = 'NonUniversalGroup'
            '2097152'         = 'Disable User'
            '4194304'         = 'MicrosoftExchange'
            '8388608'         = 'ArbitrationMailbox'
            '16777216'        = 'MailboxPlan'
            '33554432'        = 'LinkedUser'
            '268435456'       = 'RoomList'
            '536870912'       = 'DiscoveryMailbox'
            '1073741824'      = 'RoleGroup'
            '2147483648'      = 'RemoteUserMailbox'
            '4294967296'      = 'Computer'
            '8589934592'      = 'RemoteRoomMailbox'
            '17179869184'     = 'RemoteEquipmentMailbox'
            '34359738368'     = 'RemoteSharedMailbox'
            '68719476736'     = 'PublicFolderMailbox'
            '137438953472'    = 'Team Mailbox'
            '274877906944'    = 'RemoteTeamMailbox'
            '549755813888'    = 'MonitoringMailbox'
            '1099511627776'   = 'GroupMailbox'
            '2199023255552'   = 'LinkedRoomMailbox'
            '4398046511104'   = 'AuditLogMailbox'
            '8796093022208'   = 'RemoteGroupMailbox'
            '17592186044416'  = 'SchedulingMailbox'
            '35184372088832'  = 'GuestMailUser'
            '70368744177664'  = 'AuxAuditLogMailbox'
            '140737488355328' = 'SupervisoryReviewPolicyMailbox'
        }
        if ($All) {
            $ListMsExchRecipientTypeDetails
        }
        else {
            if ($null -ne $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails]) {
                $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails]
            }
            else {
                $msExchRecipientTypeDetails
            }
        }
    }
    elseif ($PSBoundParameters.ContainsKey('msExchRecipientDisplayType')) {
        $ListMsExchRecipientDisplayType = [ordered] @{
            '0'           = 'MailboxUser'
            '1'           = 'DistributionGroup'
            '2'           = 'PublicFolder'
            '3'           = 'DynamicDistributionGroup'
            '4'           = 'Organization'
            '5'           = 'PrivateDistributionList'
            '6'           = 'RemoteMailUser'
            '7'           = 'ConferenceRoomMailbox'
            '8'           = 'EquipmentMailbox'
            '10'          = 'ArbitrationMailbox'
            '11'          = 'MailboxPlan'
            '12'          = 'LinkedUser'
            '15'          = 'RoomList'
            '17'          = 'Microsoft365Group' 
            '-2147483642' = 'SyncedMailboxUser'
            '-2147483391' = 'SyncedUDGasUDG'
            '-2147483386' = 'SyncedUDGasContact'
            '-2147483130' = 'SyncedPublicFolder'
            '-2147482874' = 'SyncedDynamicDistributionGroup'
            '-2147482106' = 'SyncedRemoteMailUser'
            '-2147481850' = 'SyncedConferenceRoomMailbox'
            '-2147481594' = 'SyncedEquipmentMailbox'
            '-2147481343' = 'SyncedUSGasUDG'
            '-2147481338' = 'SyncedUSGasContact'
            '-1073741818' = 'ACLableSyncedMailboxUser'
            '-1073740282' = 'ACLableSyncedRemoteMailUser'
            '-1073739514' = 'ACLableSyncedUSGasContact'
            '-1073739511' = 'SyncedUSGasUSG'
            '1043741833'  = 'SecurityDistributionGroup'
            '1073739511'  = 'SyncedUSGasUSG'
            '1073739514'  = 'ACLableSyncedUSGasContact'
            '1073741824'  = 'ACLableMailboxUser' 
            '1073741830'  = 'ACLableRemoteMailUser'
        }
        if ($All) {
            $ListMsExchRecipientDisplayType
        }
        else {
            if ($null -ne $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType]) {
                $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType]
            }
            else {
                $msExchRecipientDisplayType
            }
        }
    }
    elseif ($PSBoundParameters.ContainsKey('msExchRemoteRecipientType')) {
        $ListMsExchRemoteRecipientType = [ordered] @{

            '1'   = 'ProvisionMailbox'
            '2'   = 'ProvisionArchive (On-Prem Mailbox)'
            '3'   = 'ProvisionMailbox, ProvisionArchive'
            '4'   = 'Migrated (UserMailbox)'
            '6'   = 'ProvisionArchive, Migrated'
            '8'   = 'DeprovisionMailbox'
            '10'  = 'ProvisionArchive, DeprovisionMailbox'
            '16'  = 'DeprovisionArchive (On-Prem Mailbox)'
            '17'  = 'ProvisionMailbox, DeprovisionArchive'
            '20'  = 'Migrated, DeprovisionArchive'
            '24'  = 'DeprovisionMailbox, DeprovisionArchive'
            '33'  = 'ProvisionMailbox, RoomMailbox'
            '35'  = 'ProvisionMailbox, ProvisionArchive, RoomMailbox'
            '36'  = 'Migrated, RoomMailbox'
            '38'  = 'ProvisionArchive, Migrated, RoomMailbox'
            '49'  = 'ProvisionMailbox, DeprovisionArchive, RoomMailbox'
            '52'  = 'Migrated, DeprovisionArchive, RoomMailbox'
            '65'  = 'ProvisionMailbox, EquipmentMailbox'
            '67'  = 'ProvisionMailbox, ProvisionArchive, EquipmentMailbox'
            '68'  = 'Migrated, EquipmentMailbox'
            '70'  = 'ProvisionArchive, Migrated, EquipmentMailbox'
            '81'  = 'ProvisionMailbox, DeprovisionArchive, EquipmentMailbox'
            '84'  = 'Migrated, DeprovisionArchive, EquipmentMailbox'
            '100'    = 'Migrated, SharedMailbox'
            '102'    = 'ProvisionArchive, Migrated, SharedMailbox'
            '116'    = 'Migrated, DeprovisionArchive, SharedMailbox'
        }
        if ($All) {
            $ListMsExchRemoteRecipientType
        }
        else {
            if ($null -ne $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType]) {
                $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType]
            }
            else {
                $msExchRemoteRecipientType
            }
        }
    }
}

function Convert-ExchangeSize {
    <#
    .SYNOPSIS
    Converts the size of Exchange data to a specified unit of measurement.
 
    .DESCRIPTION
    This function takes the size of Exchange data and converts it to the specified unit of measurement (Bytes, KB, MB, GB, TB).
 
    .PARAMETER To
    The unit of measurement to convert the size to. Default is 'MB'.
 
    .PARAMETER Size
    The size of Exchange data to be converted.
 
    .PARAMETER Precision
    The number of decimal places to round the converted size to. Default is 4.
 
    .PARAMETER Display
    Switch to display the converted size with the unit of measurement.
 
    .PARAMETER Default
    The default value to return if the size is null or empty. Default is 'N/A'.
 
    .EXAMPLE
    Convert-ExchangeSize -To MB -Size '49 GB (52,613,349,376 bytes)'
    # Returns the size converted to MB.
 
    .EXAMPLE
    Convert-ExchangeSize -To GB -Size '49 GB (52,613,349,376 bytes)' -Precision 2 -Display
    # Returns the size converted to GB with 2 decimal places and displays the result.
 
    #>

    [cmdletbinding()]
    param(
        [validateset("Bytes", "KB", "MB", "GB", "TB")][string]$To = 'MB',
        [string]$Size,
        [int]$Precision = 4,
        [switch]$Display,
        [string]$Default = 'N/A'
    )
    if ([string]::IsNullOrWhiteSpace($Size)) {
        return $Default
    }
    $Pattern = [Regex]::new('(?<=\()([0-9]*[,.].*[0-9])')  
    $Value = ($Size | Select-String $Pattern -AllMatches).Matches.Value

    if ($null -ne $Value) {
        $Value = $Value.Replace(',', '').Replace('.', '')
    }

    switch ($To) {
        "Bytes" {
            return $value
        }
        "KB" {
            $Value = $Value / 1KB
        }
        "MB" {
            $Value = $Value / 1MB
        }
        "GB" {
            $Value = $Value / 1GB
        }
        "TB" {
            $Value = $Value / 1TB
        }
    }

    if ($Display) {
        return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To"
    }
    else {
        return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero)
    }
}
function ConvertFrom-Color {
    <#
    .SYNOPSIS
    Converts color names or hex codes to different formats.
 
    .DESCRIPTION
    ConvertFrom-Color function converts color names or hex codes to different formats such as decimal values or System.Drawing.Color objects.
 
    .PARAMETER Color
    Specifies the color names or hex codes to convert.
 
    .PARAMETER AsDecimal
    Indicates whether to convert the color to a decimal value.
 
    .PARAMETER AsDrawingColor
    Indicates whether to convert the color to a System.Drawing.Color object.
 
    .EXAMPLE
    ConvertFrom-Color -Color Red, Blue -AsDecimal
    Converts the colors Red and Blue to decimal values.
 
    .EXAMPLE
    ConvertFrom-Color -Color "#FFA500" -AsDrawingColor
    Converts the color with hex code #FFA500 to a System.Drawing.Color object.
 
    #>

    [alias('Convert-FromColor')]
    [CmdletBinding()]
    param (
        [ValidateScript( {
                if ($($_ -in $Script:RGBColors.Keys -or $_ -match "^#([A-Fa-f0-9]{6})$" -or $_ -eq "") -eq $false) {
                    throw "The Input value is not a valid colorname nor an valid color hex code."
                }
                else {
                    $true 
                }
            })]
        [alias('Colors')][string[]] $Color,
        [switch] $AsDecimal,
        [switch] $AsDrawingColor
    )
    $Colors = foreach ($C in $Color) {
        $Value = $Script:RGBColors."$C"
        if ($C -match "^#([A-Fa-f0-9]{6})$") {
            $C
            continue
        }
        if ($null -eq $Value) {
            continue
        }
        $HexValue = Convert-Color -RGB $Value
        Write-Verbose "Convert-FromColor - Color Name: $C Value: $Value HexValue: $HexValue"
        if ($AsDecimal) {
            [Convert]::ToInt64($HexValue, 16)
        }
        elseif ($AsDrawingColor) {
            [System.Drawing.Color]::FromArgb("#$($HexValue)")
        }
        else {
            "#$($HexValue)"
        }
    }
    $Colors
}
$ScriptBlockColors = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $Script:RGBColors.Keys | Where-Object { $_ -like "$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName ConvertFrom-Color -ParameterName Color -ScriptBlock $ScriptBlockColors
function Convert-HexToBinary {
    <#
    .SYNOPSIS
    Converts a hexadecimal string to a binary representation.
 
    .DESCRIPTION
    This function takes a hexadecimal string as input and converts it to a binary representation.
 
    .PARAMETER Hex
    Specifies the hexadecimal string to convert to binary.
 
    .EXAMPLE
    Convert-HexToBinary -Hex "1A"
    # Outputs: 00011010
 
    .EXAMPLE
    "1A" | Convert-HexToBinary
    # Outputs: 00011010
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [string] $Hex
    )
    $return = for ($i = 0; $i -lt $Hex.Length ; $i += 2) {
        [Byte]::Parse($Hex.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
    }
    Write-Output $return -NoEnumerate
}
function Convert-Identity {
    <#
    .SYNOPSIS
    Small command that tries to resolve any given object
 
    .DESCRIPTION
    Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname
 
    .PARAMETER Identity
    Type to resolve in form of Identity, DN, SID
 
    .PARAMETER SID
    Allows to pass SID directly, rather then going thru verification process
 
    .PARAMETER Name
    Allows to pass Name directly, rather then going thru verification process
 
    .PARAMETER Force
    Allows to clear cache, useful when you want to force refresh
 
    .EXAMPLE
    $Identity = @(
        'S-1-5-4'
        'S-1-5-4'
        'S-1-5-11'
        'S-1-5-32-549'
        'S-1-5-32-550'
        'S-1-5-32-548'
        'S-1-5-64-10'
        'S-1-5-64-14'
        'S-1-5-64-21'
        'S-1-5-18'
        'S-1-5-19'
        'S-1-5-32-544'
        'S-1-5-20-20-10-51' # Wrong SID
        'S-1-5-21-853615985-2870445339-3163598659-512'
        'S-1-5-21-3661168273-3802070955-2987026695-512'
        'S-1-5-21-1928204107-2710010574-1926425344-512'
        'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl'
        'Test Local Group'
        'przemyslaw.klys@evotec.pl'
        'test2'
        'NT AUTHORITY\NETWORK'
        'NT AUTHORITY\SYSTEM'
        'S-1-5-21-853615985-2870445339-3163598659-519'
        'TEST\some'
        'EVOTECPL\Domain Admins'
        'NT AUTHORITY\INTERACTIVE'
        'INTERACTIVE'
        'EVOTEC\Domain Admins'
        'EVOTECPL\Domain Admins'
        'Test\Domain Admins'
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid
        'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid
        'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached
    )
 
    $TestOutput = Convert-Identity -Identity $Identity -Verbose
 
    Output:
 
    Name SID DomainName Type Error
    ---- --- ---------- ---- -----
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup
    BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup
    BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup
    BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup
    NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup
    NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup
    NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup
    BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative
    S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative
    EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative
    EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative
    test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup
    NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative
    EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative
    TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup
    EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative
    EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
    S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated."
    TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Identity')]
    param(
        [parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity,
        [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID,
        [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name,
        [switch] $Force
    )
    Begin {

        if (-not $Script:GlobalCacheSidConvert -or $Force) {
            $Script:GlobalCacheSidConvert = @{

                'NT AUTHORITY\SYSTEM'                         = [PSCustomObject] @{
                    Name       = 'BUILTIN\Administrators'
                    SID        = 'S-1-5-18'
                    DomainName = ''
                    Type       = 'WellKnownAdministrative'
                    Error      = ''
                }

                'BUILTIN\Administrators'                      = [PSCustomObject] @{
                    Name       = 'BUILTIN\Administrators'
                    SID        = 'S-1-5-32-544'
                    DomainName = ''
                    Type       = 'WellKnownAdministrative'
                    Error      = ''
                }
                'BUILTIN\Users'                               = [PSCustomObject] @{
                    Name       = 'BUILTIN\Users'
                    SID        = 'S-1-5-32-545'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Guests'                              = [PSCustomObject] @{
                    Name       = 'BUILTIN\Guests'
                    SID        = 'S-1-5-32-546'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Power Users'                         = [PSCustomObject] @{
                    Name       = 'BUILTIN\Power Users'
                    SID        = 'S-1-5-32-547'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Account Operators'                   = [PSCustomObject] @{
                    Name       = 'BUILTIN\Account Operators'
                    SID        = 'S-1-5-32-548'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Server Operators'                    = [PSCustomObject] @{
                    Name       = 'BUILTIN\Server Operators'
                    SID        = 'S-1-5-32-549'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Print Operators'                     = [PSCustomObject] @{
                    Name       = 'BUILTIN\Print Operators'
                    SID        = 'S-1-5-32-550'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Backup Operators'                    = [PSCustomObject] @{
                    Name       = 'BUILTIN\Backup Operators'
                    SID        = 'S-1-5-32-551'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Replicator'                          = [PSCustomObject] @{
                    Name       = 'BUILTIN\Replicators'
                    SID        = 'S-1-5-32-552'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Pre-Windows 2000 Compatible Access'  = [PSCustomObject] @{
                    Name       = 'BUILTIN\Pre-Windows 2000 Compatible Access'
                    SID        = 'S-1-5-32-554'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Remote Desktop Users'                = [PSCustomObject] @{
                    Name       = 'BUILTIN\Remote Desktop Users'
                    SID        = 'S-1-5-32-555'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Network Configuration Operators'     = [PSCustomObject] @{
                    Name       = 'BUILTIN\Network Configuration Operators'
                    SID        = 'S-1-5-32-556'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Incoming Forest Trust Builders'      = [PSCustomObject] @{
                    Name       = 'BUILTIN\Incoming Forest Trust Builders'
                    SID        = 'S-1-5-32-557'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Performance Monitor Users'           = [PSCustomObject] @{
                    Name       = 'BUILTIN\Performance Monitor Users'
                    SID        = 'S-1-5-32-558'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Performance Log Users'               = [PSCustomObject] @{
                    Name       = 'BUILTIN\Performance Log Users'
                    SID        = 'S-1-5-32-559'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Windows Authorization Access Group'  = [PSCustomObject] @{
                    Name       = 'BUILTIN\Windows Authorization Access Group'
                    SID        = 'S-1-5-32-560'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Terminal Server License Servers'     = [PSCustomObject] @{
                    Name       = 'BUILTIN\Terminal Server License Servers'
                    SID        = 'S-1-5-32-561'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Distributed COM Users'               = [PSCustomObject] @{
                    Name       = 'BUILTIN\Distributed COM Users'
                    SID        = 'S-1-5-32-562'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\IIS_IUSRS'                           = [PSCustomObject] @{
                    Name       = 'BUILTIN\IIS_IUSRS'
                    SID        = 'S-1-5-32-568'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Cryptographic Operators'             = [PSCustomObject] @{
                    Name       = 'BUILTIN\Cryptographic Operators'
                    SID        = 'S-1-5-32-569'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Event Log Readers'                   = [PSCustomObject] @{
                    Name       = 'BUILTIN\Event Log Readers'
                    SID        = 'S-1-5-32-573'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Certificate Service DCOM Access'     = [PSCustomObject] @{
                    Name       = 'BUILTIN\Certificate Service DCOM Access'
                    SID        = 'S-1-5-32-574'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\RDS Remote Access Servers'           = [PSCustomObject] @{
                    Name       = 'BUILTIN\RDS Remote Access Servers'
                    SID        = 'S-1-5-32-575'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\RDS Endpoint Servers'                = [PSCustomObject] @{
                    Name       = 'BUILTIN\RDS Endpoint Servers'
                    SID        = 'S-1-5-32-576'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\RDS Management Servers'              = [PSCustomObject] @{
                    Name       = 'BUILTIN\RDS Management Servers'
                    SID        = 'S-1-5-32-577'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Hyper-V Administrators'              = [PSCustomObject] @{
                    Name       = 'BUILTIN\Hyper-V Administrators'
                    SID        = 'S-1-5-32-578'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Access Control Assistance Operators' = [PSCustomObject] @{
                    Name       = 'BUILTIN\Access Control Assistance Operators'
                    SID        = 'S-1-5-32-579'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'BUILTIN\Remote Management Users'             = [PSCustomObject] @{
                    Name       = 'BUILTIN\Remote Management Users'
                    SID        = 'S-1-5-32-580'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'Window Manager\Window Manager Group'         = [PSCustomObject] @{
                    Name       = 'Window Manager\Window Manager Group'
                    SID        = 'S-1-5-90-0'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'NT SERVICE\WdiServiceHost'                   = [PSCustomObject] @{
                    Name       = 'NT SERVICE\WdiServiceHost'
                    SID        = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'NT SERVICE\MSSQLSERVER'                      = [PSCustomObject] @{
                    Name       = 'NT SERVICE\MSSQLSERVER'
                    SID        = 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'NT SERVICE\SQLSERVERAGENT'                   = [PSCustomObject] @{
                    Name       = 'NT SERVICE\SQLSERVERAGENT'
                    SID        = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'NT SERVICE\SQLTELEMETRY'                     = [PSCustomObject] @{
                    Name       = 'NT SERVICE\SQLTELEMETRY'
                    SID        = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
                'NT SERVICE\ADSync'                           = [PSCustomObject] @{
                    Name       = 'NT SERVICE\ADSync'
                    SID        = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }

                'NT Service\himds'                            = [PSCustomObject] @{
                    Name       = 'NT Service\himds'
                    SID        = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083'
                    DomainName = ''
                    Type       = 'WellKnownGroup'
                    Error      = ''
                }
            }
        }
    }
    Process {
        if ($Identity) {
            foreach ($Ident in $Identity) {
                $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+")
                if ($Script:GlobalCacheSidConvert[$Ident]) {

                    Write-Verbose "Convert-Identity - Processing $Ident (Cache)"
                    $Script:GlobalCacheSidConvert[$Ident]
                }
                elseif ($MatchRegex.Success) {

                    Write-Verbose "Convert-Identity - Processing $Ident (SID)"
                    if ($MatchRegex.Value -ne $Ident) {
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value
                    }
                    else {
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                }
                elseif ($Ident -like '*DC=*') {

                    Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)"
                    try {
                        $Object = [adsi]"LDAP://$($Ident)"
                        $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    }
                    catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{
                            Name       = $Ident
                            SID        = $null
                            DomainName = ''
                            Type       = 'Unknown'
                            Error      = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                }
                else {

                    Write-Verbose "Convert-Identity - Processing $Ident (Other)"
                    try {
                        $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue
                    }
                    catch {
                        $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{
                            Name       = $Ident
                            SID        = $null
                            DomainName = ''
                            Type       = 'Unknown'
                            Error      = $_.Exception.Message -replace [environment]::NewLine, ' '
                        }
                    }
                    $Script:GlobalCacheSidConvert[$Ident]
                }
            }
        }
        else {
            if ($SID) {
                foreach ($S in $SID) {
                    if ($Script:GlobalCacheSidConvert[$S]) {
                        $Script:GlobalCacheSidConvert[$S]
                    }
                    else {
                        $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S
                        $Script:GlobalCacheSidConvert[$S]
                    }
                }
            }
            else {
                foreach ($Ident in $Name) {
                    if ($Script:GlobalCacheSidConvert[$Ident]) {
                        $Script:GlobalCacheSidConvert[$Ident]
                    }
                    else {
                        $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        $Script:GlobalCacheSidConvert[$Ident]
                    }
                }
            }
        }
    }
    End {
    }
}
function Convert-IpAddressToPtr {
    <#
    .SYNOPSIS
    Converts an IP address to a PTR record
 
    .DESCRIPTION
    This function takes an IP address as input and converts it to a PTR record.
 
    .PARAMETER IPAddress
    The IP address to convert
 
    .PARAMETER AsObject
    Should the function return an object instead of a string?
 
    .EXAMPLE
    Convert-IpAddressToPtr -IPAddress "10.1.2.3"
 
    .EXAMPLE
    Convert-IpAddressToPtr -IPAddress "10.1.2.3", "8.8.8.8" -AsObject
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string[]]$IPAddress,
        [switch] $AsObject
    )
    foreach ($IP in $IPAddress) {

        $octets = $IP -split "\."

        [array]::Reverse($octets)

        $ptrString = ($octets -join ".") + ".in-addr.arpa"

        if ($AsObject) {
            [pscustomobject] @{
                IPAddress = $IP
                PTR       = $ptrString
            }
        }
        else {
            $ptrString
        }
    }
}
function Convert-KeyToKeyValue {
    <#
    .SYNOPSIS
    Converts keys of an object to key-value pairs.
 
    .DESCRIPTION
    This function takes an object and converts its keys to key-value pairs, where the key is the original key concatenated with its corresponding value.
 
    .PARAMETER Object
    Specifies the object whose keys are to be converted to key-value pairs.
 
    .EXAMPLE
    $Object = @{
        Key1 = 'Value1'
        Key2 = 'Value2'
    }
    Convert-KeyToKeyValue -Object $Object
    # Returns a new hash table with keys as 'Key1 (Value1)' and 'Key2 (Value2)'.
 
    #>

    [CmdletBinding()]
    param (
        [object] $Object
    )
    $NewHash = [ordered] @{}
    foreach ($O in $Object.Keys) {
        $KeyName = "$O ($($Object.$O))"
        $KeyValue = $Object.$O
        $NewHash.$KeyName = $KeyValue
    }
    return $NewHash
}
function Convert-Office365License {
    <#
    .SYNOPSIS
    Converts Office 365 licenses between their names and SKUs.
 
    .DESCRIPTION
    This function allows for the conversion of Office 365 licenses between their names and SKUs. It provides flexibility to handle multiple values for licenses.
 
    .PARAMETER License
    Specifies the Office 365 license SKU or name to convert. Supports multiple values.
 
    .PARAMETER ToSku
    Indicates whether to convert the license name to SKU.
 
    .PARAMETER Separator
    Specifies the separator to use when returning multiple values.
 
    .PARAMETER ReturnArray
    Indicates whether to return the result as an array.
 
    .EXAMPLE
    Convert-Office365License -License 'VISIOCLIENT','PROJECTONLINE_PLAN_1','test','tenant:VISIOCLIENT'
    Converts the specified licenses to their corresponding SKUs.
 
    .EXAMPLE
    Convert-Office365License -License "Office 365 A3 for faculty", "Project Plan 3 (for Department)", 'test' -ToSku
    Converts the specified license names to their corresponding SKUs.
 
    .NOTES
    For more information on Office 365 licensing, refer to: https://learn.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromPipeline)][Array] $License,
        [alias('SKU')][switch] $ToSku,
        [string] $Separator = ', ',
        [switch] $ReturnArray
    )
    Begin {
        $O365SKU = [ordered] @{
            'AAD_BASIC'                                                             = "Azure Active Directory Basic"
            'AAD_BASIC_EDU'                                                         = "Azure Active Directory Basic for Education"
            'AAD_EDU'                                                               = "Azure Active Directory for Education"
            'AAD_PREMIUM'                                                           = "Azure Active Directory Premium P1"
            'AAD_PREMIUM_FACULTY'                                                   = "Azure Active Directory Premium P1 for Faculty"
            'AAD_PREMIUM_P2'                                                        = "Azure Active Directory Premium P2"
            'AAD_SMB'                                                               = "Azure Active Directory"
            'ADALLOM_FOR_AATP'                                                      = "SecOps Investigation for MDI"
            'ADALLOM_O365'                                                          = "Office 365 Cloud App Security"
            'ADALLOM_S_DISCOVERY'                                                   = "CLOUD APP SECURITY DISCOVERY"
            'ADALLOM_S_O365'                                                        = "Office 365 Cloud App Security"
            'ADALLOM_S_STANDALONE'                                                  = "MICROSOFT CLOUD APP SECURITY"
            'ADALLOM_S_STANDALONE_DOD'                                              = "Microsoft Defender for Cloud Apps for DOD"
            'ADALLOM_STANDALONE'                                                    = "Microsoft Cloud App Security"
            'ADV_COMMS'                                                             = "Advanced Communications"
            'ATA'                                                                   = "Microsoft Defender for Identity"
            'ATP_ENTERPRISE'                                                        = "Microsoft Defender for Office 365 (Plan 1)"
            'ATP_ENTERPRISE_FACULTY'                                                = "Microsoft Defender for Office 365 (Plan 1) Faculty"
            'ATP_ENTERPRISE_GOV'                                                    = "Microsoft Defender for Office 365 (Plan 1) GCC"
            'AX7_USER_TRIAL'                                                        = "Microsoft Dynamics AX7 User Trial"
            'BI_AZURE_P_2_GOV'                                                      = "Power BI Pro for Government"
            'BI_AZURE_P0'                                                           = "Power BI (free)"
            'BI_AZURE_P1'                                                           = "Microsoft Power BI Reporting and Analytics Plan 1"
            'BI_AZURE_P2'                                                           = "Power BI Pro"
            'BI_AZURE_P3'                                                           = "Power BI Premium Per User"
            'BPOS_S_DlpAddOn'                                                       = "Data Loss Prevention"
            'BPOS_S_TODO_1'                                                         = "To-Do (Plan 1)"
            'BPOS_S_TODO_2'                                                         = "To-Do (Plan 2)"
            'BPOS_S_TODO_3'                                                         = "To-Do (Plan 3)"
            'BPOS_S_TODO_FIRSTLINE'                                                 = "To-Do (Firstline)"
            'CCIBOTS_PRIVPREV_VIRAL'                                                = "Power Virtual Agents Viral Trial"
            'CDS_ATTENDED_RPA'                                                      = "Common Data Service Attended RPA"
            'CDS_CUSTOMER_INSIGHTS'                                                 = "Common Data Service for Customer Insights"
            'CDS_CUSTOMER_INSIGHTS_BASE'                                            = "Dataverse for Customer Insights�BASE"
            'CDS_CUSTOMER_INSIGHTS_TRIAL'                                           = "Common Data Service for Customer Insights Trial"
            'CDS_DB_CAPACITY'                                                       = "Common Data Service Database Capacity"
            'CDS_DB_CAPACITY_GOV'                                                   = "Common Data Service Database Capacity for Government"
            'CDS_FILE_CAPACITY'                                                     = "Common Data Service for Apps File Capacity"
            'CDS_Flow_Business_Process'                                             = "Common data service for Flow per business process plan"
            'CDS_FORM_PRO_USL'                                                      = "Common Data Service"
            'CDS_LOG_CAPACITY'                                                      = "Common Data Service Log Capacity"
            'CDS_O365_E5_KM'                                                        = "Common Data Service for SharePoint Syntex"
            'CDS_O365_F1'                                                           = "Common Data Service for Teams"
            'CDS_O365_F1_GCC'                                                       = "Common Data Service for Teams_F1 GCC"
            'CDS_O365_P1'                                                           = "COMMON DATA SERVICE FOR TEAMS_P1"
            'CDS_O365_P1_GCC'                                                       = "Common Data Service for Teams_P1 GCC"
            'CDS_O365_P2'                                                           = "Common Data Service for Teams"
            'CDS_O365_P2_GCC'                                                       = "COMMON DATA SERVICE FOR TEAMS_P2 GCC"
            'CDS_O365_P3'                                                           = "Common Data Service for Teams"
            'CDS_O365_P3_GCC'                                                       = "Common Data Service for Teams"
            'CDS_PER_APP'                                                           = "CDS PowerApps per app plan"
            'CDS_PER_APP_IWTRIAL'                                                   = "CDS Per app baseline access"
            'CDS_POWERAPPS_PORTALS_LOGIN'                                           = "Common Data Service Power Apps Portals Login Capacity"
            'CDS_POWERAPPS_PORTALS_LOGIN_GCC'                                       = "Common Data Service Power Apps Portals Login Capacity for GCC"
            'CDS_POWERAPPS_PORTALS_PAGEVIEW'                                        = "CDS PowerApps Portals page view capacity add-on"
            'CDS_POWERAPPS_PORTALS_PAGEVIEW_GCC'                                    = "CDS PowerApps Portals page view capacity add-on for GCC"
            'CDS_REMOTE_ASSIST'                                                     = "Common Data Service for Remote Assist"
            'CDS_UNATTENDED_RPA'                                                    = "Common Data Service Unattended RPA"
            'CDS_VIRTUAL_AGENT_BASE'                                                = "Common Data Service for Virtual Agent Base"
            'CDS_VIRTUAL_AGENT_USL'                                                 = "Common Data Service"
            'CDSAICAPACITY'                                                         = "AI Builder Capacity add-on"
            'CDSAICAPACITY_PERAPP'                                                  = "AI Builder capacity Per App add-on"
            'CDSAICAPACITY_PERUSER'                                                 = "AI Builder capacity Per User add-on"
            'CDSAICAPACITY_PERUSER_NEW'                                             = "AI Builder capacity Per User add-on"
            'CMPA_addon'                                                            = "Compliance Manager Premium Assessment Add-On"
            'CMPA_addon_GCC'                                                        = "Compliance Manager Premium Assessment Add-On for GCC"
            'COMMUNICATIONS_COMPLIANCE'                                             = "Microsoft Communications Compliance"
            'COMMUNICATIONS_DLP'                                                    = "Microsoft Communications DLP"
            'COMPLIANCE_MANAGER_PREMIUM_ASSESSMENT_ADDON'                           = "Compliance Manager Premium Assessment Add-On"
            'Content_Explorer'                                                      = "Information Protection and Governance Analytics - Premium"
            'ContentExplorer_Standard'                                              = "Information Protection and Governance Analytics � Standard"
            'CORTEX'                                                                = "Microsoft Viva Topics"
            'CPC_1'                                                                 = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB"
            'CPC_2'                                                                 = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB"
            'CPC_B_1C_2RAM_64GB'                                                    = "Windows 365 Business 1 vCPU 2 GB 64 GB"
            'CPC_B_2C_4RAM_128GB'                                                   = "Windows 365 Business 2 vCPU 4 GB 128 GB"
            'CPC_B_2C_4RAM_256GB'                                                   = "Windows 365 Business 2 vCPU 4 GB 256 GB"
            'CPC_B_2C_4RAM_64GB'                                                    = "Windows 365 Business 2 vCPU 4 GB 64 GB"
            'CPC_B_2C_8RAM_128GB'                                                   = "Windows 365 Business 2 vCPU 8 GB 128 GB"
            'CPC_B_2C_8RAM_256GB'                                                   = "Windows 365 Business 2 vCPU 8 GB 256 GB"
            'CPC_B_4C_16RAM_128GB'                                                  = "Windows 365 Business 4 vCPU 16 GB 128 GB"
            'CPC_B_4C_16RAM_128GB_WHB'                                              = "Windows 365 Business 4 vCPU 16 GB 128 GB (with Windows Hybrid Benefit)"
            'CPC_B_4C_16RAM_256GB'                                                  = "Windows 365 Business 4 vCPU 16 GB 256 GB"
            'CPC_B_4C_16RAM_512GB'                                                  = "Windows 365 Business 4 vCPU 16 GB 512 GB"
            'CPC_B_8C_32RAM_128GB'                                                  = "Windows 365 Business 8 vCPU 32 GB 128 GB"
            'CPC_B_8C_32RAM_256GB'                                                  = "Windows 365 Business 8 vCPU 32 GB 256 GB"
            'CPC_B_8C_32RAM_512GB'                                                  = "Windows 365 Business 8 vCPU 32 GB 512 GB"
            'CPC_E_1C_2GB_64GB'                                                     = "Windows 365 Enterprise 1 vCPU 2 GB 64 GB"
            'CPC_E_2C_4GB_128GB'                                                    = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB"
            'CPC_E_2C_4GB_256GB'                                                    = "Windows 365 Enterprise 2 vCPU 4 GB 256 GB"
            'CPC_E_2C_4GB_64GB'                                                     = "Windows 365 Enterprise 2 vCPU 4 GB 64 GB"
            'CPC_E_2C_8GB_128GB'                                                    = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB"
            'CPC_E_2C_8GB_256GB'                                                    = "Windows 365 Enterprise 2 vCPU 8 GB 256 GB"
            'CPC_E_4C_16GB_128GB'                                                   = "Windows 365 Enterprise 4 vCPU 16 GB 128 GB"
            'CPC_E_4C_16GB_256GB'                                                   = "Windows 365 Enterprise 4 vCPU 16 GB 256 GB"
            'CPC_E_4C_16GB_512GB'                                                   = "Windows 365 Enterprise 4 vCPU 16 GB 512 GB"
            'CPC_E_8C_32GB_128GB'                                                   = "Windows 365 Enterprise 8 vCPU 32 GB 128 GB"
            'CPC_E_8C_32GB_256GB'                                                   = "Windows 365 Enterprise 8 vCPU 32 GB 256 GB"
            'CPC_E_8C_32GB_512GB'                                                   = "Windows 365 Enterprise 8 vCPU 32 GB 512 GB"
            'CPC_LVL_1'                                                             = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB (Preview)"
            'CPC_LVL_2'                                                             = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB (Preview)"
            'CPC_LVL_3'                                                             = "Windows 365 Enterprise 4 vCPU 16 GB 256 GB (Preview)"
            'CPC_S_2C_4GB_128GB'                                                    = "Windows 365 Shared Use 2 vCPU 4 GB 128 GB"
            'CPC_S_2C_4GB_256GB'                                                    = "Windows 365 Shared Use 2 vCPU 4 GB 256 GB"
            'CPC_S_2C_4GB_64GB'                                                     = "Windows 365 Shared Use 2 vCPU 4 GB 64 GB"
            'CPC_S_2C_8GB_128GB'                                                    = "Windows 365 Shared Use 2 vCPU 8 GB 128 GB"
            'CPC_S_2C_8GB_256GB'                                                    = "Windows 365 Shared Use 2 vCPU 8 GB 256 GB"
            'CPC_S_4C_16GB_128GB'                                                   = "Windows 365 Shared Use 4 vCPU 16 GB 128 GB"
            'CPC_S_4C_16GB_256GB'                                                   = "Windows 365 Shared Use 4 vCPU 16 GB 256 GB"
            'CPC_S_4C_16GB_512GB'                                                   = "Windows 365 Shared Use 4 vCPU 16 GB 512 GB"
            'CPC_S_8C_32GB_128GB'                                                   = "Windows 365 Shared Use 8 vCPU 32 GB 128 GB"
            'CPC_S_8C_32GB_256GB'                                                   = "Windows 365 Shared Use 8 vCPU 32 GB 256 GB"
            'CPC_S_8C_32GB_512GB'                                                   = "Windows 365 Shared Use 8 vCPU 32 GB 512 GB"
            'CPC_SS_2'                                                              = "Windows 365 Business 2 vCPU, 8 GB, 128 GB"
            'CRM_AUTO_ROUTING_ADDON'                                                = "Dynamics 365 Field Service, Enterprise Edition - Resource Scheduling Optimization"
            'CRM_AUTO_ROUTING_ENGINE_ADDON'                                         = "Field Service � Automated Routing Engine Add-On"
            'CRM_HYBRIDCONNECTOR'                                                   = "Dynamics 365 Hybrid Connector"
            'CRM_ONLINE_PORTAL'                                                     = "Dynamics 365 Enterprise Edition - Additional Portal (Qualified Offer)"
            'CRMINSTANCE'                                                           = "Dynamics 365 - Additional Production Instance (Qualified Offer)"
            'CRMPLAN2'                                                              = "Microsoft Dynamics CRM Online Basic"
            'CRMSTANDARD'                                                           = "Microsoft Dynamics CRM Online"
            'CRMSTORAGE'                                                            = "Dynamics 365 - Additional Database Storage (Qualified Offer)"
            'CRMTESTINSTANCE'                                                       = "Dynamics 365 - Additional Non-Production Instance (Qualified Offer)"
            'CUSTOMER_KEY'                                                          = "Microsoft Customer Key"
            'CUSTOMER_VOICE_ADDON'                                                  = "Dynamics Customer Voice Add-On"
            'Customer_Voice_Base'                                                   = "Dynamics 365 Customer Voice Base Plan"
            'Customer_Voice_Customer_Insights'                                      = "Microsoft Dynamics 365 Customer Voice for Customer Insights App"
            'CUSTOMER_VOICE_DYN365_VIRAL_TRIAL'                                     = "Customer Voice for Dynamics 365 vTrial"
            'D365_AssetforSCM'                                                      = "Asset Maintenance Add-in"
            'D365_CSI_EMBED_CE'                                                     = "Dynamics 365 Customer Service Insights for CE Plan"
            'D365_CSI_EMBED_CSEnterprise'                                           = "Dynamics 365 Customer Service Insights for CS Enterprise"
            'D365_CUSTOMER_SERVICE_ENT_ATTACH'                                      = "Dynamics 365 for Customer Service Enterprise Attach to Qualifying Dynamics 365 Base Offer A"
            'D365_FIELD_SERVICE_ATTACH'                                             = "Dynamics 365 for Field Service Attach to Qualifying Dynamics 365 Base Offer"
            'D365_Finance'                                                          = "Microsoft Dynamics 365 for Finance"
            'D365_IOTFORSCM'                                                        = "Iot Intelligence Add-in for D365 Supply Chain Management"
            'D365_IOTFORSCM_ADDITIONAL'                                             = "IoT Intelligence Add-in Additional Machines"
            'D365_MARKETING_USER'                                                   = "Dynamics 365 for Marketing USL"
            'D365_ProjectOperations'                                                = "Dynamics 365 Project Operations"
            'D365_ProjectOperationsCDS'                                             = "Dynamics 365 Project Operations CDS"
            'D365_SALES_ENT_ATTACH'                                                 = "Dynamics 365 Sales Enterprise Attach to Qualifying Dynamics 365 Base Offer"
            'D365_SALES_PRO'                                                        = "Dynamics 365 For Sales Professional"
            'D365_SALES_PRO_ATTACH'                                                 = "Dynamics 365 Sales Professional Attach to Qualifying Dynamics 365 Base Offer"
            'D365_SALES_PRO_IW'                                                     = "Dynamics 365 For Sales Professional Trial"
            'D365_SALES_PRO_IW_Trial'                                               = "Dynamics 365 for Sales Professional Trial"
            'D365_SCM'                                                              = "DYNAMICS 365 FOR SUPPLY CHAIN MANAGEMENT"
            'DATA_INVESTIGATIONS'                                                   = "Microsoft Data Investigations"
            'DATAVERSE_FOR_POWERAUTOMATE_DESKTOP'                                   = "Dataverse for PAD"
            'DATAVERSE_POWERAPPS_PER_APP_NEW'                                       = "Dataverse for Power Apps per app"
            'DDYN365_CDS_DYN_P2'                                                    = "COMMON DATA SERVICE"
            'DEFENDER_ENDPOINT_P1'                                                  = "Microsoft Defender for Endpoint P1"
            'DEFENDER_ENDPOINT_P1_EDU'                                              = "Microsoft Defender for Endpoint P1 for EDU"
            'Defender_Threat_Intelligence'                                          = "Defender Threat Intelligence"
            'Deskless'                                                              = "Microsoft StaffHub"
            'DESKLESSPACK'                                                          = "Office 365 F3"
            'DEVELOPERPACK'                                                         = "Office 365 E3 Developer"
            'DEVELOPERPACK_E5'                                                      = "Microsoft 365 E5 Developer (without Windows and Audio Conferencing)"
            'DYN365_ ENTERPRISE _RELATIONSHIP_SALES'                                = "Microsoft Relationship Sales solution"
            'DYN365_AI_SERVICE_INSIGHTS'                                            = "Dynamics 365 Customer Service Insights Trial"
            'DYN365_ASSETMANAGEMENT'                                                = "Dynamics 365 Asset Management Addl Assets"
            'DYN365_BUSCENTRAL_ADD_ENV_ADDON'                                       = "Dynamics 365 Business Central Additional Environment Addon"
            'DYN365_BUSCENTRAL_DB_CAPACITY'                                         = "Dynamics 365 Business Central Database Capacity"
            'DYN365_BUSCENTRAL_ENVIRONMENT'                                         = "Dynamics 365 Business Central Additional Environment Addon"
            'DYN365_BUSCENTRAL_ESSENTIAL'                                           = "Dynamics 365 Business Central Essentials"
            'DYN365_BUSCENTRAL_PREMIUM'                                             = "Dynamics 365 Business Central Premium"
            'DYN365_BUSCENTRAL_TEAM_MEMBER'                                         = "Dynamics 365 Business Central Team Members"
            'DYN365_BUSINESS_MARKETING'                                             = "Dynamics 365 for Marketing Business Edition"
            'DYN365_CDS_CCI_BOTS'                                                   = "Common Data Service for CCI Bots"
            'DYN365_CDS_DEV_VIRAL'                                                  = "Common Data Service - DEV VIRAL"
            'DYN365_CDS_DYN_APPS'                                                   = "Common Data Service"
            'DYN365_CDS_FINANCE'                                                    = "Common Data Service for Dynamics 365 Finance"
            'DYN365_CDS_FOR_PROJECT_P1'                                             = "COMMON DATA SERVICE FOR PROJECT P1"
            'DYN365_CDS_FORMS_PRO'                                                  = "Common Data Service"
            'DYN365_CDS_GUIDES'                                                     = "Common Data Service"
            'DYN365_CDS_O365_F1'                                                    = "Common Data Service"
            'DYN365_CDS_O365_F1_GCC'                                                = "Common Data Service - O365 F1"
            'DYN365_CDS_O365_P1'                                                    = "Common Data Service - O365 P1"
            'DYN365_CDS_O365_P1_GCC'                                                = "Common Data Service - O365 P1 GCC"
            'DYN365_CDS_O365_P2'                                                    = "Common Data Service"
            'DYN365_CDS_O365_P2_GCC'                                                = "COMMON DATA SERVICE - O365 P2 GCC"
            'DYN365_CDS_O365_P3'                                                    = "Common Data Service"
            'DYN365_CDS_O365_P3_GCC'                                                = "Common Data Service"
            'DYN365_CDS_P1_GOV'                                                     = "Common Data Service for Government"
            'DYN365_CDS_P2'                                                         = "Common Data Service - P2"
            'DYN365_CDS_P2_GOV'                                                     = "Common Data Service for Government"
            'DYN365_CDS_PROJECT'                                                    = "Common Data Service for Project"
            'DYN365_CDS_SUPPLYCHAINMANAGEMENT'                                      = "COMMON DATA SERVICE FOR DYNAMICS 365 SUPPLY CHAIN MANAGEMENT"
            'DYN365_CDS_VIRAL'                                                      = "Common Data Service"
            'DYN365_CS_CHAT'                                                        = "Dynamics 365 for Customer Service Chat"
            'DYN365_CS_CHAT_FPA'                                                    = "Dynamics 365 Customer Service Chat Application Integration"
            'DYN365_CS_ENTERPRISE_VIRAL_TRIAL'                                      = "Dynamics 365 Customer Service Enterprise vTrial"
            'DYN365_CS_MESSAGING_TPS'                                               = "Dynamics 365 Customer Service Digital Messaging add-on"
            'DYN365_CS_MESSAGING_VIRAL_TRIAL'                                       = "Dynamics 365 Customer Service Digital Messaging vTrial"
            'DYN365_CS_VOICE'                                                       = "Dynamics 365 for Customer Service Voice Add-in"
            'DYN365_CS_VOICE_VIRAL_TRIAL'                                           = "Dynamics 365 Customer Service Voice vTrial"
            'DYN365_CUSTOMER_INSIGHTS_ATTACH'                                       = "Dynamics 365 Customer Insights Attach"
            'DYN365_CUSTOMER_INSIGHTS_BASE'                                         = "Dynamics 365 Customer Insights Standalone"
            'DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE'                     = "Dynamics 365 Customer Insights Engagement Insights"
            'DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE_TRIAL'               = "Dynamics 365 Customer Insights Engagement Insights Viral"
            'DYN365_CUSTOMER_INSIGHTS_VIRAL'                                        = "Dynamics 365 Customer Insights vTrial"
            'DYN365_CUSTOMER_SERVICE_PRO'                                           = "Dynamics 365 Customer Service Professional"
            'DYN365_CUSTOMER_VOICE_ADDON'                                           = "Dynamics 365 Customer Voice Additional Responses"
            'DYN365_CUSTOMER_VOICE_BASE'                                            = "Dynamics 365 Customer Voice"
            'DYN365_ENTERPRISE_CASE_MANAGEMENT'                                     = "Dynamics 365 for Case Management Enterprise Edition"
            'DYN365_ENTERPRISE_CUSTOMER_SERVICE'                                    = "Dynamics 365 for Customer Service Enterprise Edition"
            'DYN365_ENTERPRISE_FIELD_SERVICE'                                       = "Dynamics 365 for Field Service Enterprise Edition"
            'DYN365_ENTERPRISE_P1'                                                  = "Dynamics 365 P1"
            'DYN365_ENTERPRISE_P1_IW'                                               = "Dynamics 365 P1 Tria for Information Workers"
            'DYN365_ENTERPRISE_PLAN1'                                               = "Dynamics 365 Customer Engagement Plan"
            'DYN365_ENTERPRISE_SALES'                                               = "Dynamics 365 for Sales Enterprise Edition"
            'DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE'                               = "Dynamics 365 for Sales and Customer Service Enterprise Edition"
            'DYN365_Enterprise_Talent_Attract_TeamMember'                           = "DYNAMICS 365 FOR TALENT - ATTRACT EXPERIENCE TEAM MEMBER"
            'DYN365_Enterprise_Talent_Onboard_TeamMember'                           = "DYNAMICS 365 FOR TALENT - ONBOARD EXPERIENCE"
            'DYN365_ENTERPRISE_TEAM_MEMBERS'                                        = "Dynamics 365 for Team Members Enterprise Edition"
            'DYN365_FINANCE'                                                        = "Dynamics 365 Finance"
            'DYN365_FINANCIALS_ACCOUNTANT'                                          = "Dynamics 365 Business Central External Accountant"
            'DYN365_FINANCIALS_ACCOUNTANT_SKU'                                      = "Dynamics 365 Business Central External Accountant"
            'DYN365_FINANCIALS_BUSINESS'                                            = "Dynamics 365 for Business Central Essentials"
            'DYN365_FINANCIALS_BUSINESS_SKU'                                        = "Dynamics 365 for Financials Business Edition"
            'DYN365_FINANCIALS_TEAM_MEMBERS'                                        = "Dynamics 365 for Team Members"
            'DYN365_FS_ENTERPRISE_VIRAL_TRIAL'                                      = "Dynamics 365 Field Service Enterprise vTrial"
            'DYN365_IOT_INTELLIGENCE_ADDL_MACHINES'                                 = "Sensor Data Intelligence Additional Machines Add-in for Dynamics 365 Supply Chain Management"
            'DYN365_IOT_INTELLIGENCE_SCENARIO'                                      = "Sensor Data Intelligence Scenario Add-in for Dynamics 365 Supply Chain Management"
            'DYN365_MARKETING_50K_CONTACT_ADDON'                                    = "Dynamics 365 for Marketing 50K Addnl Contacts"
            'DYN365_MARKETING_APP'                                                  = "Dynamics 365 for Marketing"
            'DYN365_MARKETING_APP_ATTACH'                                           = "Dynamics 365 for Marketing Attach"
            'DYN365_MARKETING_APPLICATION_ADDON'                                    = "Dynamics 365 for Marketing Additional Application"
            'DYN365_MARKETING_CONTACT_ADDON_T3'                                     = "Dynamics 365 for Marketing Addnl Contacts Tier 3"
            'DYN365_MARKETING_CONTACT_ADDON_T5'                                     = "Dynamics 365 for Marketing Addnl Contacts Tier 5"
            'DYN365_MARKETING_MSE_USER'                                             = "Dynamics 365 for Marketing MSE User"
            'DYN365_MARKETING_SANDBOX_APPLICATION_ADDON'                            = "Dynamics 365 for Marketing Additional Non-Prod Application"
            'DYN365_MARKETING_USER'                                                 = "Dynamics 365 for Marketing USL"
            'DYN365_REGULATORY_SERVICE'                                             = "Dynamics 365 Regulatory Service - Enterprise Edition Trial"
            'DYN365_RETAIL_DEVICE'                                                  = "Dynamics 365 for Retail Device"
            'DYN365_RETAIL_TRIAL'                                                   = "Dynamics 365 Commerce Trial"
            'DYN365_SALES_ENTERPRISE_VIRAL_TRIAL'                                   = "Dynamics 365 Sales Enterprise vTrial"
            'DYN365_SALES_INSIGHTS'                                                 = "Dynamics 365 AI for Sales (Embedded)"
            'DYN365_SALES_INSIGHTS_VIRAL_TRIAL'                                     = "Dynamics 365 Sales Insights vTrial"
            'DYN365_SALES_PREMIUM'                                                  = "Dynamics 365 Sales Premium"
            'DYN365_SALES_PRO'                                                      = "Dynamics 365 for Sales Professional"
            'DYN365_SCM'                                                            = "Dynamics 365 for Supply Chain Management"
            'DYN365_TALENT_ENTERPRISE'                                              = "DYNAMICS 365 FOR TALENT"
            'DYN365_TEAM_MEMBERS'                                                   = "Dynamics 365 Team Members"
            'DYN365BC_MS_INVOICING'                                                 = "Microsoft Invoicing"
            'Dynamics_365_Customer_Service_Enterprise_admin_trial'                  = "Dynamics 365 Customer Service Enterprise Admin"
            'Dynamics_365_Customer_Service_Enterprise_viral_trial'                  = "Dynamics 365 Customer Service Enterprise Viral Trial"
            'Dynamics_365_Field_Service_Enterprise_viral_trial'                     = "Dynamics 365 Field Service Viral Trial"
            'Dynamics_365_for_HCM_Trial'                                            = "Dynamics 365 for HCM Trial"
            'Dynamics_365_for_Operations'                                           = "Dynamics 365 UNF OPS Plan ENT Edition"
            'Dynamics_365_for_Operations_Devices'                                   = "Dynamics 365 Operations - Device"
            'Dynamics_365_for_Operations_Sandbox_Tier2'                             = "Dynamics 365 for Operations non-production multi-box instance for standard acceptance testing (Tier 2)"
            'Dynamics_365_for_Operations_Sandbox_Tier2_SKU'                         = "Dynamics 365 Operations - Sandbox Tier 2:Standard Acceptance Testing"
            'Dynamics_365_for_Operations_Sandbox_Tier4'                             = "Dynamics 365 for Operations Enterprise Edition - Sandbox Tier 4:Standard Performance Testing"
            'Dynamics_365_for_Operations_Sandbox_Tier4_SKU'                         = "Dynamics 365 Operations - Sandbox Tier 4:Standard Performance Testing"
            'DYNAMICS_365_FOR_OPERATIONS_TEAM_MEMBERS'                              = "DYNAMICS 365 FOR OPERATIONS TEAM MEMBERS"
            'Dynamics_365_for_OperationsDevices'                                    = "Dynamics 365 for Operations Devices"
            'Dynamics_365_for_Retail'                                               = "DYNAMICS 365 FOR RETAIL"
            'Dynamics_365_for_Retail_Team_members'                                  = "DYNAMICS 365 FOR RETAIL TEAM MEMBERS"
            'Dynamics_365_for_Talent_Team_members'                                  = "DYNAMICS 365 FOR TALENT TEAM MEMBERS"
            'Dynamics_365_Hiring_Free_PLAN'                                         = "Dynamics 365 for Talent: Attract"
            'Dynamics_365_Hiring_SKU'                                               = "Dynamics 365 Talent: Attract"
            'Dynamics_365_Onboarding_Free_PLAN'                                     = "Dynamics 365 for Talent: Onboard"
            'DYNAMICS_365_ONBOARDING_SKU'                                           = "Dynamics 365 Talent: Onboard"
            'Dynamics_365_Sales_Field_Service_and_Customer_Service_Partner_Sandbox' = "Dynamics 365 Sales, Field Service and Customer Service Partner Sandbox"
            'Dynamics_365_Sales_Premium_Viral_Trial'                                = "Dynamics 365 Sales Premium Viral Trial"
            'Dynamics_365_Talent_Onboard'                                           = "DYNAMICS 365 FOR TALENT: ONBOARD"
            'DYNB365_CSI_VIRAL_TRIAL'                                               = "Dynamics 365 Customer Service Insights vTrial"
            'E3_VDA_only'                                                           = "Windows 10/11 Enterprise E3 VDA"
            'EducationAnalyticsP1'                                                  = "Education Analytics"
            'EMS'                                                                   = "Enterprise Mobility + Security E3"
            'EMS_EDU_FACULTY'                                                       = "Enterprise Mobility + Security A3 for Faculty"
            'EMS_GOV'                                                               = "Enterprise Mobility + Security G3 GCC"
            'EMSPREMIUM'                                                            = "Enterprise Mobility + Security E5"
            'EMSPREMIUM_GOV'                                                        = "Enterprise Mobility + Security G5 GCC"
            'ENTERPRISEPACK'                                                        = "Office 365 E3"
            'ENTERPRISEPACK_GOV'                                                    = "Office 365 G3 GCC"
            'ENTERPRISEPACK_USGOV_DOD'                                              = "Office 365 E3_USGOV_DOD"
            'ENTERPRISEPACK_USGOV_GCCHIGH'                                          = "Office 365 E3_USGOV_GCCHIGH"
            'ENTERPRISEPACKPLUS_FACULTY'                                            = "Office 365 A3 for faculty"
            'ENTERPRISEPACKPLUS_STUDENT'                                            = "Office 365 A3 for students"
            'ENTERPRISEPREMIUM'                                                     = "Office 365 E5"
            'ENTERPRISEPREMIUM_FACULTY'                                             = "Office 365 A5 for faculty"
            'ENTERPRISEPREMIUM_GOV'                                                 = "Office 365 G5 GCC"
            'ENTERPRISEPREMIUM_NOPSTNCONF'                                          = "Office 365 E5 Without Audio Conferencing"
            'ENTERPRISEPREMIUM_STUDENT'                                             = "Office 365 A5 for students"
            'ENTERPRISEWITHSCAL'                                                    = "Office 365 E4"
            'EOP_ENTERPRISE'                                                        = "Exchange Online Protection"
            'EOP_ENTERPRISE_PREMIUM'                                                = "Exchange Enterprise CAL Services (EOP DLP)"
            'EQUIVIO_ANALYTICS'                                                     = "Office 365 Advanced Compliance"
            'EQUIVIO_ANALYTICS_GOV'                                                 = "Office 365 Advanced Compliance for GCC"
            'ERP_TRIAL_INSTANCE'                                                    = "Dynamics 365 Operations Trial Environment"
            'EXCEL_PREMIUM'                                                         = "Microsoft Excel Advanced Analytics"
            'EXCHANGE_ANALYTICS'                                                    = "Microsoft MyAnalytics (Full)"
            'EXCHANGE_ANALYTICS_GOV'                                                = "Microsoft MyAnalytics for Government (Full)"
            'EXCHANGE_B_STANDARD'                                                   = "EXCHANGE ONLINE POP"
            'EXCHANGE_FOUNDATION_GOV'                                               = "EXCHANGE FOUNDATION FOR GOVERNMENT"
            'EXCHANGE_L_STANDARD'                                                   = "EXCHANGE ONLINE (P1)"
            'EXCHANGE_S_ARCHIVE'                                                    = "EXCHANGE ONLINE ARCHIVING FOR EXCHANGE SERVER"
            'EXCHANGE_S_ARCHIVE_ADDON'                                              = "EXCHANGE ONLINE ARCHIVING FOR EXCHANGE ONLINE"
            'EXCHANGE_S_DESKLESS'                                                   = "EXCHANGE ONLINE KIOSK"
            'EXCHANGE_S_DESKLESS_GOV'                                               = "Exchange Online (Kiosk) for Government"
            'EXCHANGE_S_ENTERPRISE'                                                 = "EXCHANGE ONLINE (PLAN 2)"
            'EXCHANGE_S_ENTERPRISE_GOV'                                             = "Exchange Online (Plan 2) for Government"
            'EXCHANGE_S_ESSENTIALS'                                                 = "Exchange Online Essentials"
            'EXCHANGE_S_FOUNDATION'                                                 = "Exchange Foundation"
            'EXCHANGE_S_FOUNDATION_GOV'                                             = "Exchange Foundation for Government"
            'EXCHANGE_S_STANDARD'                                                   = "Exchange Online (Plan 1)"
            'EXCHANGE_S_STANDARD_GOV'                                               = "Exchange Online (Plan 1) for Government"
            'EXCHANGE_S_STANDARD_MIDMARKET'                                         = "EXCHANGE ONLINE PLAN"
            'EXCHANGEARCHIVE'                                                       = "Exchange Online Archiving for Exchange Server"
            'EXCHANGEARCHIVE_ADDON'                                                 = "Exchange Online Archiving for Exchange Online"
            'EXCHANGEDESKLESS'                                                      = "Exchange Online Kiosk"
            'EXCHANGEENTERPRISE'                                                    = "Exchange Online (Plan 2)"
            'EXCHANGEENTERPRISE_FACULTY'                                            = "Exchange Online (Plan 2) for Faculty"
            'EXCHANGEESSENTIALS'                                                    = "Exchange Online Essentials (ExO P1 Based)"
            'EXCHANGEONLINE_MULTIGEO'                                               = "Exchange Online Multi-Geo"
            'EXCHANGESTANDARD'                                                      = "Exchange Online (Plan 1)"
            'EXCHANGESTANDARD_ALUMNI'                                               = "Exchange Online (Plan 1) for Alumni with Yammer"
            'EXCHANGESTANDARD_GOV'                                                  = "Exchange Online (Plan 1) for GCC"
            'EXCHANGESTANDARD_STUDENT'                                              = "Exchange Online (Plan 1) for Students"
            'EXCHANGETELCO'                                                         = "Exchange Online POP"
            'EXPERTS_ON_DEMAND'                                                     = "Microsoft Threat Experts - Experts on Demand"
            'FLOW_BUSINESS_PROCESS'                                                 = "Power Automate per flow plan"
            'FLOW_CCI_BOTS'                                                         = "Flow for CCI Bots"
            'FLOW_CUSTOMER_SERVICE_PRO'                                             = "Power Automate for Customer Service Pro"
            'FLOW_DEV_VIRAL'                                                        = "Flow for Developer"
            'FLOW_DYN_APPS'                                                         = "Flow for Dynamics 365"
            'FLOW_DYN_P2'                                                           = "Flow for Dynamics 365"
            'FLOW_DYN_TEAM'                                                         = "Power Automate for Dynamics 365"
            'FLOW_FOR_PROJECT'                                                      = "Flow for Project"
            'FLOW_FORMS_PRO'                                                        = "Power Automate for Dynamics 365 Customer Voice"
            'FLOW_FREE'                                                             = "Microsoft Power Automate Free"
            'FLOW_O365_P1'                                                          = "FLOW FOR OFFICE 365"
            'FLOW_O365_P1_GOV'                                                      = "Power Automate for Office 365 for Government"
            'FLOW_O365_P2'                                                          = "Power Automate for Office 365"
            'FLOW_O365_P2_GOV'                                                      = "POWER AUTOMATE FOR OFFICE 365 FOR GOVERNMENT"
            'FLOW_O365_P3'                                                          = "Power Automate for Office 365"
            'FLOW_O365_P3_GOV'                                                      = "Power Automate for Office 365 for Government"
            'FLOW_O365_S1'                                                          = "Power Automate for Office 365 F3"
            'FLOW_O365_S1_GOV'                                                      = "Power Automate for Office 365 F3 for Government"
            'FLOW_P1_GOV'                                                           = "Power Automate (Plan 1) for Government"
            'FLOW_P2'                                                               = "Microsoft Power Automate Plan 2"
            'FLOW_P2_VIRAL'                                                         = "Flow Free"
            'FLOW_P2_VIRAL_REAL'                                                    = "Flow P2 Viral"
            'Flow_Per_APP'                                                          = "Power Automate for Power Apps per App Plan"
            'Flow_Per_APP_IWTRIAL'                                                  = "Flow per app baseline access"
            'FLOW_PER_USER'                                                         = "Power Automate per user plan"
            'FLOW_PER_USER_DEPT'                                                    = "Power Automate per user plan dept"
            'FLOW_PER_USER_GCC'                                                     = "Power Automate per user plan for Government"
            'Flow_PowerApps_PerUser'                                                = "Power Automate for Power Apps per User Plan"
            'Flow_PowerApps_PerUser_GCC'                                            = "Power Automate for Power Apps per User Plan for GCC"
            'FLOW_VIRTUAL_AGENT_BASE'                                               = "Power Automate for Virtual Agent"
            'FLOW_VIRTUAL_AGENT_USL'                                                = "Power Automate for Virtual Agent"
            'FORMS_GOV_E1'                                                          = "Forms for Government (Plan E1)"
            'FORMS_GOV_E3'                                                          = "FORMS FOR GOVERNMENT (PLAN E3)"
            'FORMS_GOV_E5'                                                          = "Microsoft Forms for Government (Plan E5)"
            'FORMS_GOV_F1'                                                          = "Forms for Government (Plan F1)"
            'FORMS_PLAN_E1'                                                         = "MICROSOFT FORMS (PLAN E1)"
            'FORMS_PLAN_E3'                                                         = "Microsoft Forms (Plan E3)"
            'FORMS_PLAN_E5'                                                         = "Microsoft Forms (Plan E5)"
            'FORMS_PLAN_K'                                                          = "Microsoft Forms (Plan F1)"
            'FORMS_PRO'                                                             = "Dynamics 365 Customer Voice Trial"
            'Forms_Pro_AddOn'                                                       = "Dynamics 365 Customer Voice Additional Responses"
            'Forms_Pro_CE'                                                          = "Microsoft Dynamics 365 Customer Voice for Customer Engagement Plan"
            'Forms_Pro_Customer_Insights'                                           = "Microsoft Dynamics 365 Customer Voice for Customer Insights"
            'Forms_Pro_FS'                                                          = "Microsoft Dynamics 365 Customer Voice for Field Service"
            'Forms_Pro_Marketing'                                                   = "Microsoft Dynamics 365 Customer Voice for Marketing"
            'Forms_Pro_Marketing_App'                                               = "Microsoft Dynamics 365 Customer Voice for Marketing Application"
            'Forms_Pro_Relationship_Sales'                                          = "Microsoft Dynamics 365 Customer Voice for Relationship Sales"
            'Forms_Pro_SalesEnt'                                                    = "Microsoft Dynamics 365 Customer Voice for Sales Enterprise"
            'Forms_Pro_Service'                                                     = "Microsoft Dynamics 365 Customer Voice for Customer Service Enterprise"
            'Forms_Pro_USL'                                                         = "Dynamics 365 Customer Voice USL"
            'GRAPH_CONNECTORS_SEARCH_INDEX'                                         = "Graph Connectors Search with Index"
            'GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP'                                = "Graph Connectors Search with Index (Microsoft Viva Topics)"
            'GUIDES'                                                                = "Dynamics 365 Guides"
            'GUIDES_USER'                                                           = "Dynamics 365 Guides"
            'IDENTITY_THREAT_PROTECTION'                                            = "Microsoft 365 E5 Security"
            'IDENTITY_THREAT_PROTECTION_FOR_EMS_E5'                                 = "Microsoft 365 E5 Security for EMS E5"
            'INFO_GOVERNANCE'                                                       = "Microsoft Information Governance"
            'INFORMATION_BARRIERS'                                                  = "Information Barriers"
            'INFORMATION_PROTECTION_COMPLIANCE'                                     = "Microsoft 365 E5 Compliance"
            'INSIDER_RISK'                                                          = "Microsoft Insider Risk Management"
            'INSIDER_RISK_MANAGEMENT'                                               = "Microsoft Insider Risk Management"
            'Intelligent_Content_Services'                                          = "SharePoint Syntex"
            'Intelligent_Content_Services_SPO_type'                                 = "SharePoint Syntex - SPO type"
            'INTUNE_A'                                                              = "Intune"
            'INTUNE_A_D'                                                            = "Microsoft Intune Device"
            'INTUNE_A_D_GOV'                                                        = "Microsoft Intune Device for Government"
            'Intune_AdvancedEA'                                                     = "Intune Advanced endpoint analytics"
            'Intune_Defender'                                                       = "MDE_SecurityManagement"
            'INTUNE_EDU'                                                            = "Intune for Education"
            'INTUNE_O365'                                                           = "Mobile Device Management for Office 365"
            'INTUNE_P2'                                                             = "Intune Plan 2"
            'INTUNE_SMB'                                                            = "Microsoft Intune SMB"
            'INTUNE_SMBIZ'                                                          = "Microsoft Intune"
            'Intune-EPM'                                                            = "Intune Endpoint Privilege Management"
            'Intune-MAMTunnel'                                                      = "Microsoft Tunnel for Mobile Application Management"
            'IT_ACADEMY_AD'                                                         = "Microsoft Imagine Academy"
            'KAIZALA_O365_P1'                                                       = "Microsoft Kaizala Pro"
            'KAIZALA_O365_P2'                                                       = "Microsoft Kaizala Pro"
            'KAIZALA_O365_P3'                                                       = "Microsoft Kaizala Pro"
            'KAIZALA_STANDALONE'                                                    = "Microsoft Kaizala Pro"
            'LITEPACK'                                                              = "Office 365 Small Business"
            'LITEPACK_P2'                                                           = "Office 365 Small Business Premium"
            'LOCKBOX_ENTERPRISE'                                                    = "Customer Lockbox"
            'LOCKBOX_ENTERPRISE_GOV'                                                = "Customer Lockbox for Government"
            'M365_A5_SUITE_COMPONENTS_FACULTY'                                      = "Microsoft 365 A5 Suite features for faculty"
            'M365_ADVANCED_AUDITING'                                                = "Microsoft 365 Advanced Auditing"
            'M365_AUDIT_PLATFORM'                                                   = "Microsoft 365 Audit Platform"
            'M365_E5_SUITE_COMPONENTS'                                              = "Microsoft 365 E5 Suite features"
            'M365_F1'                                                               = "Microsoft 365 F1"
            'M365_F1_COMM'                                                          = "Microsoft 365 F1"
            'M365_F1_GOV'                                                           = "Microsoft 365 F3 GCC"
            'M365_G3_GOV'                                                           = "Microsoft 365 G3 GCC"
            'M365_G5_GCC'                                                           = "Microsoft 365 GCC G5"
            'M365_LIGHTHOUSE_CUSTOMER_PLAN1'                                        = "Microsoft 365 Lighthouse (Plan 1)"
            'M365_LIGHTHOUSE_PARTNER_PLAN1'                                         = "Microsoft 365 Lighthouse (Plan 2)"
            'M365_SECURITY_COMPLIANCE_FOR_FLW'                                      = "Microsoft 365 Security and Compliance for Firstline Workers"
            'M365EDU_A1'                                                            = "Microsoft 365 A1"
            'M365EDU_A3_FACULTY'                                                    = "Microsoft 365 A3 for Faculty"
            'M365EDU_A3_STUDENT'                                                    = "Microsoft 365 A3 for Students"
            'M365EDU_A3_STUUSEBNFT'                                                 = "Microsoft 365 A3 for students use benefit"
            'M365EDU_A3_STUUSEBNFT_RPA1'                                            = "Microsoft 365 A3 - Unattended License for students use benefit"
            'M365EDU_A5_FACULTY'                                                    = "Microsoft 365 A5 for Faculty"
            'M365EDU_A5_NOPSTNCONF_STUUSEBNFT'                                      = "Microsoft 365 A5 without Audio Conferencing for students use benefit"
            'M365EDU_A5_STUDENT'                                                    = "Microsoft 365 A5 for Students"
            'M365EDU_A5_STUUSEBNFT'                                                 = "Microsoft 365 A5 for students use benefit"
            'MCO_TEAMS_IW'                                                          = "MICROSOFT TEAMS"
            'MCO_VIRTUAL_APPT'                                                      = "Microsoft Teams Premium Virtual Appointments"
            'MCOCAP'                                                                = "Microsoft Teams Shared Devices"
            'MCOCAP_GOV'                                                            = "Microsoft Teams Shared Devices for GCC"
            'MCOEV'                                                                 = "Microsoft Teams Phone Standard"
            'MCOEV_DOD'                                                             = "Microsoft Teams Phone Standard for DOD"
            'MCOEV_FACULTY'                                                         = "Microsoft Teams Phone Standard for Faculty"
            'MCOEV_GCCHIGH'                                                         = "Microsoft Teams Phone Standard for GCCHIGH"
            'MCOEV_GOV'                                                             = "Microsoft Teams Phone Standard for GCC"
            'MCOEV_STUDENT'                                                         = "Microsoft Teams Phone Standard for Students"
            'MCOEV_TELSTRA'                                                         = "Microsoft Teams Phone Standard for TELSTRA"
            'MCOEV_USGOV_DOD'                                                       = "Microsoft Teams Phone Standard_USGOV_DOD"
            'MCOEV_USGOV_GCCHIGH'                                                   = "Microsoft Teams Phone Standard_USGOV_GCCHIGH"
            'MCOEV_VIRTUALUSER'                                                     = "Microsoft 365 Phone Standard Resource Account"
            'MCOEV_VIRTUALUSER_GOV'                                                 = "Microsoft 365 Phone Standard Resource Account for Government"
            'MCOEVSMB'                                                              = "SKYPE FOR BUSINESS CLOUD PBX FOR SMALL AND MEDIUM BUSINESS"
            'MCOEVSMB_1'                                                            = "Microsoft Teams Phone Standard for Small and Medium Business"
            'MCOFREE'                                                               = "MCO FREE FOR MICROSOFT TEAMS (FREE)"
            'MCOIMP'                                                                = "Skype for Business Online (Plan 1)"
            'MCOIMP_GOV'                                                            = "Skype for Business Online (Plan 1) for Government"
            'MCOLITE'                                                               = "SKYPE FOR BUSINESS ONLINE (PLAN P1)"
            'MCOMEETACPEA'                                                          = "Microsoft 365 Audio Conferencing Pay-Per-Minute - EA"
            'MCOMEETADV'                                                            = "Microsoft 365 Audio Conferencing"
            'MCOMEETADV_GOV'                                                        = "Microsoft 365 Audio Conferencing for GCC"
            'MCOMEETBASIC'                                                          = "Microsoft Teams Audio Conferencing with dial-out to select geographies"
            'MCOPSTN_1_GOV'                                                         = "Microsoft 365 Domestic Calling Plan for GCC"
            'MCOPSTN_5'                                                             = "Microsoft 365 Domestic Calling Plan (120 Minutes)"
            'MCOPSTN1'                                                              = "Skype for Business PSTN Domestic Calling"
            'MCOPSTN1_GOV'                                                          = "Domestic Calling for Government"
            'MCOPSTN2'                                                              = "Skype for Business PSTN Domestic and International Calling"
            'MCOPSTN3'                                                              = "MCOPSTN3"
            'MCOPSTN5'                                                              = "Skype for Business PSTN Domestic Calling (120 Minutes)"
            'MCOPSTN8'                                                              = "Microsoft 365 Domestic Calling Plan (120 min) at User Level"
            'MCOPSTNC'                                                              = "Communications Credits"
            'MCOPSTNEAU'                                                            = "AUSTRALIA CALLING PLAN"
            'MCOPSTNEAU2'                                                           = "TELSTRA Calling for O365"
            'MCOPSTNPP'                                                             = "Skype for Business PSTN Usage Calling Plan"
            'MCOSTANDARD'                                                           = "Skype for Business Online (Plan 2)"
            'MCOSTANDARD_GOV'                                                       = "Skype for Business Online (Plan 2) for Government"
            'MCOSTANDARD_MIDMARKET'                                                 = "SKYPE FOR BUSINESS ONLINE (PLAN 2) FOR MIDSIZ"
            'MCOTEAMS_ESSENTIALS'                                                   = "Teams Phone with Calling Plan"
            'MCOVOICECONF'                                                          = "SKYPE FOR BUSINESS ONLINE (PLAN 3)"
            'MCS_BizApps_Cloud_for_Sustainability_vTrial'                           = "MCS - BizApps_Cloud for Sustainability_vTrial"
            'MDATP_Server'                                                          = "Microsoft Defender for Endpoint Server"
            'MDATP_XPLAT'                                                           = "Microsoft Defender for Endpoint P2_XPLAT"
            'MDE_LITE'                                                              = "Microsoft Defender for Endpoint Plan 1"
            'MDE_SMB'                                                               = "Microsoft Defender for Business"
            'MDM_SALES_COLLABORATION'                                               = "MICROSOFT DYNAMICS MARKETING SALES COLLABORATION - ELIGIBILITY CRITERIA APPLY"
            'MEE_FACULTY'                                                           = "Minecraft Education Faculty"
            'MEE_STUDENT'                                                           = "Minecraft Education Student"
            'MEETING_ROOM'                                                          = "Microsoft Teams Rooms Standard"
            'MEETING_ROOM_NOAUDIOCONF'                                              = "Microsoft Teams Rooms Standard without Audio Conferencing"
            'MFA_PREMIUM'                                                           = "MICROSOFT AZURE MULTI-FACTOR AUTHENTICATION"
            'MFA_STANDALONE'                                                        = "Microsoft Azure Multi-Factor Authentication"
            'Microsoft 365 A3 Suite features for faculty'                           = "Microsoft 365 A3 Suite features for faculty"
            'Microsoft_365_E3'                                                      = "Microsoft 365 E3 (500 seats min)_HUB"
            'Microsoft_365_E3_Extra_Features'                                       = "Microsoft 365 E3 Extra Features"
            'Microsoft_365_E5'                                                      = "Microsoft 365 E5 (500 seats min)_HUB"
            'Microsoft_365_E5_without_Audio_Conferencing'                           = "Microsoft 365 E5 without Audio Conferencing (500 seats min)_HUB"
            'MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_A'                     = "Microsoft Application Protection and Governance (A)"
            'MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_D'                     = "Microsoft Application Protection and Governance (D)"
            'MICROSOFT_BUSINESS_CENTER'                                             = "Microsoft Business Center"
            'Microsoft_Cloud_App_Security_App_Governance_Add_On'                    = "App governance add-on to Microsoft Defender for Cloud Apps"
            'Microsoft_Cloud_for_Sustainability_vTrial'                             = "Microsoft Cloud for Sustainability vTrial"
            'MICROSOFT_COMMUNICATION_COMPLIANCE'                                    = "Microsoft 365 Communication Compliance"
            'MICROSOFT_ECDN'                                                        = "Microsoft eCDN"
            'Microsoft_Intune_Suite'                                                = "Microsoft Intune Suite"
            'MICROSOFT_REMOTE_ASSIST'                                               = "Dynamics 365 Remote Assist"
            'MICROSOFT_REMOTE_ASSIST_HOLOLENS'                                      = "Dynamics 365 Remote Assist HoloLens"
            'MICROSOFT_SEARCH'                                                      = "Microsoft Search"
            'Microsoft_Teams_Audio_Conferencing_select_dial_out'                    = "Microsoft Teams Audio Conferencing with dial-out to USA/CAN"
            'Microsoft_Teams_Premium'                                               = "Microsoft Teams Premium Introductory Pricing"
            'Microsoft_Teams_Rooms_Basic'                                           = "Microsoft Teams Rooms Basic"
            'Microsoft_Teams_Rooms_Basic_FAC'                                       = "Microsoft Teams Rooms Basic for EDU"
            'Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing'                = "Microsoft Teams Rooms Basic without Audio Conferencing"
            'Microsoft_Teams_Rooms_Pro'                                             = "Microsoft Teams Rooms Pro"
            'Microsoft_Teams_Rooms_Pro_FAC'                                         = "Microsoft Teams Rooms Pro for EDU"
            'Microsoft_Teams_Rooms_Pro_without_Audio_Conferencing'                  = "Microsoft Teams Rooms Pro without Audio Conferencing"
            'Microsoft_Viva_Goals'                                                  = "Microsoft Viva Goals"
            'Microsoft_Viva_Sales_PowerAutomate'                                    = "Microsoft Viva Sales Premium with Power Automate"
            'Microsoft_Viva_Sales_PremiumTrial'                                     = "Microsoft Viva Sales Premium & Trial"
            'Microsoft365_Lighthouse'                                               = "Microsoft 365 Lighthouse"
            'MICROSOFTBOOKINGS'                                                     = "Microsoft Bookings"
            'MICROSOFTENDPOINTDLP'                                                  = "Microsoft Endpoint DLP"
            'MICROSOFTSTREAM'                                                       = "MICROSOFT STREAM"
            'MIDSIZEPACK'                                                           = "Office 365 Midsize Business"
            'MINECRAFT_EDUCATION_EDITION'                                           = "Minecraft Education Edition"
            'MIP_S_CLP1'                                                            = "Information Protection for Office 365 - Standard"
            'MIP_S_CLP2'                                                            = "Information Protection for Office 365 - Premium"
            'MIP_S_Exchange'                                                        = "Data Classification in Microsoft 365"
            'MIP_S_EXCHANGE_CO'                                                     = "Data Classification in Microsoft 365 - Company Level"
            'ML_CLASSIFICATION'                                                     = "Microsoft ML-Based Classification"
            'MMR_P1'                                                                = "Meeting Room Managed Services"
            'MS_TEAMS_IW'                                                           = "Microsoft Teams Trial"
            'MTP'                                                                   = "Microsoft 365 Defender"
            'MTR_PREM'                                                              = "Teams Rooms Premium"
            'MTRProManagement'                                                      = "Microsoft Teams Rooms Pro Management"
            'MYANALYTICS_P2'                                                        = "Insights by MyAnalytics"
            'MYANALYTICS_P2_GOV'                                                    = "INSIGHTS BY MYANALYTICS FOR GOVERNMENT"
            'NBENTERPRISE'                                                          = "Microsoft Social Engagement Enterprise"
            'NBPROFESSIONALFORCRM'                                                  = "MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY"
            'NONPROFIT_PORTAL'                                                      = "Nonprofit Portal"
            'Nucleus'                                                               = "Nucleus"
            'O365_BUSINESS'                                                         = "Microsoft 365 Apps for Business"
            'O365_BUSINESS_ESSENTIALS'                                              = "Microsoft 365 Business Basic"
            'O365_BUSINESS_PREMIUM'                                                 = "Microsoft 365 Business Standard"
            'O365_SB_Relationship_Management'                                       = "OUTLOOK CUSTOMER MANAGER"
            'OFFICE_BUSINESS'                                                       = "OFFICE 365 BUSINESS"
            'OFFICE_FORMS_PLAN_2'                                                   = "Microsoft Forms (Plan 2)"
            'OFFICE_FORMS_PLAN_3'                                                   = "Microsoft Forms (Plan 3)"
            'OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ'                                    = "OFFICE 365 SMALL BUSINESS SUBSCRIPTION"
            'OFFICE_PROPLUS_DEVICE'                                                 = "Microsoft 365 Apps for Enterprise (Device)"
            'OFFICE_PROPLUS_DEVICE1'                                                = "Microsoft 365 Apps for enterprise (device)"
            'OFFICE_SHARED_COMPUTER_ACTIVATION'                                     = "Office Shared Computer Activation"
            'OFFICE365_MULTIGEO'                                                    = "Multi-Geo Capabilities in Office 365"
            'OFFICEMOBILE_SUBSCRIPTION'                                             = "OFFICEMOBILE_SUBSCRIPTION"
            'OFFICEMOBILE_SUBSCRIPTION_GOV'                                         = "Office Mobile Apps for Office 365 for GCC"
            'OFFICESUBSCRIPTION'                                                    = "Microsoft 365 Apps for Enterprise"
            'OFFICESUBSCRIPTION_FACULTY'                                            = "Microsoft 365 Apps for Faculty"
            'OFFICESUBSCRIPTION_GOV'                                                = "Microsoft 365 Apps for enterprise G"
            'OFFICESUBSCRIPTION_STUDENT'                                            = "Microsoft 365 Apps for Students"
            'OFFICESUBSCRIPTION_unattended'                                         = "Microsoft 365 Apps for Enterprise (Unattended)"
            'ONEDRIVE_BASIC'                                                        = "OneDrive for business Basic"
            'ONEDRIVE_BASIC_GOV'                                                    = "ONEDRIVE FOR BUSINESS BASIC FOR GOVERNMENT"
            'ONEDRIVE_BASIC_P2'                                                     = "OneDrive for Business (Basic 2)"
            'ONEDRIVEENTERPRISE'                                                    = "ONEDRIVEENTERPRISE"
            'ONEDRIVESTANDARD'                                                      = "ONEDRIVESTANDARD"
            'PAM_ENTERPRISE'                                                        = "Office 365 Privileged Access Management"
            'PBI_PREMIUM_P1_ADDON'                                                  = "Power BI Premium P1"
            'PBI_PREMIUM_PER_USER'                                                  = "Power BI Premium Per User"
            'PBI_PREMIUM_PER_USER_ADDON'                                            = "Power BI Premium Per User Add-On"
            'PBI_PREMIUM_PER_USER_DEPT'                                             = "Power BI Premium Per User Dept"
            'PBI_PREMIUM_PER_USER_FACULTY'                                          = "Power BI Premium Per User for Faculty"
            'PHONESYSTEM_VIRTUALUSER'                                               = "Microsoft Teams Phone Resource Account"
            'PHONESYSTEM_VIRTUALUSER_GOV'                                           = "Microsoft Teams Phone Resource Account for GCC"
            'POWER_APPS_DYN365_VIRAL_TRIAL'                                         = "Power Apps for Dynamics 365 vTrial"
            'POWER_AUTOMATE_ATTENDED_RPA'                                           = "Power Automate RPA Attended"
            'POWER_AUTOMATE_DYN365_VIRAL_TRIAL'                                     = "Power Automate for Dynamics 365 vTrial"
            'Power_Automate_For_Project_P1'                                         = "POWER AUTOMATE FOR PROJECT P1"
            'POWER_AUTOMATE_UNATTENDED_RPA'                                         = "Power Automate Unattended RPA add-on"
            'POWER_BI_ADDON'                                                        = "Power BI for Office 365 Add-On"
            'POWER_BI_INDIVIDUAL_USER'                                              = "Power BI"
            'POWER_BI_PRO'                                                          = "Power BI Pro"
            'POWER_BI_PRO_CE'                                                       = "Power BI Pro CE"
            'POWER_BI_PRO_DEPT'                                                     = "Power BI Pro Dept"
            'POWER_BI_PRO_FACULTY'                                                  = "Power BI Pro for Faculty"
            'POWER_BI_STANDARD'                                                     = "Power BI (free)"
            'POWER_BI_STANDARD_FACULTY'                                             = "Microsoft Fabric (Free) for faculty"
            'POWER_BI_STANDARD_STUDENT'                                             = "Microsoft Fabric (Free) for student"
            'Power_Pages_Internal_User'                                             = "Power Pages Internal User"
            'POWER_PAGES_VTRIAL'                                                    = "Power Pages vTrial for Makers"
            'Power_Pages_vTrial_for_Makers'                                         = "Power Pages vTrial for Makers"
            'POWER_VIRTUAL_AGENTS_D365_CS_CHAT'                                     = "Power Virtual Agents for Chat"
            'POWER_VIRTUAL_AGENTS_D365_CS_MESSAGING'                                = "Power Virtual Agents for Digital Messaging"
            'POWER_VIRTUAL_AGENTS_D365_CS_VOICE'                                    = "Power Virtual Agents for Customer Service Voice"
            'POWER_VIRTUAL_AGENTS_O365_F1'                                          = "Power Virtual Agents for Office 365"
            'POWER_VIRTUAL_AGENTS_O365_P1'                                          = "POWER VIRTUAL AGENTS FOR OFFICE 365 P1"
            'POWER_VIRTUAL_AGENTS_O365_P2'                                          = "Power Virtual Agents for Office 365"
            'POWER_VIRTUAL_AGENTS_O365_P3'                                          = "Power Virtual Agents for Office 365"
            'POWERAPPS_CUSTOMER_SERVICE_PRO'                                        = "Power Apps for Customer Service Pro"
            'POWERAPPS_DEV'                                                         = "Microsoft Power Apps for Developer"
            'POWERAPPS_DEV_VIRAL'                                                   = "PowerApps for Developer"
            'POWERAPPS_DYN_APPS'                                                    = "PowerApps for Dynamics 365"
            'POWERAPPS_DYN_P2'                                                      = "Power Apps for Dynamics 365"
            'POWERAPPS_DYN_TEAM'                                                    = "Power Apps for Dynamics 365"
            'POWERAPPS_GUIDES'                                                      = "Power Apps for Guides"
            'POWERAPPS_INDIVIDUAL_USER'                                             = "Power Apps and Logic Flows"
            'POWERAPPS_O365_P1'                                                     = "POWERAPPS FOR OFFICE 365"
            'POWERAPPS_O365_P1_GOV'                                                 = "Power Apps for Office 365 for Government"
            'POWERAPPS_O365_P2'                                                     = "Power Apps for Office 365"
            'POWERAPPS_O365_P2_GOV'                                                 = "POWER APPS FOR OFFICE 365 FOR GOVERNMENT"
            'POWERAPPS_O365_P3'                                                     = "Power Apps for Office 365 (Plan 3)"
            'POWERAPPS_O365_P3_GOV'                                                 = "Power Apps for Office 365 for Government"
            'POWERAPPS_O365_S1'                                                     = "Power Apps for Office 365 F3"
            'POWERAPPS_O365_S1_GOV'                                                 = "Power Apps for Office 365 F3 for Government"
            'POWERAPPS_P1_GOV'                                                      = "PowerApps Plan 1 for Government"
            'POWERAPPS_P2'                                                          = "Power Apps (Plan 2)"
            'POWERAPPS_P2_VIRAL'                                                    = "PowerApps Trial"
            'POWERAPPS_PER_APP'                                                     = "Power Apps per app plan"
            'POWERAPPS_PER_APP_IW'                                                  = "PowerApps per app baseline access"
            'POWERAPPS_PER_APP_IWTRIAL'                                             = "PowerApps per app baseline access"
            'POWERAPPS_PER_APP_NEW'                                                 = "Power Apps per app plan (1 app or portal)"
            'POWERAPPS_PER_USER'                                                    = "Power Apps per user plan"
            'POWERAPPS_PER_USER_GCC'                                                = "Power Apps per user plan for Government"
            'POWERAPPS_PORTALS_LOGIN'                                               = "Power Apps Portals Login Capacity Add-On"
            'POWERAPPS_PORTALS_LOGIN_GCC'                                           = "Power Apps Portals Login Capacity Add-On for Government"
            'POWERAPPS_PORTALS_LOGIN_T2'                                            = "Power Apps Portals login capacity add-on Tier 2 (10 unit min)"
            'POWERAPPS_PORTALS_LOGIN_T2_GCC'                                        = "Power Apps Portals login capacity add-on Tier 2 (10 unit min) for Government"
            'POWERAPPS_PORTALS_LOGIN_T3'                                            = "Power Apps Portals login capacity add-on Tier 3 (50 unit min)"
            'POWERAPPS_PORTALS_PAGEVIEW'                                            = "Power Apps Portals page view capacity add-on"
            'POWERAPPS_PORTALS_PAGEVIEW_GCC'                                        = "Power Apps Portals page view capacity add-on for Government"
            'POWERAPPS_SALES_PRO'                                                   = "Power Apps for Sales Pro"
            'POWERAPPS_VIRAL'                                                       = "Microsoft Power Apps Plan 2 Trial"
            'POWERAPPSFREE'                                                         = "MICROSOFT POWERAPPS"
            'POWERAUTOMATE_ATTENDED_RPA'                                            = "Power Automate per user with attended RPA plan"
            'POWERAUTOMATE_DESKTOP_FOR_WIN'                                         = "PAD for Windows"
            'POWERAUTOMATE_UNATTENDED_RPA'                                          = "Power Automate unattended RPA add-on"
            'POWERBI_PRO_GOV'                                                       = "Power BI Pro for GCC"
            'POWERFLOW_P2'                                                          = "Microsoft Power Apps Plan 2 (Qualified Offer)"
            'POWERFLOWSFREE'                                                        = "LOGIC FLOWS"
            'POWERVIDEOSFREE'                                                       = "MICROSOFT POWER VIDEOS BASIC"
            'PREMIUM_ENCRYPTION'                                                    = "Premium Encryption in Office 365"
            'PRIVACY_MANAGEMENT_RISK'                                               = "Privacy Management � risk"
            'PRIVACY_MANAGEMENT_RISK_EDU'                                           = "Privacy Management - risk for EDU"
            'PRIVACY_MANAGEMENT_RISK_GCC'                                           = "Privacy Management - risk GCC"
            'PRIVACY_MANAGEMENT_RISK_USGOV_DOD'                                     = "Privacy Management - risk_USGOV_DOD"
            'PRIVACY_MANAGEMENT_RISK_USGOV_GCCHIGH'                                 = "Privacy Management - risk_USGOV_GCCHIGH"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_EDU_V2'                            = "Privacy Management - subject rights request (1) for EDU"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2'                                = "Privacy Management - subject rights request (1)"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_GCC'                            = "Privacy Management - subject rights request (1) GCC"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_DOD'                      = "Privacy Management - subject rights request (1) USGOV_DOD"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_GCCHIGH'                  = "Privacy Management - subject rights request (1) USGOV_GCCHIGH"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_EDU_V2'                           = "Privacy Management - subject rights request (10) for EDU"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2'                               = "Privacy Management - subject rights request (10)"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_GCC'                           = "Privacy Management - subject rights request (10) GCC"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_DOD'                     = "Privacy Management - subject rights request (10) USGOV_DOD"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_GCCHIGH'                 = "Privacy Management - subject rights request (10) USGOV_GCCHIGH"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_EDU_V2'                          = "Privacy Management - subject rights request (100) for EDU"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2'                              = "Privacy Management - subject rights request (100)"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_GCC'                          = "Privacy Management - subject rights request (100) GCC"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_DOD'                    = "Privacy Management - subject rights request (100) USGOV_DOD"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_GCCHIGH'                = "Privacy Management - subject rights request (100) USGOV_GCCHIGH"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50'                                  = "Privacy Management - subject rights request (50)"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_EDU_V2'                           = "Privacy Management - subject rights request (50) for EDU"
            'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_V2'                               = "Privacy Management - subject rights request (50)"
            'PRIVACY_MANGEMENT_DSR'                                                 = "Privacy Management - Subject Rights Request"
            'PRIVACY_MANGEMENT_DSR_1'                                               = "Privacy Management - Subject Rights Request (1 - Exchange)"
            'PRIVACY_MANGEMENT_DSR_10'                                              = "Privacy Management - Subject Rights Request (10)"
            'PRIVACY_MANGEMENT_DSR_100'                                             = "Privacy Management - Subject Rights Request (100)"
            'PRIVACY_MANGEMENT_DSR_EXCHANGE'                                        = "Privacy Management - Subject Rights Request (Exchange)"
            'PRIVACY_MANGEMENT_DSR_EXCHANGE_1'                                      = "Privacy Management - Subject Rights Request (1)"
            'PRIVACY_MANGEMENT_DSR_EXCHANGE_10'                                     = "Privacy Management - Subject Rights Request (10 - Exchange)"
            'PRIVACY_MANGEMENT_DSR_EXCHANGE_100'                                    = "Privacy Management - Subject Rights Request (100 - Exchange)"
            'PRIVACY_MANGEMENT_RISK'                                                = "Priva - Risk"
            'PRIVACY_MANGEMENT_RISK_EXCHANGE'                                       = "Priva - Risk (Exchange)"
            'PROJECT_CLIENT_SUBSCRIPTION'                                           = "Project Online Desktop Client"
            'PROJECT_CLIENT_SUBSCRIPTION_GOV'                                       = "Project Online Desktop Client for Government"
            'PROJECT_ESSENTIALS'                                                    = "Project Online Essentials"
            'PROJECT_ESSENTIALS_GOV'                                                = "Project Online Essentials for Government"
            'PROJECT_FOR_PROJECT_OPERATIONS'                                        = "Project for Project Operations"
            'PROJECT_MADEIRA_PREVIEW_IW'                                            = "Dynamics 365 Business Central for IWs"
            'PROJECT_MADEIRA_PREVIEW_IW_SKU'                                        = "Dynamics 365 Business Central for IWs"
            'PROJECT_O365_F3'                                                       = "Project for Office (Plan F)"
            'PROJECT_O365_P1'                                                       = "Project for Office (Plan E1)"
            'PROJECT_O365_P2'                                                       = "Project for Office (Plan E3)"
            'PROJECT_O365_P3'                                                       = "Project for Office (Plan E5)"
            'PROJECT_P1'                                                            = "Project Plan 1"
            'PROJECT_PLAN1_DEPT'                                                    = "Project Plan 1 (for Department)"
            'PROJECT_PLAN3_DEPT'                                                    = "Project Plan 3 (for Department)"
            'PROJECT_PROFESSIONAL'                                                  = "Project P3"
            'PROJECT_PROFESSIONAL_FACULTY'                                          = "Project P3 for Faculty"
            'PROJECTCLIENT'                                                         = "Project for Office 365"
            'PROJECTESSENTIALS'                                                     = "Project Online Essentials"
            'PROJECTESSENTIALS_FACULTY'                                             = "Project Online Essentials for Faculty"
            'PROJECTESSENTIALS_GOV'                                                 = "Project Online Essentials for GCC"
            'PROJECTONLINE_PLAN_1'                                                  = "Project Online Premium Without Project Client"
            'PROJECTONLINE_PLAN_1_FACULTY'                                          = "Project Plan 5 without Project Client for Faculty"
            'PROJECTONLINE_PLAN_2'                                                  = "Project Online With Project for Office 365"
            'PROJECTPREMIUM'                                                        = "Project Online Premium"
            'PROJECTPREMIUM_GOV'                                                    = "Project Plan 5 for GCC"
            'PROJECTPROFESSIONAL'                                                   = "Project Plan 3"
            'PROJECTPROFESSIONAL_FACULTY'                                           = "Project Plan 3 for Faculty"
            'PROJECTPROFESSIONAL_GOV'                                               = "Project Plan 3 for GCC"
            'PROJECTWORKMANAGEMENT'                                                 = "Microsoft Planner"
            'PROJECTWORKMANAGEMENT_GOV'                                             = "Office 365 Planner for Government"
            'RECORDS_MANAGEMENT'                                                    = "Microsoft Records Management"
            'REMOTE_HELP'                                                           = "Remote help"
            'RIGHTSMANAGEMENT'                                                      = "Azure Information Protection Plan 1"
            'RIGHTSMANAGEMENT_ADHOC'                                                = "Rights Management Adhoc"
            'RMS_S_ADHOC'                                                           = "Rights Management Adhoc"
            'RMS_S_BASIC'                                                           = "Microsoft Azure Rights Management Service"
            'RMS_S_ENTERPRISE'                                                      = "AZURE INFORMATION PROTECTION PREMIUM P1"
            'RMS_S_ENTERPRISE)'                                                     = "Microsoft Azure Active Directory Rights"
            'RMS_S_ENTERPRISE_GOV'                                                  = "Azure Rights Management"
            'RMS_S_PREMIUM'                                                         = "MICROSOFT AZURE ACTIVE DIRECTORY RIGHTS"
            'RMS_S_PREMIUM_GOV'                                                     = "Azure Information Protection Premium P1 for GCC"
            'RMS_S_PREMIUM2'                                                        = "AZURE INFORMATION PROTECTION PREMIUM P2"
            'RMS_S_PREMIUM2_GOV'                                                    = "Azure Information Protection Premium P2 for GCC"
            'RMSBASIC'                                                              = "Rights Management Service Basic Content Protection"
            'SAFEDOCS'                                                              = "Office 365 SafeDocs"
            'SCHOOL_DATA_SYNC_P1'                                                   = "School Data Sync (Plan 1)"
            'SCHOOL_DATA_SYNC_P2'                                                   = "School Data Sync (Plan 2)"
            'SharePoint Plan 1G'                                                    = "SharePoint Plan 1G"
            'SHAREPOINT_PROJECT'                                                    = "Project Online Service"
            'SHAREPOINT_PROJECT_EDU'                                                = "Project Online Service for Education"
            'SHAREPOINT_PROJECT_GOV'                                                = "Project Online Service for Government"
            'SHAREPOINT_S_DEVELOPER'                                                = "SHAREPOINT FOR DEVELOPER"
            'SHAREPOINTDESKLESS'                                                    = "SharePoint Online Kiosk"
            'SHAREPOINTDESKLESS_GOV'                                                = "SharePoint KioskG"
            'SHAREPOINTENTERPRISE'                                                  = "SharePoint Online (Plan 2)"
            'SHAREPOINTENTERPRISE_EDU'                                              = "SharePoint (Plan 2) for Education"
            'SHAREPOINTENTERPRISE_GOV'                                              = "SharePoint Plan 2G"
            'SHAREPOINTENTERPRISE_MIDMARKET'                                        = "SHAREPOINT PLAN 1"
            'SHAREPOINTLITE'                                                        = "SHAREPOINTLITE"
            'SHAREPOINTONLINE_MULTIGEO'                                             = "SharePoint Multi-Geo"
            'SHAREPOINTSTANDARD'                                                    = "SharePoint Online (Plan 1)"
            'SHAREPOINTSTANDARD_EDU'                                                = "SharePoint (Plan 1) for Education"
            'SHAREPOINTSTORAGE'                                                     = "Office 365 Extra File Storage"
            'SHAREPOINTSTORAGE_GOV'                                                 = "Office 365 Extra File Storage for GCC"
            'SHAREPOINTWAC'                                                         = "Office for the web"
            'SHAREPOINTWAC_DEVELOPER'                                               = "OFFICE ONLINE FOR DEVELOPER"
            'SHAREPOINTWAC_EDU'                                                     = "Office for the Web for Education"
            'SHAREPOINTWAC_GOV'                                                     = "Office for the Web for Government"
            'SKU_Dynamics_365_for_HCM_Trial'                                        = "Dynamics 365 for Talent"
            'SMB_APPS'                                                              = "Business Apps (free)"
            'SMB_BUSINESS'                                                          = "Microsoft 365 Apps for Business"
            'SMB_BUSINESS_ESSENTIALS'                                               = "Microsoft 365 Business Basic"
            'SMB_BUSINESS_PREMIUM'                                                  = "Microsoft 365 Business Standard - Prepaid Legacy"
            'SOCIAL_ENGAGEMENT_APP_USER'                                            = "Dynamics 365 AI for Market Insights (Preview)"
            'SPB'                                                                   = "Microsoft 365 Business Premium"
            'SPE_E3'                                                                = "Microsoft 365 E3"
            'SPE_E3_RPA1'                                                           = "Microsoft 365 E3 - Unattended License"
            'SPE_E3_USGOV_DOD'                                                      = "Microsoft 365 E3_USGOV_DOD"
            'SPE_E3_USGOV_GCCHIGH'                                                  = "Microsoft 365 E3_USGOV_GCCHIGH"
            'SPE_E5'                                                                = "Microsoft 365 E5"
            'SPE_E5_CALLINGMINUTES'                                                 = "Microsoft 365 E5 with Calling Minutes"
            'SPE_E5_NOPSTNCONF'                                                     = "Microsoft 365 E5 without Audio Conferencing"
            'SPE_F1'                                                                = "Microsoft 365 F3"
            'SPE_F5_COMP'                                                           = "Microsoft 365 F5 Compliance Add-on"
            'SPE_F5_COMP_AR_D_USGOV_DOD'                                            = "Microsoft 365 F5 Compliance Add-on AR (DOD)_USGOV_DOD"
            'SPE_F5_COMP_AR_USGOV_GCCHIGH'                                          = "Microsoft 365 F5 Compliance Add-on AR_USGOV_GCCHIGH"
            'SPE_F5_COMP_GCC'                                                       = "Microsoft 365 F5 Compliance Add-on GCC"
            'SPE_F5_SEC'                                                            = "Microsoft 365 F5 Security Add-on"
            'SPE_F5_SECCOMP'                                                        = "Microsoft 365 F5 Security + Compliance Add-on"
            'SPZA'                                                                  = "APP CONNECT"
            'SPZA_IW'                                                               = "App Connect IW"
            'SQL_IS_SSIM'                                                           = "Microsoft Power BI Information Services Plan 1"
            'STANDARDPACK'                                                          = "Office 365 E1"
            'STANDARDPACK_GOV'                                                      = "Office 365 G1 GCC"
            'STANDARDWOFFPACK'                                                      = "Office 365 E2"
            'STANDARDWOFFPACK_FACULTY'                                              = "Office 365 A1 for faculty"
            'STANDARDWOFFPACK_IW_FACULTY'                                           = "Office 365 A1 Plus for faculty"
            'STANDARDWOFFPACK_IW_STUDENT'                                           = "Office 365 A1 Plus for students"
            'STANDARDWOFFPACK_STUDENT'                                              = "Office 365 A1 for students"
            'STREAM'                                                                = "Microsoft Stream"
            'STREAM_O365_E1'                                                        = "Microsoft Stream for Office 365 E1"
            'STREAM_O365_E1_GOV'                                                    = "Microsoft Stream for O365 for Government (E1)"
            'STREAM_O365_E3'                                                        = "Microsoft Stream for Office 365 E3"
            'STREAM_O365_E3_GOV'                                                    = "MICROSOFT STREAM FOR O365 FOR GOVERNMENT (E3)"
            'STREAM_O365_E5'                                                        = "Microsoft Stream for Office 365 E5"
            'STREAM_O365_E5_GOV'                                                    = "Stream for Office 365 for Government (E5)"
            'STREAM_O365_K'                                                         = "Microsoft Stream for O365 K SKU"
            'STREAM_O365_K_GOV'                                                     = "Microsoft Stream for O365 for Government (F1)"
            'STREAM_O365_SMB'                                                       = "Stream for Office 365"
            'STREAM_P2'                                                             = "Microsoft Stream Plan 2"
            'STREAM_STORAGE'                                                        = "Microsoft Stream Storage Add-On (500 GB)"
            'SWAY'                                                                  = "Sway"
            'TEAMS_ADVCOMMS'                                                        = "Microsoft 365 Advanced Communications"
            'TEAMS_AR_DOD'                                                          = "Microsoft Teams for DOD (AR)"
            'TEAMS_AR_GCCHIGH'                                                      = "Microsoft Teams for GCCHigh (AR)"
            'TEAMS_COMMERCIAL_TRIAL'                                                = "Microsoft Teams Commercial Cloud"
            'Teams_Ess'                                                             = "Microsoft Teams Essentials"
            'TEAMS_ESSENTIALS_AAD'                                                  = "Microsoft Teams Essentials (AAD Identity)"
            'TEAMS_EXPLORATORY'                                                     = "Microsoft Teams Exploratory"
            'TEAMS_FREE'                                                            = "Microsoft Teams (Free)"
            'TEAMS_FREE_SERVICE'                                                    = "TEAMS FREE SERVICE"
            'TEAMS_GOV'                                                             = "Microsoft Teams for Government"
            'Teams_Room_Basic'                                                      = "Teams Room Basic"
            'Teams_Room_Pro'                                                        = "Teams Room Pro"
            'Teams_Room_Standard'                                                   = "Teams Room Standard"
            'TEAMS1'                                                                = "Microsoft Teams"
            'TeamsEss'                                                              = "Microsoft Teams Essentials"
            'TEAMSMULTIGEO'                                                         = "Teams Multi-Geo"
            'TEAMSPRO_CUST'                                                         = "Microsoft Teams Premium Personalized"
            'TEAMSPRO_MGMT'                                                         = "Microsoft Teams Premium Intelligent"
            'TEAMSPRO_PROTECTION'                                                   = "Microsoft Teams Premium Secure"
            'TEAMSPRO_VIRTUALAPPT'                                                  = "Microsoft Teams Premium Virtual Appointment"
            'TEAMSPRO_WEBINAR'                                                      = "Microsoft Teams Premium Webinar"
            'THREAT_INTELLIGENCE'                                                   = "Microsoft Defender for Office 365 (Plan 2)"
            'THREAT_INTELLIGENCE_APP'                                               = "Defender Threat Intelligence"
            'THREAT_INTELLIGENCE_GOV'                                               = "Microsoft Defender for Office 365 (Plan 2) GCC"
            'TOPIC_EXPERIENCES'                                                     = "Viva Topics"
            'TVM_PREMIUM_1'                                                         = "Microsoft Defender Vulnerability Management"
            'TVM_Premium_Add_on'                                                    = "Microsoft Defender Vulnerability Management Add-on"
            'TVM_Premium_Standalone'                                                = "Microsoft Defender Vulnerability Management"
            'UNIVERSAL_PRINT'                                                       = "Universal Print"
            'UNIVERSAL_PRINT_01'                                                    = "Universal Print"
            'UNIVERSAL_PRINT_NO_SEEDING'                                            = "Universal Print Without Seeding"
            'VIRTUAL_AGENT_BASE'                                                    = "Power Virtual Agent"
            'VIRTUAL_AGENT_USL'                                                     = "Power Virtual Agent User License"
            'Virtualization Rights for Windows 10 (E3/E5+VDA)'                 = "Windows 10 Enterprise (New)"
            'Virtualization Rights for Windows 10'                                  = "Windows 10/11 Enterprise"
            'Virtualization Rights for Windows 10 (E3/E5+VDA)'                      = "Windows 10/11 Enterprise"
            'VISIO_CLIENT_SUBSCRIPTION'                                             = "Visio Desktop App"
            'VISIO_CLIENT_SUBSCRIPTION_GOV'                                         = "VISIO DESKTOP APP FOR Government"
            'VISIO_PLAN1_DEPT'                                                      = "Visio Plan 1"
            'VISIO_PLAN2_DEPT'                                                      = "Visio Plan 2"
            'VISIOCLIENT'                                                           = "Visio Online Plan 2"
            'VISIOCLIENT_FACULTY'                                                   = "Visio Plan 2 for Faculty"
            'VISIOCLIENT_GOV'                                                       = "Visio Plan 2 for GCC"
            'VISIOONLINE'                                                           = "Visio web app"
            'VISIOONLINE_GOV'                                                       = "VISIO WEB APP FOR GOVERNMENT"
            'VISIOONLINE_PLAN1'                                                     = "Visio Online Plan 1"
            'VIVA'                                                                  = "Microsoft Viva Suite"
            'Viva_Goals_Premium'                                                    = "Viva Goals"
            'VIVA_LEARNING_PREMIUM'                                                 = "Viva Learning"
            'VIVA_LEARNING_SEEDED'                                                  = "Viva Learning Seeded"
            'VIVAENGAGE_COMMUNITIES_AND_COMMUNICATIONS'                             = "Viva Engage Communities and Communications"
            'VIVAENGAGE_CORE'                                                       = "Viva Engage Core"
            'VIVAENGAGE_KNOWLEDGE'                                                  = "Viva Engage Knowledge"
            'WACONEDRIVEENTERPRISE'                                                 = "OneDrive for Business (Plan 2)"
            'WACONEDRIVESTANDARD'                                                   = "OneDrive for Business (Plan 1)"
            'WHITEBOARD_FIRSTLINE1'                                                 = "Whiteboard (Firstline)"
            'WHITEBOARD_PLAN1'                                                      = "Whiteboard (Plan 1)"
            'WHITEBOARD_PLAN2'                                                      = "Whiteboard (Plan 2)"
            'WHITEBOARD_PLAN3'                                                      = "Whiteboard (Plan 3)"
            'WIN_DEF_ATP'                                                           = "Microsoft Defender for Endpoint"
            'WIN_ENT_E5'                                                            = "Windows 10/11 Enterprise E5 (Original)"
            'WIN10_ENT_A3_FAC'                                                      = "Windows 10/11 Enterprise A3 for faculty"
            'WIN10_ENT_A3_STU'                                                      = "Windows 10/11 Enterprise A3 for students"
            'WIN10_ENT_A5_FAC'                                                      = "Windows 10/11 Enterprise A5 for faculty"
            'WIN10_ENT_LOC_F1'                                                      = "Windows 10 Enterprise E3 (Local Only)"
            'WIN10_PRO_ENT_SUB'                                                     = "Windows 10/11 Enterprise E3"
            'WIN10_VDA_E3'                                                          = "Windows 10/11 Enterprise E3"
            'WIN10_VDA_E5'                                                          = "Windows 10/11 Enterprise E5"
            'WINBIZ'                                                                = "Windows 10/11 Business"
            'WINDEFATP'                                                             = "Microsoft Defender for Endpoint"
            'Windows Autopatch'                                                     = "Windows Autopatch"
            'Windows Store for Business EDU Store_faculty'                          = "Windows Store for Business EDU Store_faculty"
            'Windows_365_S_2vCPU_4GB_128GB'                                         = "Windows 365 Shared Use 2 vCPU 4 GB 128 GB"
            'Windows_365_S_2vCPU_4GB_256GB'                                         = "Windows 365 Shared Use 2 vCPU 4 GB 256 GB"
            'Windows_365_S_2vCPU_4GB_64GB'                                          = "Windows 365 Shared Use 2 vCPU 4 GB 64 GB"
            'Windows_365_S_2vCPU_8GB_128GB'                                         = "Windows 365 Shared Use 2 vCPU 8 GB 128 GB"
            'Windows_365_S_2vCPU_8GB_256GB'                                         = "Windows 365 Shared Use 2 vCPU 8 GB 256 GB"
            'Windows_365_S_4vCPU_16GB_128GB'                                        = "Windows 365 Shared Use 4 vCPU 16 GB 128 GB"
            'Windows_365_S_4vCPU_16GB_256GB'                                        = "Windows 365 Shared Use 4 vCPU 16 GB 256 GB"
            'Windows_365_S_4vCPU_16GB_512GB'                                        = "Windows 365 Shared Use 4 vCPU 16 GB 512 GB"
            'Windows_365_S_8vCPU_32GB_128GB'                                        = "Windows 365 Shared Use 8 vCPU 32 GB 128 GB"
            'Windows_365_S_8vCPU_32GB_256GB'                                        = "Windows 365 Shared Use 8 vCPU 32 GB 256 GB"
            'Windows_365_S_8vCPU_32GB_512GB'                                        = "Windows 365 Shared Use 8 vCPU 32 GB 512 GB"
            'Windows_Autopatch'                                                     = "Windows Autopatch"
            'WINDOWS_STORE'                                                         = "Windows Store for Business"
            'WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE'                            = "Windows Update for Business Deployment Service"
            'WINE5_GCC_COMPAT'                                                      = "Windows 10/11 Enterprise E5 Commercial (GCC Compatible)"
            'WORKPLACE_ANALYTICS'                                                   = "Microsoft Workplace Analytics"
            'WORKPLACE_ANALYTICS_INSIGHTS_BACKEND'                                  = "Microsoft Viva Insights Backend"
            'WORKPLACE_ANALYTICS_INSIGHTS_USER'                                     = "Microsoft Viva Insights"
            'WSFB_EDU_FACULTY'                                                      = "Windows Store for Business EDU Faculty"
            'YAMMER_EDU'                                                            = "Yammer for Academic"
            'YAMMER_ENTERPRISE'                                                     = "YAMMER_ENTERPRISE"
            'YAMMER_MIDSIZE'                                                        = "YAMMER MIDSIZE"
        }
        $SKUO365 = [ordered] @{
            'AAD_PREMIUM'                                                                                            = "AAD_PREMIUM"
            'ADALLOM_S_DISCOVERY'                                                                                    = "ADALLOM_S_DISCOVERY"
            'ADALLOM_S_O365'                                                                                         = "ADALLOM_S_O365"
            'Advanced Communications'                                                                                = "ADV_COMMS"
            'AI Builder Capacity add-on'                                                                             = "CDSAICAPACITY"
            'AI Builder capacity Per App add-on'                                                                     = "CDSAICAPACITY_PERAPP"
            'AI Builder capacity Per User add-on'                                                                    = "CDSAICAPACITY_PERUSER"
            'APP CONNECT'                                                                                            = "SPZA"
            'App Connect IW'                                                                                         = "SPZA_IW"
            'App governance add-on to Microsoft Defender for Cloud Apps'                                             = "Microsoft_Cloud_App_Security_App_Governance_Add_On"
            'Asset Maintenance Add-in'                                                                               = "D365_AssetforSCM"
            'ATP_ENTERPRISE_GOV'                                                                                     = "ATP_ENTERPRISE_GOV"
            'AUSTRALIA CALLING PLAN'                                                                                 = "MCOPSTNEAU"
            'Azure Active Directory'                                                                                 = "AAD_SMB"
            'Azure Active Directory Basic'                                                                           = "AAD_BASIC"
            'Azure Active Directory Basic for EDU'                                                                   = "AAD_BASIC_EDU"
            'Azure Active Directory Basic for Education'                                                             = "AAD_BASIC_EDU"
            'Azure Active Directory for Education'                                                                   = "AAD_EDU"
            'Azure Active Directory Premium P1'                                                                      = "AAD_PREMIUM"
            'Azure Active Directory Premium P1 for Faculty'                                                          = "AAD_PREMIUM_FACULTY"
            'Azure Active Directory Premium P2'                                                                      = "AAD_PREMIUM_P2"
            'Azure Active Directory Premium Plan 1'                                                                  = "AAD_PREMIUM"
            'Azure Information Protection Plan 1'                                                                    = "RIGHTSMANAGEMENT"
            'Azure Information Protection Premium P'                                                                 = "RMS_S_PREMIUM"
            'AZURE INFORMATION PROTECTION PREMIUM P1'                                                                = "RMS_S_ENTERPRISE"
            'Azure Information Protection Premium P1 for GCC'                                                        = "RMS_S_PREMIUM_GOV"
            'AZURE INFORMATION PROTECTION PREMIUM P2'                                                                = "RMS_S_PREMIUM2"
            'Azure Information Protection Premium P2 for GCC'                                                        = "RMS_S_PREMIUM2_GOV"
            'Azure Rights Management'                                                                                = "RMS_S_ENTERPRISE"
            'AZURE RIGHTS MANAGEMENT PREMIUM FOR GOVERNMENT'                                                         = "RMS_S_PREMIUM_GOV"
            'BI_AZURE_P_2_GOV'                                                                                       = "BI_AZURE_P_2_GOV"
            'BI_AZURE_P0'                                                                                            = "BI_AZURE_P0"
            'BPOS_S_TODO_1'                                                                                          = "BPOS_S_TODO_1"
            'BPOS_S_TODO_2'                                                                                          = "BPOS_S_TODO_2"
            'BPOS_S_TODO_3'                                                                                          = "BPOS_S_TODO_3"
            'Business Apps (free)'                                                                                   = "SMB_APPS"
            'CDS Per app baseline access'                                                                            = "CDS_PER_APP_IWTRIAL"
            'CDS PowerApps per app plan'                                                                             = "CDS_PER_APP"
            'CDS PowerApps Portals page view capacity add-on'                                                        = "CDS_POWERAPPS_PORTALS_PAGEVIEW"
            'CDS PowerApps Portals page view capacity add-on for GCC'                                                = "CDS_POWERAPPS_PORTALS_PAGEVIEW_GCC"
            'CDS_O365_P3_GCC'                                                                                        = "CDS_O365_P3_GCC"
            'CLOUD APP SECURITY DISCOVERY'                                                                           = "ADALLOM_S_DISCOVERY"
            'Common Data Service'                                                                                    = "DYN365_CDS_FORMS_PRO"
            'Common Data Service - DEV VIRAL'                                                                        = "DYN365_CDS_DEV_VIRAL"
            'Common Data Service - O365 F1'                                                                          = "DYN365_CDS_O365_F1_GCC"
            'Common Data Service - O365 P1'                                                                          = "DYN365_CDS_O365_P1"
            'Common Data Service - O365 P1 GCC'                                                                      = "DYN365_CDS_O365_P1_GCC"
            'Common Data Service - O365 P2'                                                                          = "DYN365_CDS_O365_P2"
            'COMMON DATA SERVICE - O365 P2 GCC'                                                                      = "DYN365_CDS_O365_P2_GCC"
            'Common Data Service - O365 P3'                                                                          = "DYN365_CDS_O365_P3"
            'Common Data Service - P2'                                                                               = "DYN365_CDS_P2"
            'Common Data Service - VIRAL'                                                                            = "DYN365_CDS_VIRAL"
            'Common Data Service Attended RPA'                                                                       = "CDS_ATTENDED_RPA"
            'Common Data Service Database Capacity'                                                                  = "CDS_DB_CAPACITY"
            'Common Data Service Database Capacity for Government'                                                   = "CDS_DB_CAPACITY_GOV"
            'Common Data Service for Apps Database Capacity'                                                         = "CDS_DB_CAPACITY"
            'Common Data Service for Apps Database Capacity for Government'                                          = "CDS_DB_CAPACITY_GOV"
            'Common Data Service for Apps File Capacity'                                                             = "CDS_FILE_CAPACITY"
            'Common Data Service for Apps Log Capacity'                                                              = "CDS_LOG_CAPACITY"
            'Common Data Service for CCI Bots'                                                                       = "DYN365_CDS_CCI_BOTS"
            'Common Data Service for Customer Insights'                                                              = "CDS_CUSTOMER_INSIGHTS"
            'Common Data Service for Customer Insights Trial'                                                        = "CDS_CUSTOMER_INSIGHTS_TRIAL"
            'Common Data Service for Dynamics 365 Finance'                                                           = "DYN365_CDS_FINANCE"
            'COMMON DATA SERVICE FOR DYNAMICS 365 SUPPLY CHAIN MANAGEMENT'                                           = "DYN365_CDS_SUPPLYCHAINMANAGEMENT"
            'Common data service for Flow per business process plan'                                                 = "CDS_Flow_Business_Process"
            'Common Data Service for Government'                                                                     = "DYN365_CDS_P2_GOV"
            'Common Data Service for Project'                                                                        = "DYN365_CDS_PROJECT"
            'COMMON DATA SERVICE FOR PROJECT P1'                                                                     = "DYN365_CDS_FOR_PROJECT_P1"
            'Common Data Service for Remote Assist'                                                                  = "CDS_REMOTE_ASSIST"
            'Common Data Service for SharePoint Syntex'                                                              = "CDS_O365_E5_KM"
            'Common Data Service for Teams'                                                                          = "CDS_O365_P2"
            'Common Data Service for Teams_F1'                                                                       = "CDS_O365_F1"
            'Common Data Service for Teams_F1 GCC'                                                                   = "CDS_O365_F1_GCC"
            'COMMON DATA SERVICE FOR TEAMS_P1'                                                                       = "CDS_O365_P1"
            'Common Data Service for Teams_P1 GCC'                                                                   = "CDS_O365_P1_GCC"
            'Common Data Service for Teams_P2'                                                                       = "CDS_O365_P2"
            'COMMON DATA SERVICE FOR TEAMS_P2 GCC'                                                                   = "CDS_O365_P2_GCC"
            'Common Data Service for Teams_P3'                                                                       = "CDS_O365_P3"
            'Common Data Service for Virtual Agent Base'                                                             = "CDS_VIRTUAL_AGENT_BASE"
            'Common Data Service Log Capacity'                                                                       = "CDS_LOG_CAPACITY"
            'Common Data Service Power Apps Portals Login Capacity'                                                  = "CDS_POWERAPPS_PORTALS_LOGIN"
            'Common Data Service Power Apps Portals Login Capacity for GCC'                                          = "CDS_POWERAPPS_PORTALS_LOGIN_GCC"
            'Common Data Service Unattended RPA'                                                                     = "CDS_UNATTENDED_RPA"
            'Communications Credits'                                                                                 = "MCOPSTNC"
            'COMMUNICATIONS_DLP'                                                                                     = "COMMUNICATIONS_DLP"
            'Compliance Manager Premium Assessment Add-On'                                                           = "CMPA_addon"
            'Compliance Manager Premium Assessment Add-On for GCC'                                                   = "CMPA_addon_GCC"
            'Content_Explorer'                                                                                       = "Content_Explorer"
            'ContentExplorer_Standard'                                                                               = "ContentExplorer_Standard"
            'CRM Hybrid Connector'                                                                                   = "CRM_HYBRIDCONNECTOR"
            'Customer Lockbox'                                                                                       = "LOCKBOX_ENTERPRISE"
            'Customer Lockbox for Government'                                                                        = "LOCKBOX_ENTERPRISE_GOV"
            'Customer Voice for Dynamics 365 vTrial'                                                                 = "CUSTOMER_VOICE_DYN365_VIRAL_TRIAL"
            'CUSTOMER_KEY'                                                                                           = "CUSTOMER_KEY"
            'Data Classification in Microsoft 365'                                                                   = "MIP_S_Exchange"
            'Data Classification in Microsoft 365 - Company Level'                                                   = "MIP_S_EXCHANGE_CO"
            'Data Loss Prevention'                                                                                   = "BPOS_S_DlpAddOn"
            'Dataverse for Cust Insights�BASE'                                                                       = "CDS_CUSTOMER_INSIGHTS_BASE"
            'Dataverse for Customer Insights�BASE'                                                                   = "CDS_CUSTOMER_INSIGHTS_BASE"
            'Dataverse for PAD'                                                                                      = "DATAVERSE_FOR_POWERAUTOMATE_DESKTOP"
            'Dataverse for Power Apps per app'                                                                       = "DATAVERSE_POWERAPPS_PER_APP_NEW"
            'Defender Threat Intelligence'                                                                           = "Defender_Threat_Intelligence"
            'DOMESTIC AND INTERNATIONAL CALLING PLAN'                                                                = "MCOPSTN2"
            'Domestic Calling for Government'                                                                        = "MCOPSTN1_GOV"
            'DOMESTIC CALLING PLAN'                                                                                  = "MCOPSTN1"
            'DYN365_CDS_O365_F1'                                                                                     = "DYN365_CDS_O365_F1"
            'DYN365_CDS_O365_P3_GCC'                                                                                 = "DYN365_CDS_O365_P3_GCC"
            'Dynamics 365 - Additional Database Storage (Qualified Offer)'                                           = "CRMSTORAGE"
            'Dynamics 365 - Additional Non-Production Instance (Qualified Offer)'                                    = "CRMTESTINSTANCE"
            'Dynamics 365 - Additional Production Instance (Qualified Offer)'                                        = "CRMINSTANCE"
            'Dynamics 365 AI for Customer Service Trial'                                                             = "DYN365_AI_SERVICE_INSIGHTS"
            'Dynamics 365 AI for Customer Service Virtual Agents Viral'                                              = "CCIBOTS_PRIVPREV_VIRAL"
            'Dynamics 365 AI for Market Insights - Free'                                                             = "SOCIAL_ENGAGEMENT_APP_USER"
            'Dynamics 365 AI for Market Insights (Preview)'                                                          = "SOCIAL_ENGAGEMENT_APP_USER"
            'Dynamics 365 AI for Sales (Embedded)'                                                                   = "DYN365_SALES_INSIGHTS"
            'Dynamics 365 Asset Management Addl Assets'                                                              = "DYN365_ASSETMANAGEMENT"
            'Dynamics 365 Business Central Additional Environment Addon'                                             = "DYN365_BUSCENTRAL_ADD_ENV_ADDON"
            'Dynamics 365 Business Central Database Capacity'                                                        = "DYN365_BUSCENTRAL_DB_CAPACITY"
            'Dynamics 365 Business Central Essentials'                                                               = "DYN365_BUSCENTRAL_ESSENTIAL"
            'Dynamics 365 Business Central External Accountant'                                                      = "DYN365_FINANCIALS_ACCOUNTANT_SKU"
            'Dynamics 365 Business Central for IWs'                                                                  = "PROJECT_MADEIRA_PREVIEW_IW_SKU"
            'Dynamics 365 Business Central Premium'                                                                  = "DYN365_BUSCENTRAL_PREMIUM"
            'Dynamics 365 Business Central Team Members'                                                             = "DYN365_BUSCENTRAL_TEAM_MEMBER"
            'Dynamics 365 Commerce Trial'                                                                            = "DYN365_RETAIL_TRIAL"
            'Dynamics 365 Customer Engagement Plan'                                                                  = "DYN365_ENTERPRISE_PLAN1"
            'Dynamics 365 Customer Insights'                                                                         = "DYN365_CUSTOMER_INSIGHTS_BASE"
            'Dynamics 365 Customer Insights Attach'                                                                  = "DYN365_CUSTOMER_INSIGHTS_ATTACH"
            'Dynamics 365 Customer Insights Engagement Insights'                                                     = "DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE"
            'Dynamics 365 Customer Insights Engagement Insights Viral'                                               = "DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE_TRIAL"
            'Dynamics 365 Customer Insights Standalone'                                                              = "DYN365_CUSTOMER_INSIGHTS_BASE"
            'Dynamics 365 Customer Insights Viral Plan'                                                              = "DYN365_CUSTOMER_INSIGHTS_VIRAL"
            'Dynamics 365 Customer Insights vTrial'                                                                  = "DYN365_CUSTOMER_INSIGHTS_VIRAL"
            'Dynamics 365 Customer Service Chat Application Integration'                                             = "DYN365_CS_CHAT_FPA"
            'Dynamics 365 Customer Service Digital Messaging add-on'                                                 = "DYN365_CS_MESSAGING_TPS"
            'Dynamics 365 Customer Service Digital Messaging vTrial'                                                 = "DYN365_CS_MESSAGING_VIRAL_TRIAL"
            'Dynamics 365 Customer Service Enterprise Admin'                                                         = "Dynamics_365_Customer_Service_Enterprise_admin_trial"
            'Dynamics 365 Customer Service Enterprise Viral Trial'                                                   = "Dynamics_365_Customer_Service_Enterprise_viral_trial"
            'Dynamics 365 Customer Service Enterprise vTrial'                                                        = "DYN365_CS_ENTERPRISE_VIRAL_TRIAL"
            'Dynamics 365 Customer Service Insights for CE Plan'                                                     = "D365_CSI_EMBED_CE"
            'Dynamics 365 Customer Service Insights for CS Enterprise'                                               = "D365_CSI_EMBED_CSEnterprise"
            'Dynamics 365 Customer Service Insights Trial'                                                           = "DYN365_AI_SERVICE_INSIGHTS"
            'Dynamics 365 Customer Service Insights vTrial'                                                          = "DYNB365_CSI_VIRAL_TRIAL"
            'Dynamics 365 Customer Service Professional'                                                             = "DYN365_CUSTOMER_SERVICE_PRO"
            'Dynamics 365 Customer Service Voice vTrial'                                                             = "DYN365_CS_VOICE_VIRAL_TRIAL"
            'Dynamics 365 Customer Voice'                                                                            = "DYN365_CUSTOMER_VOICE_BASE"
            'Dynamics 365 Customer Voice Additional Responses'                                                       = "DYN365_CUSTOMER_VOICE_ADDON"
            'Dynamics 365 Customer Voice Base Plan'                                                                  = "Customer_Voice_Base"
            'Dynamics 365 Customer Voice Trial'                                                                      = "FORMS_PRO"
            'Dynamics 365 Customer Voice USL'                                                                        = "Forms_Pro_USL"
            'Dynamics 365 Enterprise Edition - Additional Portal (Qualified Offer)'                                  = "CRM_ONLINE_PORTAL"
            'Dynamics 365 Field Service Enterprise vTrial'                                                           = "DYN365_FS_ENTERPRISE_VIRAL_TRIAL"
            'Dynamics 365 Field Service Viral Trial'                                                                 = "Dynamics_365_Field_Service_Enterprise_viral_trial"
            'Dynamics 365 Field Service, Enterprise Edition - Resource Scheduling Optimization'                      = "CRM_AUTO_ROUTING_ADDON"
            'Dynamics 365 Finance'                                                                                   = "DYN365_FINANCE"
            'Dynamics 365 for Business Central Essentials'                                                           = "DYN365_FINANCIALS_BUSINESS"
            'Dynamics 365 for Case Management'                                                                       = "DYN365_ENTERPRISE_CASE_MANAGEMENT"
            'Dynamics 365 for Case Management Enterprise Edition'                                                    = "DYN365_ENTERPRISE_CASE_MANAGEMENT"
            'Dynamics 365 for Customer Service'                                                                      = "DYN365_ENTERPRISE_CUSTOMER_SERVICE"
            'Dynamics 365 for Customer Service Chat'                                                                 = "DYN365_CS_CHAT"
            'Dynamics 365 for Customer Service Enterprise Attach'                                                    = "D365_CUSTOMER_SERVICE_ENT_ATTACH"
            'Dynamics 365 for Customer Service Enterprise Attach to Qualifying Dynamics 365 Base Offer A'            = "D365_CUSTOMER_SERVICE_ENT_ATTACH"
            'Dynamics 365 for Customer Service Enterprise Edition'                                                   = "DYN365_ENTERPRISE_CUSTOMER_SERVICE"
            'Dynamics 365 for Customer Service Pro'                                                                  = "DYN365_CUSTOMER_SERVICE_PRO"
            'Dynamics 365 for Customer Service Voice Add-in'                                                         = "DYN365_CS_VOICE"
            'Dynamics 365 for Field Service'                                                                         = "DYN365_ENTERPRISE_FIELD_SERVICE"
            'Dynamics 365 for Field Service Attach'                                                                  = "D365_FIELD_SERVICE_ATTACH"
            'Dynamics 365 for Field Service Attach to Qualifying Dynamics 365 Base Offer'                            = "D365_FIELD_SERVICE_ATTACH"
            'Dynamics 365 for Field Service Enterprise Edition'                                                      = "DYN365_ENTERPRISE_FIELD_SERVICE"
            'Dynamics 365 for Finance and Operations Enterprise edition - Regulatory Service'                        = "DYN365_REGULATORY_SERVICE"
            'DYNAMICS 365 FOR FINANCIALS'                                                                            = "POWERAPPS_DYN_APPS"
            'Dynamics 365 for Financials Business Edition'                                                           = "DYN365_FINANCIALS_BUSINESS_SKU"
            'Dynamics 365 for HCM Trial'                                                                             = "Dynamics_365_for_HCM_Trial"
            'Dynamics 365 for Marketing'                                                                             = "DYN365_MARKETING_APP"
            'Dynamics 365 for Marketing 50K Addnl Contacts'                                                          = "DYN365_MARKETING_50K_CONTACT_ADDON"
            'Dynamics 365 for Marketing Additional Application'                                                      = "DYN365_MARKETING_APPLICATION_ADDON"
            'Dynamics 365 for Marketing Additional Non-Prod Application'                                             = "DYN365_MARKETING_SANDBOX_APPLICATION_ADDON"
            'Dynamics 365 for Marketing Addnl Contacts Tier 3'                                                       = "DYN365_MARKETING_CONTACT_ADDON_T3"
            'Dynamics 365 for Marketing Addnl Contacts Tier 5'                                                       = "DYN365_MARKETING_CONTACT_ADDON_T5"
            'Dynamics 365 for Marketing Attach'                                                                      = "DYN365_MARKETING_APP_ATTACH"
            'Dynamics 365 for Marketing Business Edition'                                                            = "DYN365_BUSINESS_MARKETING"
            'Dynamics 365 for Marketing MSE User'                                                                    = "DYN365_MARKETING_MSE_USER"
            'Dynamics 365 for Marketing USL'                                                                         = "D365_MARKETING_USER"
            'Dynamics 365 for Operations Devices'                                                                    = "Dynamics_365_for_OperationsDevices"
            'Dynamics 365 for Operations Enterprise Edition - Sandbox Tier 4:Standard Performance Testing'           = "Dynamics_365_for_Operations_Sandbox_Tier4"
            'Dynamics 365 for Operations non-production multi-box instance for standard acceptance testing (Tier 2)' = "Dynamics_365_for_Operations_Sandbox_Tier2"
            'DYNAMICS 365 FOR OPERATIONS TEAM MEMBERS'                                                               = "DYNAMICS_365_FOR_OPERATIONS_TEAM_MEMBERS"
            'DYNAMICS 365 FOR RETAIL'                                                                                = "Dynamics_365_for_Retail"
            'Dynamics 365 for Retail Device'                                                                         = "DYN365_RETAIL_DEVICE"
            'DYNAMICS 365 FOR RETAIL TEAM MEMBERS'                                                                   = "Dynamics_365_for_Retail_Team_members"
            'Dynamics 365 for Retail Trial'                                                                          = "DYN365_RETAIL_TRIAL"
            'DYNAMICS 365 FOR SALES'                                                                                 = "DYN365_ENTERPRISE_SALES"
            'Dynamics 365 for Sales and Customer Service Enterprise Edition'                                         = "DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE"
            'Dynamics 365 for Sales Enterprise Attach'                                                               = "D365_SALES_ENT_ATTACH"
            'Dynamics 365 for Sales Enterprise Edition'                                                              = "DYN365_ENTERPRISE_SALES"
            'Dynamics 365 for Sales Pro Attach'                                                                      = "D365_SALES_PRO_ATTACH"
            'Dynamics 365 For Sales Professional'                                                                    = "D365_SALES_PRO"
            'Dynamics 365 For Sales Professional Trial'                                                              = "D365_SALES_PRO_IW"
            'Dynamics 365 for Supply Chain Management'                                                               = "DYN365_SCM"
            'Dynamics 365 for Talent'                                                                                = "SKU_Dynamics_365_for_HCM_Trial"
            'DYNAMICS 365 FOR TALENT - ATTRACT EXPERIENCE TEAM MEMBER'                                               = "DYN365_Enterprise_Talent_Attract_TeamMember"
            'DYNAMICS 365 FOR TALENT - ONBOARD EXPERIENCE'                                                           = "DYN365_Enterprise_Talent_Onboard_TeamMember"
            'DYNAMICS 365 FOR TALENT TEAM MEMBERS'                                                                   = "Dynamics_365_for_Talent_Team_members"
            'Dynamics 365 for Talent: Attract'                                                                       = "Dynamics_365_Hiring_Free_PLAN"
            'Dynamics 365 for Talent: Onboard'                                                                       = "Dynamics_365_Onboarding_Free_PLAN"
            'Dynamics 365 for Team Members'                                                                          = "DYN365_FINANCIALS_TEAM_MEMBERS"
            'Dynamics 365 for Team Members Enterprise Edition'                                                       = "DYN365_ENTERPRISE_TEAM_MEMBERS"
            'DYNAMICS 365 FOR_OPERATIONS'                                                                            = "Dynamics_365_for_Operations"
            'Dynamics 365 Guides'                                                                                    = "GUIDES_USER"
            'DYNAMICS 365 HIRING FREE PLAN'                                                                          = "DYNAMICS_365_HIRING_FREE_PLAN"
            'Dynamics 365 Hybrid Connector'                                                                          = "CRM_HYBRIDCONNECTOR"
            'Dynamics 365 Marketing'                                                                                 = "DYN365_BUSINESS_Marketing"
            'Dynamics 365 Marketing Sandbox Application AddOn'                                                       = "DYN365_MARKETING_SANDBOX_APPLICATION_ADDON"
            'Dynamics 365 Operations - Device'                                                                       = "Dynamics_365_for_Operations_Devices"
            'Dynamics 365 Operations - Sandbox Tier 2:Standard Acceptance Testing'                                   = "Dynamics_365_for_Operations_Sandbox_Tier2_SKU"
            'Dynamics 365 Operations - Sandbox Tier 4:Standard Performance Testing'                                  = "Dynamics_365_for_Operations_Sandbox_Tier4_SKU"
            'Dynamics 365 Operations Trial Environment'                                                              = "ERP_TRIAL_INSTANCE"
            'Dynamics 365 P1'                                                                                        = "DYN365_ENTERPRISE_P1"
            'Dynamics 365 P1 Tria for Information Workers'                                                           = "DYN365_ENTERPRISE_P1_IW"
            'DYNAMICS 365 P1 TRIAL FOR INFORMATION WORKERS'                                                          = "DYN365_ENTERPRISE_P1_IW"
            'Dynamics 365 Project Operations'                                                                        = "D365_ProjectOperations"
            'Dynamics 365 Project Operations CDS'                                                                    = "D365_ProjectOperationsCDS"
            'Dynamics 365 Regulatory Service - Enterprise Edition Trial'                                             = "DYN365_REGULATORY_SERVICE"
            'Dynamics 365 Remote Assist'                                                                             = "MICROSOFT_REMOTE_ASSIST"
            'Dynamics 365 Remote Assist HoloLens'                                                                    = "MICROSOFT_REMOTE_ASSIST_HOLOLENS"
            'Dynamics 365 Sales Enterprise Attach to Qualifying Dynamics 365 Base Offer'                             = "D365_SALES_ENT_ATTACH"
            'Dynamics 365 Sales Enterprise vTrial'                                                                   = "DYN365_SALES_ENTERPRISE_VIRAL_TRIAL"
            'Dynamics 365 Sales Insights vTrial'                                                                     = "DYN365_SALES_INSIGHTS_VIRAL_TRIAL"
            'Dynamics 365 Sales Premium'                                                                             = "DYN365_SALES_PREMIUM"
            'Dynamics 365 Sales Premium Viral Trial'                                                                 = "Dynamics_365_Sales_Premium_Viral_Trial"
            'Dynamics 365 Sales Professional Attach to Qualifying Dynamics 365 Base Offer'                           = "D365_SALES_PRO_ATTACH"
            'Dynamics 365 Sales, Field Service and Customer Service Partner Sandbox'                                 = "Dynamics_365_Sales_Field_Service_and_Customer_Service_Partner_Sandbox"
            'Dynamics 365 Talent: Attract'                                                                           = "Dynamics_365_Hiring_SKU"
            'Dynamics 365 Talent: Onboard'                                                                           = "DYNAMICS_365_ONBOARDING_SKU"
            'Dynamics 365 Team Members'                                                                              = "DYN365_TEAM_MEMBERS"
            'Dynamics 365 UNF OPS Plan ENT Edition'                                                                  = "Dynamics_365_for_Operations"
            'Dynamics Customer Voice Add-On'                                                                         = "CUSTOMER_VOICE_ADDON"
            'Education Analytics'                                                                                    = "EducationAnalyticsP1"
            'Enterprise Mobility + Security A3 for Faculty'                                                          = "EMS_EDU_FACULTY"
            'Enterprise Mobility + Security E3'                                                                      = "EMS"
            'Enterprise Mobility + Security E5'                                                                      = "EMSPREMIUM"
            'Enterprise Mobility + Security G3 GCC'                                                                  = "EMS_GOV"
            'Enterprise Mobility + Security G5 GCC'                                                                  = "EMSPREMIUM_GOV"
            'EQUIVIO_ANALYTICS_GOV'                                                                                  = "EQUIVIO_ANALYTICS_GOV"
            'Exchange Enterprise CAL Services (EOP DLP)'                                                             = "EOP_ENTERPRISE_PREMIUM"
            'EXCHANGE ESSENTIALS'                                                                                    = "EXCHANGE_S_ESSENTIALS"
            'Exchange Foundation'                                                                                    = "EXCHANGE_S_FOUNDATION"
            'Exchange Foundation for Government'                                                                     = "EXCHANGE_S_FOUNDATION_GOV"
            'Exchange Online (Kiosk) for Government'                                                                 = "EXCHANGE_S_DESKLESS_GOV"
            'EXCHANGE ONLINE (P1)'                                                                                   = "EXCHANGE_L_STANDARD"
            'Exchange Online (Plan 1)'                                                                               = "EXCHANGESTANDARD"
            'Exchange Online (Plan 1) for Alumni with Yammer'                                                        = "EXCHANGESTANDARD_ALUMNI"
            'Exchange Online (Plan 1) for GCC'                                                                       = "EXCHANGESTANDARD_GOV"
            'Exchange Online (Plan 1) for Government'                                                                = "EXCHANGE_S_STANDARD_GOV"
            'Exchange Online (Plan 1) for Students'                                                                  = "EXCHANGESTANDARD_STUDENT"
            'Exchange Online (Plan 2)'                                                                               = "EXCHANGEENTERPRISE"
            'Exchange Online (Plan 2) for Faculty'                                                                   = "EXCHANGEENTERPRISE_FACULTY"
            'Exchange Online (Plan 2) for Government'                                                                = "EXCHANGE_S_ENTERPRISE_GOV"
            'Exchange Online Archiving'                                                                              = "EXCHANGE_S_ARCHIVE_ADDON"
            'Exchange Online Archiving for Exchange Online'                                                          = "EXCHANGEARCHIVE_ADDON"
            'Exchange Online Archiving for Exchange Server'                                                          = "EXCHANGEARCHIVE"
            'Exchange Online Essentials'                                                                             = "EXCHANGE_S_ESSENTIALS"
            'Exchange Online Essentials (ExO P1 Based)'                                                              = "EXCHANGEESSENTIALS"
            'Exchange Online Kiosk'                                                                                  = "EXCHANGEDESKLESS"
            'Exchange Online Multi-Geo'                                                                              = "EXCHANGEONLINE_MULTIGEO"
            'EXCHANGE ONLINE PLAN'                                                                                   = "EXCHANGE_S_STANDARD_MIDMARKET"
            'Exchange Online POP'                                                                                    = "EXCHANGETELCO"
            'Exchange Online Protection'                                                                             = "EOP_ENTERPRISE"
            'EXCHANGE PLAN 2G'                                                                                       = "EXCHANGE_S_ENTERPRISE_GOV"
            'EXCHANGE_ANALYTICS_GOV'                                                                                 = "EXCHANGE_ANALYTICS_GOV"
            'EXCHANGE_S_DESKLESS'                                                                                    = "EXCHANGE_S_DESKLESS"
            'EXCHANGE_S_ENTERPRISE_GOV'                                                                              = "EXCHANGE_S_ENTERPRISE_GOV"
            'EXCHANGE_S_FOUNDATION'                                                                                  = "EXCHANGE_S_FOUNDATION"
            'EXCHANGE_S_FOUNDATION_GOV'                                                                              = "EXCHANGE_S_FOUNDATION_GOV"
            'Field Service � Automated Routing Engine Add-On'                                                        = "CRM_AUTO_ROUTING_ENGINE_ADDON"
            'Flow for CCI Bots'                                                                                      = "FLOW_CCI_BOTS"
            'Flow for Developer'                                                                                     = "FLOW_DEV_VIRAL"
            'FLOW FOR DYNAMICS 36'                                                                                   = "FLOW_DYN_P2"
            'Flow for Dynamics 365'                                                                                  = "FLOW_DYN_APPS"
            'FLOW FOR OFFICE 365'                                                                                    = "FLOW_O365_P1"
            'Flow for Project'                                                                                       = "FLOW_FOR_PROJECT"
            'Flow Free'                                                                                              = "FLOW_P2_VIRAL"
            'Flow P2 Viral'                                                                                          = "FLOW_P2_VIRAL_REAL"
            'Flow per app baseline access'                                                                           = "Flow_Per_APP_IWTRIAL"
            'Flow per business process plan'                                                                         = "FLOW_BUSINESS_PROCESS"
            'Flow per user plan'                                                                                     = "FLOW_PER_USER"
            'FLOW_O365_P3_GOV'                                                                                       = "FLOW_O365_P3_GOV"
            'Forms for Government (Plan E1)'                                                                         = "FORMS_GOV_E1"
            'FORMS FOR GOVERNMENT (PLAN E3)'                                                                         = "FORMS_GOV_E3"
            'Forms for Government (Plan F1)'                                                                         = "FORMS_GOV_F1"
            'FORMS_GOV_E5'                                                                                           = "FORMS_GOV_E5"
            'Graph Connectors Search with Index'                                                                     = "GRAPH_CONNECTORS_SEARCH_INDEX"
            'Graph Connectors Search with Index (Microsoft Viva Topics)'                                             = "GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP"
            'Graph Connectors Search with Index (Viva Topics)'                                                       = "GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP"
            'INFO_GOVERNANCE'                                                                                        = "INFO_GOVERNANCE"
            'Information Barriers'                                                                                   = "INFORMATION_BARRIERS"
            'Information Protection and Governance Analytics � Premium'                                              = "Content_Explorer"
            'Information Protection and Governance Analytics - Premium'                                              = "Content_Explorer"
            'Information Protection and Governance Analytics - Premium)'                                             = "Content_Explorer"
            'Information Protection and Governance Analytics � Standard'                                             = "ContentExplorer_Standard"
            'Information Protection and Governance Analytics - Standard'                                             = "ContentExplorer_Standard"
            'Information Protection and Governance Analytics -Premium'                                               = "Content_Explorer"
            'Information Protection for Office 365 - Premium'                                                        = "MIP_S_CLP2"
            'Information Protection for Office 365 - Standard'                                                       = "MIP_S_CLP1"
            'INFORMATION_BARRIERS'                                                                                   = "INFORMATION_BARRIERS"
            'Insights by MyAnalytics'                                                                                = "MYANALYTICS_P2"
            'INSIGHTS BY MYANALYTICS FOR GOVERNMENT'                                                                 = "MYANALYTICS_P2_GOV"
            'Intune'                                                                                                 = "INTUNE_A"
            'Intune Advanced endpoint analytics'                                                                     = "Intune_AdvancedEA"
            'Intune Endpoint Privilege Management'                                                                   = "Intune-EPM"
            'Intune for Education'                                                                                   = "INTUNE_EDU"
            'Intune Plan 2'                                                                                          = "INTUNE_P2"
            'INTUNE_A'                                                                                               = "INTUNE_A"
            'INTUNE_O365'                                                                                            = "INTUNE_O365"
            'IoT Intelligence Add-in Additional Machines'                                                            = "D365_IOTFORSCM_ADDITIONAL"
            'Iot Intelligence Add-in for D365 Supply Chain Management'                                               = "D365_IOTFORSCM"
            'LOCKBOX_ENTERPRISE_GOV'                                                                                 = "LOCKBOX_ENTERPRISE_GOV"
            'LOGIC FLOWS'                                                                                            = "POWERFLOWSFREE"
            'M365 Communication Compliance'                                                                          = "MICROSOFT_COMMUNICATION_COMPLIANCE"
            'M365_ADVANCED_AUDITING'                                                                                 = "M365_ADVANCED_AUDITING"
            'MCO FREE FOR MICROSOFT TEAMS (FREE)'                                                                    = "MCOFREE"
            'MCOEV_GOV'                                                                                              = "MCOEV_GOV"
            'MCOIMP'                                                                                                 = "MCOIMP"
            'MCOMEETADV_GOV'                                                                                         = "MCOMEETADV_GOV"
            'MCOPSTN3'                                                                                               = "MCOPSTN3"
            'MCOSTANDARD_GOV'                                                                                        = "MCOSTANDARD_GOV"
            'MCS - BizApps_Cloud for Sustainability_vTrial'                                                          = "MCS_BizApps_Cloud_for_Sustainability_vTrial"
            'MDE_SecurityManagement'                                                                                 = "Intune_Defender"
            'Meeting Room Managed Services'                                                                          = "MMR_P1"
            'MFA_PREMIUM'                                                                                            = "MFA_PREMIUM"
            'Microsoft 365 A1'                                                                                       = "M365EDU_A1"
            'Microsoft 365 A3 - Unattended License for students use benefit'                                         = "M365EDU_A3_STUUSEBNFT_RPA1"
            'Microsoft 365 A3 for Faculty'                                                                           = "M365EDU_A3_FACULTY"
            'Microsoft 365 A3 for Students'                                                                          = "M365EDU_A3_STUDENT"
            'Microsoft 365 A3 for students use benefit'                                                              = "M365EDU_A3_STUUSEBNFT"
            'Microsoft 365 A3 Suite features for faculty'                                                            = "Microsoft 365 A3 Suite features for faculty"
            'Microsoft 365 A5 for Faculty'                                                                           = "M365EDU_A5_FACULTY"
            'Microsoft 365 A5 for Students'                                                                          = "M365EDU_A5_STUDENT"
            'Microsoft 365 A5 for students use benefit'                                                              = "M365EDU_A5_STUUSEBNFT"
            'Microsoft 365 A5 Suite features for faculty'                                                            = "M365_A5_SUITE_COMPONENTS_FACULTY"
            'Microsoft 365 A5 without Audio Conferencing for students use benefit'                                   = "M365EDU_A5_NOPSTNCONF_STUUSEBNFT"
            'Microsoft 365 Advanced Auditing'                                                                        = "M365_ADVANCED_AUDITING"
            'Microsoft 365 Advanced Communications'                                                                  = "TEAMS_ADVCOMMS"
            'Microsoft 365 Apps for Business'                                                                        = "SMB_BUSINESS"
            'Microsoft 365 Apps for Enterprise'                                                                      = "OFFICESUBSCRIPTION"
            'Microsoft 365 Apps for enterprise (device)'                                                             = "OFFICE_PROPLUS_DEVICE1"
            'Microsoft 365 Apps for Enterprise (Unattended)'                                                         = "OFFICESUBSCRIPTION_unattended"
            'Microsoft 365 Apps for enterprise G'                                                                    = "OFFICESUBSCRIPTION_GOV"
            'Microsoft 365 Apps for Faculty'                                                                         = "OFFICESUBSCRIPTION_FACULTY"
            'Microsoft 365 Apps for Students'                                                                        = "OFFICESUBSCRIPTION_STUDENT"
            'Microsoft 365 Audio Conferencing'                                                                       = "MCOMEETADV"
            'Microsoft 365 Audio Conferencing for GCC'                                                               = "MCOMEETADV_GOV"
            'MICROSOFT 365 AUDIO CONFERENCING FOR GOVERNMENT'                                                        = "MCOMEETADV_GOV"
            'Microsoft 365 Audio Conferencing Pay-Per-Minute'                                                        = "MCOMEETACPEA"
            'Microsoft 365 Audio Conferencing Pay-Per-Minute - EA'                                                   = "MCOMEETACPEA"
            'Microsoft 365 Audit Platform'                                                                           = "M365_AUDIT_PLATFORM"
            'Microsoft 365 Business Basic'                                                                           = "SMB_BUSINESS_ESSENTIALS"
            'Microsoft 365 Business Premium'                                                                         = "SPB"
            'Microsoft 365 Business Standard'                                                                        = "O365_BUSINESS_PREMIUM"
            'Microsoft 365 Business Standard - Prepaid Legacy'                                                       = "SMB_BUSINESS_PREMIUM"
            'Microsoft 365 Communication Compliance'                                                                 = "MICROSOFT_COMMUNICATION_COMPLIANCE"
            'Microsoft 365 Defender'                                                                                 = "MTP"
            'Microsoft 365 Domestic Calling Plan'                                                                    = "MCOPSTN1"
            'MICROSOFT 365 DOMESTIC CALLING PLAN (120 min)'                                                          = "MCOPSTN5"
            'Microsoft 365 Domestic Calling Plan (120 min) at User Level'                                            = "MCOPSTN8"
            'Microsoft 365 Domestic Calling Plan (120 Minutes)'                                                      = "MCOPSTN_5"
            'Microsoft 365 Domestic Calling Plan for GCC'                                                            = "MCOPSTN_1_GOV"
            'Microsoft 365 E3'                                                                                       = "SPE_E3"
            'Microsoft 365 E3 - Unattended License'                                                                  = "SPE_E3_RPA1"
            'Microsoft 365 E3 (500 seats min)_HUB'                                                                   = "Microsoft_365_E3"
            'Microsoft 365 E3 Extra Features'                                                                        = "Microsoft_365_E3_Extra_Features"
            'Microsoft 365 E3_USGOV_DOD'                                                                             = "SPE_E3_USGOV_DOD"
            'Microsoft 365 E3_USGOV_GCCHIGH'                                                                         = "SPE_E3_USGOV_GCCHIGH"
            'Microsoft 365 E5'                                                                                       = "SPE_E5"
            'Microsoft 365 E5 (500 seats min)_HUB'                                                                   = "Microsoft_365_E5"
            'Microsoft 365 E5 Compliance'                                                                            = "INFORMATION_PROTECTION_COMPLIANCE"
            'Microsoft 365 E5 Developer (without Windows and Audio Conferencing)'                                    = "DEVELOPERPACK_E5"
            'Microsoft 365 E5 Security'                                                                              = "IDENTITY_THREAT_PROTECTION"
            'Microsoft 365 E5 Security for EMS E5'                                                                   = "IDENTITY_THREAT_PROTECTION_FOR_EMS_E5"
            'Microsoft 365 E5 Suite features'                                                                        = "M365_E5_SUITE_COMPONENTS"
            'Microsoft 365 E5 with Calling Minutes'                                                                  = "SPE_E5_CALLINGMINUTES"
            'Microsoft 365 E5 without Audio Conferencing'                                                            = "SPE_E5_NOPSTNCONF"
            'Microsoft 365 E5 without Audio Conferencing (500 seats min)_HUB'                                        = "Microsoft_365_E5_without_Audio_Conferencing"
            'Microsoft 365 F1'                                                                                       = "M365_F1_COMM"
            'Microsoft 365 F3'                                                                                       = "SPE_F1"
            'Microsoft 365 F3 GCC'                                                                                   = "M365_F1_GOV"
            'Microsoft 365 F5 Compliance Add-on'                                                                     = "SPE_F5_COMP"
            'Microsoft 365 F5 Compliance Add-on AR (DOD)_USGOV_DOD'                                                  = "SPE_F5_COMP_AR_D_USGOV_DOD"
            'Microsoft 365 F5 Compliance Add-on AR_USGOV_GCCHIGH'                                                    = "SPE_F5_COMP_AR_USGOV_GCCHIGH"
            'Microsoft 365 F5 Compliance Add-on GCC'                                                                 = "SPE_F5_COMP_GCC"
            'Microsoft 365 F5 Security + Compliance Add-on'                                                          = "SPE_F5_SECCOMP"
            'Microsoft 365 F5 Security Add-on'                                                                       = "SPE_F5_SEC"
            'Microsoft 365 G3 GCC'                                                                                   = "M365_G3_GOV"
            'Microsoft 365 GCC G5'                                                                                   = "M365_G5_GCC"
            'Microsoft 365 Lighthouse'                                                                               = "Microsoft365_Lighthouse"
            'Microsoft 365 Lighthouse (Plan 1)'                                                                      = "M365_LIGHTHOUSE_CUSTOMER_PLAN1"
            'Microsoft 365 Lighthouse (Plan 2)'                                                                      = "M365_LIGHTHOUSE_PARTNER_PLAN1"
            'Microsoft 365 Phone Standard Resource Account'                                                          = "MCOEV_VIRTUALUSER"
            'Microsoft 365 Phone Standard Resource Account for Government'                                           = "MCOEV_VIRTUALUSER_GOV"
            'MICROSOFT 365 PHONE SYSTE'                                                                              = "MCOEV"
            'Microsoft 365 Phone System'                                                                             = "MCOEV"
            'Microsoft 365 Phone System for Government'                                                              = "MCOEV_GOV"
            'Microsoft 365 Security and Compliance for Firstline Workers'                                            = "M365_SECURITY_COMPLIANCE_FOR_FLW"
            'Microsoft Application Protection and Governance (A)'                                                    = "MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_A"
            'Microsoft Application Protection and Governance (D)'                                                    = "MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_D"
            'MICROSOFT AZURE ACTIVE DIRECTORY BASIC'                                                                 = "AAD_BASIC"
            'MICROSOFT AZURE ACTIVE DIRECTORY RIGHTS'                                                                = "RMS_S_PREMIUM"
            'Microsoft Azure Multi-Factor Authentication'                                                            = "MFA_STANDALONE"
            'Microsoft Azure Rights Management Service'                                                              = "RMS_S_BASIC"
            'Microsoft Bookings'                                                                                     = "MICROSOFTBOOKINGS"
            'Microsoft Business Center'                                                                              = "MICROSOFT_BUSINESS_CENTER"
            'Microsoft Cloud App Security'                                                                           = "ADALLOM_STANDALONE"
            'Microsoft Cloud for Sustainability vTrial'                                                              = "Microsoft_Cloud_for_Sustainability_vTrial"
            'Microsoft Communications Compliance'                                                                    = "COMMUNICATIONS_COMPLIANCE"
            'Microsoft Communications DLP'                                                                           = "COMMUNICATIONS_DLP"
            'Microsoft Customer Key'                                                                                 = "CUSTOMER_KEY"
            'Microsoft Data Investigations'                                                                          = "DATA_INVESTIGATIONS"
            'Microsoft Defender for Business'                                                                        = "MDE_SMB"
            'Microsoft Defender for Cloud Apps'                                                                      = "ADALLOM_S_STANDALONE"
            'Microsoft Defender for Cloud Apps Discovery'                                                            = "ADALLOM_S_DISCOVERY"
            'Microsoft Defender for Cloud Apps for DOD'                                                              = "ADALLOM_S_STANDALONE_DOD"
            'Microsoft Defender for Endpoint'                                                                        = "WIN_DEF_ATP"
            'Microsoft Defender for Endpoint P1'                                                                     = "DEFENDER_ENDPOINT_P1"
            'Microsoft Defender for Endpoint P1 for EDU'                                                             = "DEFENDER_ENDPOINT_P1_EDU"
            'Microsoft Defender for Endpoint P2_XPLAT'                                                               = "MDATP_XPLAT"
            'Microsoft Defender for Endpoint Plan 1'                                                                 = "MDE_LITE"
            'Microsoft Defender for Endpoint Server'                                                                 = "MDATP_Server"
            'Microsoft Defender for Identity'                                                                        = "ATA"
            'Microsoft Defender for Office 365 (Plan 1)'                                                             = "ATP_ENTERPRISE"
            'Microsoft Defender for Office 365 (Plan 1) Faculty'                                                     = "ATP_ENTERPRISE_FACULTY"
            'Microsoft Defender for Office 365 (Plan 1) for Government'                                              = "ATP_ENTERPRISE_GOV"
            'Microsoft Defender for Office 365 (Plan 1) GCC'                                                         = "ATP_ENTERPRISE_GOV"
            'Microsoft Defender for Office 365 (Plan 2)'                                                             = "THREAT_INTELLIGENCE"
            'Microsoft Defender for Office 365 (Plan 2) for Government'                                              = "THREAT_INTELLIGENCE_GOV"
            'Microsoft Defender for Office 365 (Plan 2) GCC'                                                         = "THREAT_INTELLIGENCE_GOV"
            'Microsoft Defender Vulnerability Management'                                                            = "TVM_Premium_Standalone"
            'Microsoft Defender Vulnerability Management Add-on'                                                     = "TVM_Premium_Add_on"
            'Microsoft Dynamics 365 Customer Voice Add-on'                                                           = "Forms_Pro_AddOn"
            'Microsoft Dynamics 365 Customer Voice for Customer Engagement Plan'                                     = "Forms_Pro_CE"
            'Microsoft Dynamics 365 Customer Voice for Customer Insights'                                            = "Forms_Pro_Customer_Insights"
            'Microsoft Dynamics 365 Customer Voice for Customer Insights App'                                        = "Customer_Voice_Customer_Insights"
            'Microsoft Dynamics 365 Customer Voice for Customer Service Enterprise'                                  = "Forms_Pro_Service"
            'Microsoft Dynamics 365 Customer Voice for Field Service'                                                = "Forms_Pro_FS"
            'Microsoft Dynamics 365 Customer Voice for Marketing'                                                    = "Forms_Pro_Marketing"
            'Microsoft Dynamics 365 Customer Voice for Marketing Application'                                        = "Forms_Pro_Marketing_App"
            'Microsoft Dynamics 365 Customer Voice for Relationship Sales'                                           = "Forms_Pro_Relationship_Sales"
            'Microsoft Dynamics 365 Customer Voice for Sales Enterprise'                                             = "Forms_Pro_SalesEnt"
            'Microsoft Dynamics 365 Customer Voice USL'                                                              = "Forms_Pro_USL"
            'Microsoft Dynamics 365 for Finance'                                                                     = "D365_Finance"
            'Microsoft Dynamics AX7 User Trial'                                                                      = "AX7_USER_TRIAL"
            'Microsoft Dynamics CRM Online'                                                                          = "CRMSTANDARD"
            'Microsoft Dynamics CRM Online - Portal Add-On'                                                          = "CRM_ONLINE_PORTAL"
            'Microsoft Dynamics CRM Online Additional Test Instance'                                                 = "CRMTESTINSTANCE"
            'Microsoft Dynamics CRM Online Basic'                                                                    = "CRMPLAN2"
            'Microsoft Dynamics CRM Online Instance'                                                                 = "CRMINSTANCE"
            'MICROSOFT DYNAMICS CRM ONLINE PROFESSIONA'                                                              = "CRMSTANDARD"
            'Microsoft Dynamics CRM Online Storage Add-On'                                                           = "CRMSTORAGE"
            'MICROSOFT DYNAMICS MARKETING SALES COLLABORATION - ELIGIBILITY CRITERIA APPLY'                          = "MDM_SALES_COLLABORATION"
            'Microsoft eCDN'                                                                                         = "MICROSOFT_ECDN"
            'Microsoft Endpoint DLP'                                                                                 = "MICROSOFTENDPOINTDLP"
            'Microsoft Excel Advanced Analytics'                                                                     = "EXCEL_PREMIUM"
            'Microsoft Fabric (Free)'                                                                                = "POWER_BI_STANDARD"
            'Microsoft Fabric (Free) for faculty'                                                                    = "POWER_BI_STANDARD_FACULTY"
            'Microsoft Fabric (Free) for student'                                                                    = "POWER_BI_STANDARD_STUDENT"
            'Microsoft Forms (Plan 2)'                                                                               = "OFFICE_FORMS_PLAN_2"
            'Microsoft Forms (Plan 3)'                                                                               = "OFFICE_FORMS_PLAN_3"
            'MICROSOFT FORMS (PLAN E1)'                                                                              = "FORMS_PLAN_E1"
            'Microsoft Forms (Plan E3)'                                                                              = "FORMS_PLAN_E3"
            'Microsoft Forms (Plan E5)'                                                                              = "FORMS_PLAN_E5"
            'Microsoft Forms (Plan F1)'                                                                              = "FORMS_PLAN_K"
            'Microsoft Forms for Government (Plan E5)'                                                               = "FORMS_GOV_E5"
            'Microsoft Imagine Academy'                                                                              = "IT_ACADEMY_AD"
            'Microsoft Information Governance'                                                                       = "INFO_GOVERNANCE"
            'Microsoft Insider Risk Management'                                                                      = "INSIDER_RISK"
            'Microsoft Intune'                                                                                       = "INTUNE_A"
            'Microsoft Intune Device'                                                                                = "INTUNE_A_D"
            'Microsoft Intune Device for Government'                                                                 = "INTUNE_A_D_GOV"
            'Microsoft Intune for Education'                                                                         = "INTUNE_EDU"
            'Microsoft Intune Plan 1'                                                                                = "INTUNE_A"
            'Microsoft Intune Plan 1 for Education'                                                                  = "INTUNE_EDU"
            'Microsoft Intune SMB'                                                                                   = "INTUNE_SMB"
            'Microsoft Intune Suite'                                                                                 = "Microsoft_Intune_Suite"
            'Microsoft Invoicing'                                                                                    = "DYN365BC_MS_INVOICING"
            'Microsoft Kaizala'                                                                                      = "KAIZALA_STANDALONE"
            'Microsoft Kaizala Pro'                                                                                  = "KAIZALA_O365_P3"
            'Microsoft Kaizala Pro Plan 1'                                                                           = "KAIZALA_O365_P1"
            'Microsoft Kaizala Pro Plan 2'                                                                           = "KAIZALA_O365_P2"
            'Microsoft Kaizala Pro Plan 3'                                                                           = "KAIZALA_O365_P3"
            'Microsoft ML-Based Classification'                                                                      = "ML_CLASSIFICATION"
            'Microsoft MyAnalytics (Full)'                                                                           = "EXCHANGE_ANALYTICS"
            'Microsoft MyAnalytics for Government (Full)'                                                            = "EXCHANGE_ANALYTICS_GOV"
            'MICROSOFT PLANNE'                                                                                       = "PROJECTWORKMANAGEMENT"
            'Microsoft Planner'                                                                                      = "PROJECTWORKMANAGEMENT"
            'Microsoft Power Apps for Developer'                                                                     = "POWERAPPS_DEV"
            'Microsoft Power Apps Plan 2 (Qualified Offer)'                                                          = "POWERFLOW_P2"
            'Microsoft Power Apps Plan 2 Trial'                                                                      = "POWERAPPS_VIRAL"
            'Microsoft Power Automate Free'                                                                          = "FLOW_FREE"
            'Microsoft Power Automate Plan 2'                                                                        = "FLOW_P2"
            'MICROSOFT POWER BI INFORMATION SERVICES PLAN'                                                           = "SQL_IS_SSIM"
            'Microsoft Power BI Information Services Plan 1'                                                         = "SQL_IS_SSIM"
            'Microsoft Power BI Reporting and Analytics Plan 1'                                                      = "BI_AZURE_P1"
            'MICROSOFT POWER VIDEOS BASIC'                                                                           = "POWERVIDEOSFREE"
            'MICROSOFT POWERAPPS'                                                                                    = "POWERAPPSFREE"
            'Microsoft Records Management'                                                                           = "RECORDS_MANAGEMENT"
            'Microsoft Relationship Sales solution'                                                                  = "DYN365_ ENTERPRISE _RELATIONSHIP_SALES"
            'Microsoft Remote Assist'                                                                                = "MICROSOFT_REMOTE_ASSIST"
            'Microsoft Search'                                                                                       = "MICROSOFT_SEARCH"
            'MICROSOFT SOCIAL ENGAGEMENT - SERVICE DISCONTINUATION'                                                  = "DYN365_ENTERPRISE_CUSTOMER_SERVICE"
            'Microsoft Social Engagement Enterprise'                                                                 = "NBENTERPRISE"
            'MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY'                                  = "NBPROFESSIONALFORCRM"
            'Microsoft StaffHub'                                                                                     = "Deskless"
            'Microsoft Stream'                                                                                       = "STREAM"
            'MICROSOFT STREAM FOR O365 E1 SKU'                                                                       = "STREAM_O365_E1"
            'Microsoft Stream for O365 E3 SKU'                                                                       = "STREAM_O365_E3"
            'MICROSOFT STREAM FOR O365 E5 SKU'                                                                       = "STREAM_O365_E5"
            'Microsoft Stream for O365 for Government (E1)'                                                          = "STREAM_O365_E1_GOV"
            'MICROSOFT STREAM FOR O365 FOR GOVERNMENT (E3)'                                                          = "STREAM_O365_E3_GOV"
            'Microsoft Stream for O365 for Government (F1)'                                                          = "STREAM_O365_K_GOV"
            'Microsoft Stream for O365 K SKU'                                                                        = "STREAM_O365_K"
            'Microsoft Stream for Office 365 E1'                                                                     = "STREAM_O365_E1"
            'Microsoft Stream for Office 365 E3'                                                                     = "STREAM_O365_E3"
            'Microsoft Stream for Office 365 E5'                                                                     = "STREAM_O365_E5"
            'Microsoft Stream for Office 365 F3'                                                                     = "STREAM_O365_K"
            'Microsoft Stream Plan 2'                                                                                = "STREAM_P2"
            'Microsoft Stream Storage Add-On'                                                                        = "STREAM_STORAGE"
            'Microsoft Stream Storage Add-On (500 GB)'                                                               = "STREAM_STORAGE"
            'Microsoft Teams'                                                                                        = "TEAMS1"
            'Microsoft Teams (Free)'                                                                                 = "TEAMS_FREE"
            'Microsoft Teams Audio Conferencing with dial-out to select geographies'                                 = "MCOMEETBASIC"
            'Microsoft Teams Audio Conferencing with dial-out to USA/CAN'                                            = "Microsoft_Teams_Audio_Conferencing_select_dial_out"
            'Microsoft Teams Commercial Cloud'                                                                       = "TEAMS_COMMERCIAL_TRIAL"
            'Microsoft Teams Essentials'                                                                             = "Teams_Ess"
            'Microsoft Teams Essentials (AAD Identity)'                                                              = "TEAMS_ESSENTIALS_AAD"
            'Microsoft Teams Exploratory'                                                                            = "TEAMS_EXPLORATORY"
            'Microsoft Teams for DOD (AR)'                                                                           = "TEAMS_AR_DOD"
            'Microsoft Teams for GCCHigh (AR)'                                                                       = "TEAMS_AR_GCCHIGH"
            'Microsoft Teams for Government'                                                                         = "TEAMS_GOV"
            'Microsoft Teams Phone Resource Account'                                                                 = "PHONESYSTEM_VIRTUALUSER"
            'Microsoft Teams Phone Resource Account for GCC'                                                         = "PHONESYSTEM_VIRTUALUSER_GOV"
            'Microsoft Teams Phone Standard'                                                                         = "MCOEV"
            'Microsoft Teams Phone Standard for DOD'                                                                 = "MCOEV_DOD"
            'Microsoft Teams Phone Standard for Faculty'                                                             = "MCOEV_FACULTY"
            'Microsoft Teams Phone Standard for GCC'                                                                 = "MCOEV_GOV"
            'Microsoft Teams Phone Standard for GCCHIGH'                                                             = "MCOEV_GCCHIGH"
            'Microsoft Teams Phone Standard for Small and Medium Business'                                           = "MCOEVSMB_1"
            'Microsoft Teams Phone Standard for Students'                                                            = "MCOEV_STUDENT"
            'Microsoft Teams Phone Standard for TELSTRA'                                                             = "MCOEV_TELSTRA"
            'Microsoft Teams Phone Standard_USGOV_DOD'                                                               = "MCOEV_USGOV_DOD"
            'Microsoft Teams Phone Standard_USGOV_GCCHIGH'                                                           = "MCOEV_USGOV_GCCHIGH"
            'Microsoft Teams Premium Intelligent'                                                                    = "TEAMSPRO_MGMT"
            'Microsoft Teams Premium Introductory Pricing'                                                           = "Microsoft_Teams_Premium"
            'Microsoft Teams Premium Personalized'                                                                   = "TEAMSPRO_CUST"
            'Microsoft Teams Premium Secure'                                                                         = "TEAMSPRO_PROTECTION"
            'Microsoft Teams Premium Virtual Appointment'                                                            = "TEAMSPRO_VIRTUALAPPT"
            'Microsoft Teams Premium Virtual Appointments'                                                           = "MCO_VIRTUAL_APPT"
            'Microsoft Teams Premium Webinar'                                                                        = "TEAMSPRO_WEBINAR"
            'Microsoft Teams Rooms Basic'                                                                            = "Microsoft_Teams_Rooms_Basic"
            'Microsoft Teams Rooms Basic for EDU'                                                                    = "Microsoft_Teams_Rooms_Basic_FAC"
            'Microsoft Teams Rooms Basic without Audio Conferencing'                                                 = "Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing"
            'Microsoft Teams Rooms Pro'                                                                              = "Microsoft_Teams_Rooms_Pro"
            'Microsoft Teams Rooms Pro for EDU'                                                                      = "Microsoft_Teams_Rooms_Pro_FAC"
            'Microsoft Teams Rooms Pro Management'                                                                   = "MTRProManagement"
            'Microsoft Teams Rooms Pro without Audio Conferencing'                                                   = "Microsoft_Teams_Rooms_Pro_without_Audio_Conferencing"
            'Microsoft Teams Rooms Standard'                                                                         = "MEETING_ROOM"
            'Microsoft Teams Rooms Standard without Audio Conferencing'                                              = "MEETING_ROOM_NOAUDIOCONF"
            'Microsoft Teams Shared Devices'                                                                         = "MCOCAP"
            'Microsoft Teams Shared Devices for GCC'                                                                 = "MCOCAP_GOV"
            'Microsoft Teams Trial'                                                                                  = "MS_TEAMS_IW"
            'Microsoft Threat Experts - Experts on Demand'                                                           = "EXPERTS_ON_DEMAND"
            'Microsoft Tunnel for Mobile Application Management'                                                     = "Intune-MAMTunnel"
            'Microsoft Viva Goals'                                                                                   = "Microsoft_Viva_Goals"
            'Microsoft Viva Insights'                                                                                = "WORKPLACE_ANALYTICS_INSIGHTS_USER"
            'Microsoft Viva Insights Backend'                                                                        = "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND"
            'Microsoft Viva Sales Premium & Trial'                                                                   = "Microsoft_Viva_Sales_PremiumTrial"
            'Microsoft Viva Sales Premium with Power Automate'                                                       = "Microsoft_Viva_Sales_PowerAutomate"
            'Microsoft Viva Suite'                                                                                   = "VIVA"
            'Microsoft Viva Topics'                                                                                  = "CORTEX"
            'Microsoft Workplace Analytics'                                                                          = "WORKPLACE_ANALYTICS"
            'Microsoft Workplace Analytics Insights Backend'                                                         = "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND"
            'Microsoft Workplace Analytics Insights User'                                                            = "WORKPLACE_ANALYTICS_INSIGHTS_USER"
            'MICROSOFT_COMMUNICATION_COMPLIANCE'                                                                     = "MICROSOFT_COMMUNICATION_COMPLIANCE"
            'MICROSOFT_SEARCH'                                                                                       = "MICROSOFT_SEARCH"
            'MICROSOFTBOOKINGS'                                                                                      = "MICROSOFTBOOKINGS"
            'Minecraft Education'                                                                                    = "MINECRAFT_EDUCATION_EDITION"
            'Minecraft Education Edition'                                                                            = "MINECRAFT_EDUCATION_EDITION"
            'Minecraft Education Faculty'                                                                            = "MEE_FACULTY"
            'Minecraft Education Student'                                                                            = "MEE_STUDENT"
            'MIP_S_CLP1'                                                                                             = "MIP_S_CLP1"
            'MIP_S_CLP2'                                                                                             = "MIP_S_CLP2"
            'Mobile Device Management for Office 365'                                                                = "INTUNE_O365"
            'MS IMAGINE ACADEMY'                                                                                     = "IT_ACADEMY_AD"
            'MTP'                                                                                                    = "MTP"
            'Multi-Geo Capabilities in Office 365'                                                                   = "OFFICE365_MULTIGEO"
            'Nonprofit Portal'                                                                                       = "NONPROFIT_PORTAL"
            'Nucleus'                                                                                                = "Nucleus"
            'Office 365 A1 for faculty'                                                                              = "STANDARDWOFFPACK_FACULTY"
            'Office 365 A1 for students'                                                                             = "STANDARDWOFFPACK_STUDENT"
            'Office 365 A1 Plus for faculty'                                                                         = "STANDARDWOFFPACK_IW_FACULTY"
            'Office 365 A1 Plus for students'                                                                        = "STANDARDWOFFPACK_IW_STUDENT"
            'Office 365 A3 for faculty'                                                                              = "ENTERPRISEPACKPLUS_FACULTY"
            'Office 365 A3 for students'                                                                             = "ENTERPRISEPACKPLUS_STUDENT"
            'Office 365 A5 for faculty'                                                                              = "ENTERPRISEPREMIUM_FACULTY"
            'Office 365 A5 for students'                                                                             = "ENTERPRISEPREMIUM_STUDENT"
            'Office 365 Advanced Compliance'                                                                         = "EQUIVIO_ANALYTICS"
            'Office 365 Advanced Compliance for GCC'                                                                 = "EQUIVIO_ANALYTICS_GOV"
            'Office 365 Advanced eDiscovery'                                                                         = "EQUIVIO_ANALYTICS"
            'Office 365 Advanced eDiscovery for Government'                                                          = "EQUIVIO_ANALYTICS_GOV"
            'Office 365 Advanced Security Management'                                                                = "ADALLOM_S_O365"
            'OFFICE 365 BUSINESS'                                                                                    = "OFFICE_BUSINESS"
            'Office 365 Cloud App Security'                                                                          = "ADALLOM_O365"
            'Office 365 E1'                                                                                          = "STANDARDPACK"
            'Office 365 E2'                                                                                          = "STANDARDWOFFPACK"
            'Office 365 E3'                                                                                          = "ENTERPRISEPACK"
            'Office 365 E3 Developer'                                                                                = "DEVELOPERPACK"
            'Office 365 E3_USGOV_DOD'                                                                                = "ENTERPRISEPACK_USGOV_DOD"
            'Office 365 E3_USGOV_GCCHIGH'                                                                            = "ENTERPRISEPACK_USGOV_GCCHIGH"
            'Office 365 E4'                                                                                          = "ENTERPRISEWITHSCAL"
            'Office 365 E5'                                                                                          = "ENTERPRISEPREMIUM"
            'Office 365 E5 Without Audio Conferencing'                                                               = "ENTERPRISEPREMIUM_NOPSTNCONF"
            'Office 365 Extra File Storage'                                                                          = "SHAREPOINTSTORAGE"
            'Office 365 Extra File Storage for GCC'                                                                  = "SHAREPOINTSTORAGE_GOV"
            'Office 365 F3'                                                                                          = "DESKLESSPACK"
            'Office 365 G1 GCC'                                                                                      = "STANDARDPACK_GOV"
            'Office 365 G3 GCC'                                                                                      = "ENTERPRISEPACK_GOV"
            'Office 365 G5 GCC'                                                                                      = "ENTERPRISEPREMIUM_GOV"
            'Office 365 Midsize Business'                                                                            = "MIDSIZEPACK"
            'Office 365 Planner for Government'                                                                      = "PROJECTWORKMANAGEMENT_GOV"
            'Office 365 Privileged Access Management'                                                                = "PAM_ENTERPRISE"
            'Office 365 ProPlus'                                                                                     = "OFFICESUBSCRIPTION"
            'Office 365 SafeDocs'                                                                                    = "SAFEDOCS"
            'Office 365 Small Business'                                                                              = "LITEPACK"
            'Office 365 Small Business Premium'                                                                      = "LITEPACK_P2"
            'OFFICE 365 SMALL BUSINESS SUBSCRIPTION'                                                                 = "OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ"
            'Office for the web'                                                                                     = "SHAREPOINTWAC"
            'Office for the web (Education)'                                                                         = "SHAREPOINTWAC_EDU"
            'OFFICE FOR THE WEB (GOVERNMENT)'                                                                        = "SHAREPOINTWAC_GOV"
            'Office for the Web for Education'                                                                       = "SHAREPOINTWAC_EDU"
            'Office for the Web for Government'                                                                      = "SHAREPOINTWAC_GOV"
            'Office Mobile Apps for Office 365'                                                                      = "OFFICEMOBILE_SUBSCRIPTION"
            'Office Mobile Apps for Office 365 for GCC'                                                              = "OFFICEMOBILE_SUBSCRIPTION_GOV"
            'OFFICE ONLINE'                                                                                          = "SHAREPOINTWAC"
            'OFFICE ONLINE FOR DEVELOPER'                                                                            = "SHAREPOINTWAC_DEVELOPER"
            'Office Shared Computer Activation'                                                                      = "OFFICE_SHARED_COMPUTER_ACTIVATION"
            'OFFICEMOBILE_SUBSCRIPTION'                                                                              = "OFFICEMOBILE_SUBSCRIPTION"
            'OFFICESUBSCRIPTION'                                                                                     = "OFFICESUBSCRIPTION"
            'OFFICESUBSCRIPTION_GOV'                                                                                 = "OFFICESUBSCRIPTION_GOV"
            'OneDrive for Business (Basic 2)'                                                                        = "ONEDRIVE_BASIC_P2"
            'OneDrive for Business (Basic)'                                                                          = "ONEDRIVE_BASIC"
            'OneDrive for Business (Plan 1)'                                                                         = "WACONEDRIVESTANDARD"
            'OneDrive for Business (Plan 2)'                                                                         = "WACONEDRIVEENTERPRISE"
            'OneDrive for business Basic'                                                                            = "ONEDRIVE_BASIC"
            'ONEDRIVE FOR BUSINESS BASIC FOR GOVERNMENT'                                                             = "ONEDRIVE_BASIC_GOV"
            'ONEDRIVEENTERPRISE'                                                                                     = "ONEDRIVEENTERPRISE"
            'ONEDRIVESTANDARD'                                                                                       = "ONEDRIVESTANDARD"
            'OUTLOOK CUSTOMER MANAGER'                                                                               = "O365_SB_Relationship_Management"
            'PAD for Windows'                                                                                        = "POWERAUTOMATE_DESKTOP_FOR_WIN"
            'Power Apps (Plan 2)'                                                                                    = "POWERAPPS_P2"
            'Power Apps and Logic Flows'                                                                             = "POWERAPPS_INDIVIDUAL_USER"
            'Power Apps for Customer Service Pro'                                                                    = "POWERAPPS_CUSTOMER_SERVICE_PRO"
            'Power Apps for Dynamics 365'                                                                            = "POWERAPPS_DYN_TEAM"
            'Power Apps for Dynamics 365 vTrial'                                                                     = "POWER_APPS_DYN365_VIRAL_TRIAL"
            'Power Apps for Guides'                                                                                  = "POWERAPPS_GUIDES"
            'Power Apps for Office 365'                                                                              = "POWERAPPS_O365_P2"
            'Power Apps for Office 365 (Plan 3)'                                                                     = "POWERAPPS_O365_P3"
            'Power Apps for Office 365 F3'                                                                           = "POWERAPPS_O365_S1"
            'Power Apps for Office 365 F3 for Government'                                                            = "POWERAPPS_O365_S1_GOV"
            'Power Apps for Office 365 for Government'                                                               = "POWERAPPS_O365_P3_GOV"
            'Power Apps for Sales Pro'                                                                               = "POWERAPPS_SALES_PRO"
            'Power Apps per app'                                                                                     = "POWERAPPS_PER_APP_NEW"
            'Power Apps per app plan'                                                                                = "POWERAPPS_PER_APP"
            'Power Apps per app plan (1 app or portal)'                                                              = "POWERAPPS_PER_APP_NEW"
            'Power Apps per user plan'                                                                               = "POWERAPPS_PER_USER"
            'Power Apps per user plan for Government'                                                                = "POWERAPPS_PER_USER_GCC"
            'Power Apps Portals Login Capacity Add-On'                                                               = "POWERAPPS_PORTALS_LOGIN"
            'Power Apps Portals Login Capacity Add-On for Government'                                                = "POWERAPPS_PORTALS_LOGIN_GCC"
            'Power Apps Portals login capacity add-on Tier 2 (10 unit min)'                                          = "POWERAPPS_PORTALS_LOGIN_T2"
            'Power Apps Portals login capacity add-on Tier 2 (10 unit min) for Government'                           = "POWERAPPS_PORTALS_LOGIN_T2_GCC"
            'Power Apps Portals login capacity add-on Tier 3 (50 unit min)'                                          = "POWERAPPS_PORTALS_LOGIN_T3"
            'Power Apps Portals page view capacity add-on'                                                           = "POWERAPPS_PORTALS_PAGEVIEW"
            'Power Apps Portals page view capacity add-on for Government'                                            = "POWERAPPS_PORTALS_PAGEVIEW_GCC"
            'Power Automate (Plan 1) for Government'                                                                 = "FLOW_P1_GOV"
            'Power Automate (Plan 2)'                                                                                = "FLOW_P2"
            'Power Automate for Customer Service Pro'                                                                = "FLOW_CUSTOMER_SERVICE_PRO"
            'Power Automate for Dynamics 365'                                                                        = "FLOW_DYN_TEAM"
            'Power Automate for Dynamics 365 Customer Voice'                                                         = "FLOW_FORMS_PRO"
            'Power Automate for Dynamics 365 vTrial'                                                                 = "POWER_AUTOMATE_DYN365_VIRAL_TRIAL"
            'Power Automate for Office 365'                                                                          = "FLOW_O365_P2"
            'Power Automate for Office 365 F3'                                                                       = "FLOW_O365_S1"
            'Power Automate for Office 365 F3 for Government'                                                        = "FLOW_O365_S1_GOV"
            'Power Automate for Office 365 for Government'                                                           = "FLOW_O365_P3_GOV"
            'Power Automate for Power Apps per App Plan'                                                             = "Flow_Per_APP"
            'Power Automate for Power Apps per User Plan'                                                            = "Flow_PowerApps_PerUser"
            'Power Automate for Power Apps per User Plan for GCC'                                                    = "Flow_PowerApps_PerUser_GCC"
            'Power Automate for Project'                                                                             = "FLOW_FOR_PROJECT"
            'POWER AUTOMATE FOR PROJECT P1'                                                                          = "Power_Automate_For_Project_P1"
            'Power Automate for Virtual Agent'                                                                       = "FLOW_VIRTUAL_AGENT_BASE"
            'Power Automate per flow plan'                                                                           = "FLOW_BUSINESS_PROCESS"
            'Power Automate per user plan'                                                                           = "FLOW_PER_USER"
            'Power Automate per user plan dept'                                                                      = "FLOW_PER_USER_DEPT"
            'Power Automate per user plan for Government'                                                            = "FLOW_PER_USER_GCC"
            'Power Automate per user with attended RPA plan'                                                         = "POWERAUTOMATE_ATTENDED_RPA"
            'Power Automate RPA Attended'                                                                            = "POWER_AUTOMATE_ATTENDED_RPA"
            'Power Automate unattended RPA add-on'                                                                   = "POWERAUTOMATE_UNATTENDED_RPA"
            'Power BI'                                                                                               = "POWER_BI_INDIVIDUAL_USER"
            'Power BI (free)'                                                                                        = "POWER_BI_STANDARD"
            'Power BI for Office 365 Add-On'                                                                         = "POWER_BI_ADDON"
            'Power BI Premium P'                                                                                     = "PBI_PREMIUM_P1_ADDON"
            'Power BI Premium P1'                                                                                    = "PBI_PREMIUM_P1_ADDON"
            'Power BI Premium Per User'                                                                              = "PBI_PREMIUM_PER_USER"
            'Power BI Premium Per User Add-On'                                                                       = "PBI_PREMIUM_PER_USER_ADDON"
            'Power BI Premium Per User Dept'                                                                         = "PBI_PREMIUM_PER_USER_DEPT"
            'Power BI Premium Per User for Faculty'                                                                  = "PBI_PREMIUM_PER_USER_FACULTY"
            'Power BI Pro'                                                                                           = "POWER_BI_PRO"
            'Power BI Pro CE'                                                                                        = "POWER_BI_PRO_CE"
            'Power BI Pro Dept'                                                                                      = "POWER_BI_PRO_DEPT"
            'Power BI Pro for Faculty'                                                                               = "POWER_BI_PRO_FACULTY"
            'Power BI Pro for GCC'                                                                                   = "POWERBI_PRO_GOV"
            'Power BI Pro for Government'                                                                            = "BI_AZURE_P_2_GOV"
            'Power Pages Internal User'                                                                              = "Power_Pages_Internal_User"
            'Power Pages vTrial for Makers'                                                                          = "Power_Pages_vTrial_for_Makers"
            'Power Virtual Agent'                                                                                    = "VIRTUAL_AGENT_BASE"
            'Power Virtual Agent User License'                                                                       = "VIRTUAL_AGENT_USL"
            'Power Virtual Agents for Chat'                                                                          = "POWER_VIRTUAL_AGENTS_D365_CS_CHAT"
            'Power Virtual Agents for Customer Service Voice'                                                        = "POWER_VIRTUAL_AGENTS_D365_CS_VOICE"
            'Power Virtual Agents for Digital Messaging'                                                             = "POWER_VIRTUAL_AGENTS_D365_CS_MESSAGING"
            'Power Virtual Agents for Office 365'                                                                    = "POWER_VIRTUAL_AGENTS_O365_P2"
            'Power Virtual Agents for Office 365 F1'                                                                 = "POWER_VIRTUAL_AGENTS_O365_F1"
            'POWER VIRTUAL AGENTS FOR OFFICE 365 P1'                                                                 = "POWER_VIRTUAL_AGENTS_O365_P1"
            'Power Virtual Agents for Office 365 P2'                                                                 = "POWER_VIRTUAL_AGENTS_O365_P2"
            'Power Virtual Agents for Office 365 P3'                                                                 = "POWER_VIRTUAL_AGENTS_O365_P3"
            'Power Virtual Agents Viral Trial'                                                                       = "CCIBOTS_PRIVPREV_VIRAL"
            'PowerApps for Developer'                                                                                = "POWERAPPS_DEV_VIRAL"
            'PowerApps for Dynamics 365'                                                                             = "POWERAPPS_DYN_APPS"
            'POWERAPPS FOR OFFICE 36'                                                                                = "POWERAPPS_O365_P2"
            'POWERAPPS FOR OFFICE 365'                                                                               = "POWERAPPS_O365_P1"
            'PowerApps for Office 365 Plan 3'                                                                        = "POWERAPPS_O365_P3"
            'PowerApps per app baseline access'                                                                      = "POWERAPPS_PER_APP_IW"
            'PowerApps Plan 1 for Government'                                                                        = "POWERAPPS_P1_GOV"
            'PowerApps Trial'                                                                                        = "POWERAPPS_P2_VIRAL"
            'POWERAPPS_O365_P3_GOV'                                                                                  = "POWERAPPS_O365_P3_GOV"
            'Premium Encryption in Office 365'                                                                       = "PREMIUM_ENCRYPTION"
            'PREMIUM_ENCRYPTION'                                                                                     = "PREMIUM_ENCRYPTION"
            'Priva - Risk'                                                                                           = "PRIVACY_MANGEMENT_RISK"
            'Priva - Risk (Exchange)'                                                                                = "PRIVACY_MANGEMENT_RISK_EXCHANGE"
            'Privacy Management � risk'                                                                              = "PRIVACY_MANAGEMENT_RISK"
            'Privacy Management - risk for EDU'                                                                      = "PRIVACY_MANAGEMENT_RISK_EDU"
            'Privacy Management - risk GCC'                                                                          = "PRIVACY_MANAGEMENT_RISK_GCC"
            'Privacy Management - risk_USGOV_DOD'                                                                    = "PRIVACY_MANAGEMENT_RISK_USGOV_DOD"
            'Privacy Management - risk_USGOV_GCCHIGH'                                                                = "PRIVACY_MANAGEMENT_RISK_USGOV_GCCHIGH"
            'Privacy Management - Subject Rights Request'                                                            = "PRIVACY_MANGEMENT_DSR"
            'Privacy Management - Subject Rights Request (1 - Exchange)'                                             = "PRIVACY_MANGEMENT_DSR_1"
            'Privacy Management - subject rights request (1)'                                                        = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2"
            'Privacy Management - subject rights request (1) for EDU'                                                = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_EDU_V2"
            'Privacy Management - subject rights request (1) GCC'                                                    = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_GCC"
            'Privacy Management - subject rights request (1) USGOV_DOD'                                              = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_DOD"
            'Privacy Management - subject rights request (1) USGOV_GCCHIGH'                                          = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_GCCHIGH"
            'Privacy Management - Subject Rights Request (10 - Exchange)'                                            = "PRIVACY_MANGEMENT_DSR_EXCHANGE_10"
            'Privacy Management - subject rights request (10)'                                                       = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2"
            'Privacy Management - subject rights request (10) for EDU'                                               = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_EDU_V2"
            'Privacy Management - subject rights request (10) GCC'                                                   = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_GCC"
            'Privacy Management - subject rights request (10) USGOV_DOD'                                             = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_DOD"
            'Privacy Management - subject rights request (10) USGOV_GCCHIGH'                                         = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_GCCHIGH"
            'Privacy Management - Subject Rights Request (100 - Exchange)'                                           = "PRIVACY_MANGEMENT_DSR_EXCHANGE_100"
            'Privacy Management - subject rights request (100)'                                                      = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2"
            'Privacy Management - subject rights request (100) for EDU'                                              = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_EDU_V2"
            'Privacy Management - subject rights request (100) GCC'                                                  = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_GCC"
            'Privacy Management - subject rights request (100) USGOV_DOD'                                            = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_DOD"
            'Privacy Management - subject rights request (100) USGOV_GCCHIGH'                                        = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_GCCHIGH"
            'Privacy Management - subject rights request (50)'                                                       = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_V2"
            'Privacy Management - subject rights request (50) for EDU'                                               = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_EDU_V2"
            'Privacy Management - Subject Rights Request (Exchange)'                                                 = "PRIVACY_MANGEMENT_DSR_EXCHANGE"
            'Project for Office (Plan E1)'                                                                           = "PROJECT_O365_P1"
            'Project for Office (Plan E3)'                                                                           = "PROJECT_O365_P2"
            'Project for Office (Plan E5)'                                                                           = "PROJECT_O365_P3"
            'Project for Office (Plan F)'                                                                            = "PROJECT_O365_F3"
            'Project for Office 365'                                                                                 = "PROJECTCLIENT"
            'Project for Project Operations'                                                                         = "PROJECT_FOR_PROJECT_OPERATIONS"
            'Project Online Desktop Client'                                                                          = "PROJECT_CLIENT_SUBSCRIPTION"
            'Project Online Desktop Client for Government'                                                           = "PROJECT_CLIENT_SUBSCRIPTION_GOV"
            'Project Online Essentials'                                                                              = "PROJECTESSENTIALS"
            'Project Online Essentials for Faculty'                                                                  = "PROJECTESSENTIALS_FACULTY"
            'Project Online Essentials for GCC'                                                                      = "PROJECTESSENTIALS_GOV"
            'Project Online Essentials for Government'                                                               = "PROJECT_ESSENTIALS_GOV"
            'Project Online Premium'                                                                                 = "PROJECTPREMIUM"
            'Project Online Premium Without Project Client'                                                          = "PROJECTONLINE_PLAN_1"
            'Project Online Service'                                                                                 = "SHAREPOINT_PROJECT"
            'Project Online Service for Education'                                                                   = "SHAREPOINT_PROJECT_EDU"
            'Project Online Service for Government'                                                                  = "SHAREPOINT_PROJECT_GOV"
            'Project Online With Project for Office 365'                                                             = "PROJECTONLINE_PLAN_2"
            'PROJECT P1'                                                                                             = "PROJECT_P1"
            'Project P3'                                                                                             = "PROJECT_PROFESSIONAL"
            'Project P3 for Faculty'                                                                                 = "PROJECT_PROFESSIONAL_FACULTY"
            'Project Plan 1'                                                                                         = "PROJECT_P1"
            'Project Plan 1 (for Department)'                                                                        = "PROJECT_PLAN1_DEPT"
            'Project Plan 3'                                                                                         = "PROJECTPROFESSIONAL"
            'Project Plan 3 (for Department)'                                                                        = "PROJECT_PLAN3_DEPT"
            'Project Plan 3 for Faculty'                                                                             = "PROJECTPROFESSIONAL_FACULTY"
            'Project Plan 3 for GCC'                                                                                 = "PROJECTPROFESSIONAL_GOV"
            'Project Plan 5 for GCC'                                                                                 = "PROJECTPREMIUM_GOV"
            'Project Plan 5 without Project Client for Faculty'                                                      = "PROJECTONLINE_PLAN_1_FACULTY"
            'PROJECTWORKMANAGEMENT'                                                                                  = "PROJECTWORKMANAGEMENT"
            'PROJECTWORKMANAGEMENT_GOV'                                                                              = "PROJECTWORKMANAGEMENT_GOV"
            'RECORDS_MANAGEMENT'                                                                                     = "RECORDS_MANAGEMENT"
            'Remote help'                                                                                            = "REMOTE_HELP"
            'RETIRED - Field Service � Automated Routing Engine Add-On'                                              = "CRM_AUTO_ROUTING_ADDON"
            'RETIRED - Microsoft Communications Compliance'                                                          = "COMMUNICATIONS_COMPLIANCE"
            'RETIRED - Microsoft Insider Risk Management'                                                            = "INSIDER_RISK_MANAGEMENT"
            'Retired - Microsoft Social Engagement'                                                                  = "NBENTERPRISE"
            'RETIRED - Outlook Customer Manager'                                                                     = "O365_SB_Relationship_Management"
            'Rights Management Adhoc'                                                                                = "RIGHTSMANAGEMENT_ADHOC"
            'Rights Management Service Basic Content Protection'                                                     = "RMSBASIC"
            'RMS_S_ENTERPRISE'                                                                                       = "RMS_S_ENTERPRISE"
            'RMS_S_ENTERPRISE_GOV'                                                                                   = "RMS_S_ENTERPRISE_GOV"
            'RMS_S_PREMIUM'                                                                                          = "RMS_S_PREMIUM"
            'School Data Sync (Plan 1)'                                                                              = "SCHOOL_DATA_SYNC_P1"
            'School Data Sync (Plan 2)'                                                                              = "SCHOOL_DATA_SYNC_P2"
            'SecOps Investigation for MDI'                                                                           = "ADALLOM_FOR_AATP"
            'Sensor Data Intelligence Additional Machines Add-in for Dynamics 365 Supply Chain Management'           = "DYN365_IOT_INTELLIGENCE_ADDL_MACHINES"
            'Sensor Data Intelligence Scenario Add-in for Dynamics 365 Supply Chain Management'                      = "DYN365_IOT_INTELLIGENCE_SCENARIO"
            'SHAREPOINT'                                                                                             = "SHAREPOINTSTANDARD"
            'SharePoint (Plan 1)'                                                                                    = "SHAREPOINTSTANDARD"
            'SharePoint (Plan 1) for Education'                                                                      = "SHAREPOINTSTANDARD_EDU"
            'SharePoint (Plan 2)'                                                                                    = "SHAREPOINTENTERPRISE"
            'SharePoint (Plan 2) for Education'                                                                      = "SHAREPOINTENTERPRISE_EDU"
            'SharePoint (Plan 2)Dynamics 365 for Sales Pro Attach'                                                   = "SHAREPOINTENTERPRISE"
            'SHAREPOINT FOR DEVELOPER'                                                                               = "SHAREPOINT_S_DEVELOPER"
            'SharePoint Kiosk'                                                                                       = "SHAREPOINTDESKLESS"
            'SharePoint KioskG'                                                                                      = "SHAREPOINTDESKLESS_GOV"
            'SharePoint Multi-Geo'                                                                                   = "SHAREPOINTONLINE_MULTIGEO"
            'SharePoint Online (Plan 1)'                                                                             = "SHAREPOINTSTANDARD"
            'SharePoint Online (Plan 2)'                                                                             = "SHAREPOINTENTERPRISE"
            'SharePoint Online Kiosk'                                                                                = "SHAREPOINTDESKLESS"
            'SHAREPOINT PLAN 1'                                                                                      = "SHAREPOINTENTERPRISE_MIDMARKET"
            'SharePoint Plan 1G'                                                                                     = "SharePoint Plan 1G"
            'SharePoint Plan 2 for EDU'                                                                              = "SHAREPOINTENTERPRISE_EDU"
            'SharePoint Plan 2G'                                                                                     = "SHAREPOINTENTERPRISE_GOV"
            'SHAREPOINT STANDARD'                                                                                    = "SHAREPOINTSTANDARD"
            'SharePoint Syntex'                                                                                      = "Intelligent_Content_Services"
            'SharePoint Syntex - SPO type'                                                                           = "Intelligent_Content_Services_SPO_type"
            'SHAREPOINT_PROJECT'                                                                                     = "SHAREPOINT_PROJECT"
            'SHAREPOINTDESKLESS'                                                                                     = "SHAREPOINTDESKLESS"
            'SHAREPOINTENTERPRISE_GOV'                                                                               = "SHAREPOINTENTERPRISE_GOV"
            'SHAREPOINTLITE'                                                                                         = "SHAREPOINTLITE"
            'SHAREPOINTSTANDARD'                                                                                     = "SHAREPOINTSTANDARD"
            'SHAREPOINTSTORAGE_GOV'                                                                                  = "SHAREPOINTSTORAGE_GOV"
            'SHAREPOINTWAC_GOV'                                                                                      = "SHAREPOINTWAC_GOV"
            'SKYPE FOR BUSINESS CLOUD PBX FOR SMALL AND MEDIUM BUSINESS'                                             = "MCOEVSMB"
            'Skype for Business Online (Plan 1)'                                                                     = "MCOIMP"
            'Skype for Business Online (Plan 1) for Government'                                                      = "MCOIMP_GOV"
            'Skype for Business Online (Plan 2)'                                                                     = "MCOSTANDARD"
            'Skype for Business Online (Plan 2) for Government'                                                      = "MCOSTANDARD_GOV"
            'SKYPE FOR BUSINESS ONLINE (PLAN 2) FOR MIDSIZ'                                                          = "MCOSTANDARD_MIDMARKET"
            'SKYPE FOR BUSINESS ONLINE (PLAN 3)'                                                                     = "MCOVOICECONF"
            'SKYPE FOR BUSINESS ONLINE (PLAN P1)'                                                                    = "MCOLITE"
            'Skype for Business PSTN Domestic and International Calling'                                             = "MCOPSTN2"
            'Skype for Business PSTN Domestic Calling'                                                               = "MCOPSTN1"
            'Skype for Business PSTN Domestic Calling (120 Minutes)'                                                 = "MCOPSTN5"
            'Skype for Business PSTN Usage Calling Plan'                                                             = "MCOPSTNPP"
            'Stream for Office 365'                                                                                  = "STREAM_O365_SMB"
            'Stream for Office 365 for Government (E5)'                                                              = "STREAM_O365_E5_GOV"
            'STREAM_O365_E5_GOV'                                                                                     = "STREAM_O365_E5_GOV"
            'STREAM_O365_K'                                                                                          = "STREAM_O365_K"
            'Sway'                                                                                                   = "SWAY"
            'TEAMS FREE SERVICE'                                                                                     = "TEAMS_FREE_SERVICE"
            'Teams Multi-Geo'                                                                                        = "TEAMSMULTIGEO"
            'Teams Phone with Calling Plan'                                                                          = "MCOTEAMS_ESSENTIALS"
            'Teams Room Basic'                                                                                       = "Teams_Room_Basic"
            'Teams Room Pro'                                                                                         = "Teams_Room_Pro"
            'Teams Room Standard'                                                                                    = "Teams_Room_Standard"
            'Teams Rooms Premium'                                                                                    = "MTR_PREM"
            'Teams Rooms Test 1'                                                                                     = "Teams_Room_Basic"
            'Teams Rooms Test 2'                                                                                     = "Teams_Room_Pro"
            'TEAMS_GOV'                                                                                              = "TEAMS_GOV"
            'TEAMS1'                                                                                                 = "TEAMS1"
            'TELSTRA Calling for O365'                                                                               = "MCOPSTNEAU2"
            'THREAT_INTELLIGENCE_GOV'                                                                                = "THREAT_INTELLIGENCE_GOV"
            'To-Do (Firstline)'                                                                                      = "BPOS_S_TODO_FIRSTLINE"
            'To-Do (Plan 1)'                                                                                         = "BPOS_S_TODO_1"
            'To-Do (Plan 2)'                                                                                         = "BPOS_S_TODO_2"
            'To-Do (Plan 3)'                                                                                         = "BPOS_S_TODO_3"
            'Universal Print'                                                                                        = "UNIVERSAL_PRINT"
            'Universal Print Without Seeding'                                                                        = "UNIVERSAL_PRINT_NO_SEEDING"
            'Virtual Agent'                                                                                          = "VIRTUAL_AGENT_USL"
            'Virtual Agent Base'                                                                                     = "VIRTUAL_AGENT_BASE"
            'Visio Desktop App'                                                                                      = "VISIO_CLIENT_SUBSCRIPTION"
            'VISIO DESKTOP APP FOR Government'                                                                       = "VISIO_CLIENT_SUBSCRIPTION_GOV"
            'Visio Online Plan 1'                                                                                    = "VISIOONLINE_PLAN1"
            'Visio Online Plan 2'                                                                                    = "VISIOCLIENT"
            'Visio Plan 1'                                                                                           = "VISIO_PLAN1_DEPT"
            'Visio Plan 2'                                                                                           = "VISIO_PLAN2_DEPT"
            'Visio Plan 2 for Faculty'                                                                               = "VISIOCLIENT_FACULTY"
            'Visio Plan 2 for GCC'                                                                                   = "VISIOCLIENT_GOV"
            'Visio web app'                                                                                          = "VISIOONLINE"
            'VISIO WEB APP FOR GOVERNMENT'                                                                           = "VISIOONLINE_GOV"
            'Viva Engage Communities and Communications'                                                             = "VIVAENGAGE_COMMUNITIES_AND_COMMUNICATIONS"
            'Viva Engage Core'                                                                                       = "VIVAENGAGE_CORE"
            'Viva Engage Knowledge'                                                                                  = "VIVAENGAGE_KNOWLEDGE"
            'Viva Goals'                                                                                             = "Viva_Goals_Premium"
            'Viva Learning'                                                                                          = "VIVA_LEARNING_PREMIUM"
            'Viva Learning Seeded'                                                                                   = "VIVA_LEARNING_SEEDED"
            'Viva Topics'                                                                                            = "TOPIC_EXPERIENCES"
            'Whiteboard (Firstline)'                                                                                 = "WHITEBOARD_FIRSTLINE1"
            'Whiteboard (Plan 1)'                                                                                    = "WHITEBOARD_PLAN1"
            'Whiteboard (Plan 2)'                                                                                    = "WHITEBOARD_PLAN2"
            'Whiteboard (Plan 3)'                                                                                    = "WHITEBOARD_PLAN3"
            'WINDOWS 10 ENTERPRISE'                                                                                  = "WIN10_PRO_ENT_SUB"
            'Windows 10 Enterprise (New)'                                                                            = "Virtualization Rights for Windows 10 (E3/E5+VDA)"
            'Windows 10 Enterprise E3 (Local Only)'                                                                  = "WIN10_ENT_LOC_F1"
            'Windows 10/11 Business'                                                                                 = "WINBIZ"
            'Windows 10/11 Enterprise'                                                                               = "Virtualization Rights for Windows 10 (E3/E5+VDA)"
            'Windows 10/11 Enterprise (Original)'                                                                    = "WIN10_PRO_ENT_SUB"
            'Windows 10/11 Enterprise A3 for faculty'                                                                = "WIN10_ENT_A3_FAC"
            'Windows 10/11 Enterprise A3 for students'                                                               = "WIN10_ENT_A3_STU"
            'Windows 10/11 Enterprise A5 for faculty'                                                                = "WIN10_ENT_A5_FAC"
            'Windows 10/11 Enterprise E3'                                                                            = "WIN10_VDA_E3"
            'Windows 10/11 Enterprise E3 VDA'                                                                        = "E3_VDA_only"
            'Windows 10/11 Enterprise E5'                                                                            = "WIN10_VDA_E5"
            'Windows 10/11 Enterprise E5 (Original)'                                                                 = "WIN_ENT_E5"
            'Windows 10/11 Enterprise E5 Commercial (GCC Compatible)'                                                = "WINE5_GCC_COMPAT"
            'Windows 365 Business 1 vCPU 2 GB 64 GB'                                                                 = "CPC_B_1C_2RAM_64GB"
            'Windows 365 Business 2 vCPU 4 GB 128 GB'                                                                = "CPC_B_2C_4RAM_128GB"
            'Windows 365 Business 2 vCPU 4 GB 256 GB'                                                                = "CPC_B_2C_4RAM_256GB"
            'Windows 365 Business 2 vCPU 4 GB 64 GB'                                                                 = "CPC_B_2C_4RAM_64GB"
            'Windows 365 Business 2 vCPU 8 GB 128 GB'                                                                = "CPC_B_2C_8RAM_128GB"
            'Windows 365 Business 2 vCPU 8 GB 256 GB'                                                                = "CPC_B_2C_8RAM_256GB"
            'Windows 365 Business 2 vCPU, 8 GB, 128 GB'                                                              = "CPC_SS_2"
            'Windows 365 Business 4 vCPU 16 GB 128 GB'                                                               = "CPC_B_4C_16RAM_128GB"
            'Windows 365 Business 4 vCPU 16 GB 128 GB (with Windows Hybrid Benefit)'                                 = "CPC_B_4C_16RAM_128GB_WHB"
            'Windows 365 Business 4 vCPU 16 GB 256 GB'                                                               = "CPC_B_4C_16RAM_256GB"
            'Windows 365 Business 4 vCPU 16 GB 512 GB'                                                               = "CPC_B_4C_16RAM_512GB"
            'Windows 365 Business 8 vCPU 32 GB 128 GB'                                                               = "CPC_B_8C_32RAM_128GB"
            'Windows 365 Business 8 vCPU 32 GB 256 GB'                                                               = "CPC_B_8C_32RAM_256GB"
            'Windows 365 Business 8 vCPU 32 GB 512 GB'                                                               = "CPC_B_8C_32RAM_512GB"
            'Windows 365 Enterprise 1 vCPU 2 GB 64 GB'                                                               = "CPC_E_1C_2GB_64GB"
            'Windows 365 Enterprise 2 vCPU 4 GB 128 GB'                                                              = "CPC_E_2C_4GB_128GB"
            'Windows 365 Enterprise 2 vCPU 4 GB 128 GB (Preview)'                                                    = "CPC_LVL_1"
            'Windows 365 Enterprise 2 vCPU 4 GB 256 GB'                                                              = "CPC_E_2C_4GB_256GB"
            'Windows 365 Enterprise 2 vCPU 4 GB 64 GB'                                                               = "CPC_E_2C_4GB_64GB"
            'Windows 365 Enterprise 2 vCPU 8 GB 128 GB'                                                              = "CPC_E_2C_8GB_128GB"
            'Windows 365 Enterprise 2 vCPU 8 GB 128 GB (Preview)'                                                    = "CPC_LVL_2"
            'Windows 365 Enterprise 2 vCPU 8 GB 256 GB'                                                              = "CPC_E_2C_8GB_256GB"
            'Windows 365 Enterprise 4 vCPU 16 GB 128 GB'                                                             = "CPC_E_4C_16GB_128GB"
            'Windows 365 Enterprise 4 vCPU 16 GB 256 GB'                                                             = "CPC_E_4C_16GB_256GB"
            'Windows 365 Enterprise 4 vCPU 16 GB 256 GB (Preview)'                                                   = "CPC_LVL_3"
            'Windows 365 Enterprise 4 vCPU 16 GB 512 GB'                                                             = "CPC_E_4C_16GB_512GB"
            'Windows 365 Enterprise 8 vCPU 32 GB 128 GB'                                                             = "CPC_E_8C_32GB_128GB"
            'Windows 365 Enterprise 8 vCPU 32 GB 256 GB'                                                             = "CPC_E_8C_32GB_256GB"
            'Windows 365 Enterprise 8 vCPU 32 GB 512 GB'                                                             = "CPC_E_8C_32GB_512GB"
            'Windows 365 Shared Use 2 vCPU 4 GB 128 GB'                                                              = "Windows_365_S_2vCPU_4GB_128GB"
            'Windows 365 Shared Use 2 vCPU 4 GB 256 GB'                                                              = "Windows_365_S_2vCPU_4GB_256GB"
            'Windows 365 Shared Use 2 vCPU 4 GB 64 GB'                                                               = "Windows_365_S_2vCPU_4GB_64GB"
            'Windows 365 Shared Use 2 vCPU 8 GB 128 GB'                                                              = "Windows_365_S_2vCPU_8GB_128GB"
            'Windows 365 Shared Use 2 vCPU 8 GB 256 GB'                                                              = "Windows_365_S_2vCPU_8GB_256GB"
            'Windows 365 Shared Use 4 vCPU 16 GB 128 GB'                                                             = "Windows_365_S_4vCPU_16GB_128GB"
            'Windows 365 Shared Use 4 vCPU 16 GB 256 GB'                                                             = "Windows_365_S_4vCPU_16GB_256GB"
            'Windows 365 Shared Use 4 vCPU 16 GB 512 GB'                                                             = "Windows_365_S_4vCPU_16GB_512GB"
            'Windows 365 Shared Use 8 vCPU 32 GB 128 GB'                                                             = "Windows_365_S_8vCPU_32GB_128GB"
            'Windows 365 Shared Use 8 vCPU 32 GB 256 GB'                                                             = "Windows_365_S_8vCPU_32GB_256GB"
            'Windows 365 Shared Use 8 vCPU 32 GB 512 GB'                                                             = "Windows_365_S_8vCPU_32GB_512GB"
            'Windows Autopatch'                                                                                      = "Windows_Autopatch"
            'Windows Store for Business'                                                                             = "WINDOWS_STORE"
            'Windows Store for Business EDU Faculty'                                                                 = "WSFB_EDU_FACULTY"
            'Windows Store for Business EDU Store_faculty'                                                           = "Windows Store for Business EDU Store_faculty"
            'Windows Store Service'                                                                                  = "WINDOWS_STORE"
            'Windows Update for Business Deployment Service'                                                         = "WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE"
            'YAMMER ENTERPRIS'                                                                                       = "YAMMER_ENTERPRISE"
            'Yammer Enterprise'                                                                                      = "YAMMER_ENTERPRISE"
            'Yammer for Academic'                                                                                    = "YAMMER_EDU"
            'YAMMER MIDSIZE'                                                                                         = "YAMMER_MIDSIZE"
            'YAMMER_ENTERPRISE'                                                                                      = "YAMMER_ENTERPRISE"
            'YAMMER_MIDSIZE'                                                                                         = "YAMMER_MIDSIZE"
        }
    }
    Process {
        if (-not $ToSku) {
            $ConvertedLicenses = foreach ($LicenseToProcess in $License) {
                if ($LicenseToProcess -is [string]) {
                    $L = $LicenseToProcess
                }
                elseif ($LicenseToProcess -is [Microsoft.Online.Administration.UserLicense]) {
                    $L = $LicenseToProcess.AccountSkuId
                }
                else {
                    continue
                }

                $L = $L -replace '.*(:)'

                $Conversion = $O365SKU[$L]
                if ($null -eq $Conversion) {
                    $L
                }
                else {
                    $Conversion
                }
            }
        }
        else {
            $ConvertedLicenses = :Outer foreach ($L in $License) {

                $Conversion = $SKUO365[$L]
                if ($null -eq $Conversion) {
                    $L
                }
                else {
                    $Conversion
                }
            }
        }
        if ($ReturnArray) {
            $ConvertedLicenses
        }
        else {
            $ConvertedLicenses -join $Separator
        }
    }
    End {
    }
}

function Convert-Size {
    <#
    .SYNOPSIS
    Converts a value from one size unit to another.
 
    .DESCRIPTION
    This function converts a value from one size unit (Bytes, KB, MB, GB, TB) to another size unit based on the specified conversion. It provides flexibility to handle different size units and precision of the conversion.
 
    .PARAMETER From
    Specifies the original size unit of the input value.
 
    .PARAMETER To
    Specifies the target size unit to convert the input value to.
 
    .PARAMETER Value
    Specifies the numerical value to be converted.
 
    .PARAMETER Precision
    Specifies the number of decimal places to round the converted value to. Default is 4.
 
    .PARAMETER Display
    Indicates whether to display the converted value with the target size unit.
 
    .EXAMPLE
    Convert-Size -From 'KB' -To 'MB' -Value 2048
    # Converts 2048 Kilobytes to Megabytes.
 
    .EXAMPLE
    Convert-Size -From 'GB' -To 'TB' -Value 2.5 -Precision 2 -Display
    # Converts 2.5 Gigabytes to Terabytes with a precision of 2 decimal places and displays the result.
 
    #>

    # Original - https://techibee.com/powershell/convert-from-any-to-any-bytes-kb-mb-gb-tb-using-powershell/2376
    #
    # Changelog - Modified 30.03.2018 - przemyslaw.klys at evotec.pl
    # - Added $Display Switch
    [cmdletbinding()]
    param(
        [validateset("Bytes", "KB", "MB", "GB", "TB")]
        [string]$From,
        [validateset("Bytes", "KB", "MB", "GB", "TB")]
        [string]$To,
        [Parameter(Mandatory = $true)]
        [double]$Value,
        [int]$Precision = 4,
        [switch]$Display
    )
    switch ($From) {
        "Bytes" {
            $value = $Value 
        }
        "KB" {
            $value = $Value * 1024 
        }
        "MB" {
            $value = $Value * 1024 * 1024
        }
        "GB" {
            $value = $Value * 1024 * 1024 * 1024
        }
        "TB" {
            $value = $Value * 1024 * 1024 * 1024 * 1024
        }
    }

    switch ($To) {
        "Bytes" {
            return $value
        }
        "KB" {
            $Value = $Value / 1KB
        }
        "MB" {
            $Value = $Value / 1MB
        }
        "GB" {
            $Value = $Value / 1GB
        }
        "TB" {
            $Value = $Value / 1TB
        }
    }
    if ($Display) {
        return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To"
    }
    else {
        return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero)
    }
}
function Convert-TimeToDays {
    <#
    .SYNOPSIS
    Converts the time span between two dates into the number of days.
 
    .DESCRIPTION
    This function calculates the number of days between two given dates. It allows for flexibility in handling different date formats and provides an option to ignore specific dates.
 
    .PARAMETER StartTime
    Specifies the start date and time of the time span.
 
    .PARAMETER EndTime
    Specifies the end date and time of the time span.
 
    .PARAMETER Ignore
    Specifies a pattern to ignore specific dates. Default is '*1601*'.
 
    .EXAMPLE
    Convert-TimeToDays -StartTime (Get-Date).AddDays(-5) -EndTime (Get-Date)
    # Calculates the number of days between 5 days ago and today.
 
    .EXAMPLE
    Convert-TimeToDays -StartTime '2022-01-01' -EndTime '2022-01-10' -Ignore '*2022*'
    # Calculates the number of days between January 1, 2022, and January 10, 2022, ignoring any dates containing '2022'.
 
    #>

    [CmdletBinding()]
    param (
        $StartTime,
        $EndTime,
        #[nullable[DateTime]] $StartTime, # can't use this just yet, some old code uses strings in StartTime/EndTime.
        #[nullable[DateTime]] $EndTime, # After that's fixed will change this.
        [string] $Ignore = '*1601*'
    )
    if ($null -ne $StartTime -and $null -ne $EndTime) {
        try {
            if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) {
                $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days
            }
        }
        catch {
        }
    }
    elseif ($null -ne $EndTime) {
        if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) {
            $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days
        }
    }
    elseif ($null -ne $StartTime) {
        if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) {
            $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days
        }
    }
    return $Days
}
function Convert-ToDateTime {
    <#
    .SYNOPSIS
    Converts a file time string to a DateTime object.
 
    .DESCRIPTION
    This function converts a file time string to a DateTime object. It handles the conversion and provides flexibility to ignore specific file time strings.
 
    .PARAMETER Timestring
    Specifies the file time string to convert to a DateTime object.
 
    .PARAMETER Ignore
    Specifies a pattern to ignore specific file time strings. Default is '*1601*'.
 
    .EXAMPLE
    Convert-ToDateTime -Timestring '132479040000000000'
    # Converts the file time string '132479040000000000' to a DateTime object.
 
    .EXAMPLE
    Convert-ToDateTime -Timestring '132479040000000000' -Ignore '*1601*'
    # Converts the file time string '132479040000000000' to a DateTime object, ignoring any file time strings containing '1601'.
 
    #>

    [CmdletBinding()]
    param (
        [string] $Timestring,
        [string] $Ignore = '*1601*'
    )
    Try {
        $DateTime = ([datetime]::FromFileTime($Timestring))
    }
    catch {
        $DateTime = $null
    }
    if ($null -eq $DateTime -or $Timestring -like $Ignore) {
        return $null
    }
    else {
        return $DateTime
    }
}
function Convert-ToTimeSpan {
    <#
    .SYNOPSIS
    Calculates the time span between two given DateTime values.
 
    .DESCRIPTION
    This function calculates the time span between two specified DateTime values. It takes a start time and an end time as input parameters and returns the TimeSpan object representing the duration between them.
 
    .PARAMETER StartTime
    Specifies the start DateTime value. If not provided, the current date and time will be used as the default.
 
    .PARAMETER EndTime
    Specifies the end DateTime value.
 
    .EXAMPLE
    Convert-ToTimeSpan -StartTime (Get-Date).AddDays(-5) -EndTime (Get-Date)
    # Calculates the time span between 5 days ago and today.
 
    .EXAMPLE
    Convert-ToTimeSpan -StartTime '2022-01-01' -EndTime '2022-01-10'
    # Calculates the time span between January 1, 2022, and January 10, 2022.
 
    #>

    [CmdletBinding()]
    param (
        [DateTime] $StartTime = (Get-Date),
        [DateTime] $EndTime
    )
    if ($StartTime -and $EndTime) {
        try {
            $TimeSpan = (New-TimeSpan -Start $StartTime -End $EndTime)
        }
        catch {
            $TimeSpan = $null
        }
    }
    if ($null -ne $TimeSpan) {
        return $TimeSpan
    }
    else {
        return $null
    }
}

function Convert-TwoArraysIntoOne {
    <#
    .SYNOPSIS
    Combines two arrays into a single array by pairing elements from each array.
 
    .DESCRIPTION
    This function takes two arrays as input and combines them into a single array by pairing elements from each array. It creates a new array where each element is a combination of an element from the first array and the corresponding element from the second array.
 
    .PARAMETER Object
    Specifies the first array containing elements to be combined.
 
    .PARAMETER ObjectToAdd
    Specifies the second array containing elements to be paired with elements from the first array.
 
    .EXAMPLE
    Convert-TwoArraysIntoOne -Object @("A", "B", "C") -ObjectToAdd @(1, 2, 3)
    # Combines the arrays ["A", "B", "C"] and [1, 2, 3] into a single array: ["A (1)", "B (2)", "C (3)"].
 
    .EXAMPLE
    $Array1 = @("Apple", "Banana", "Cherry")
    $Array2 = @(5, 10, 15)
    Convert-TwoArraysIntoOne -Object $Array1 -ObjectToAdd $Array2
    # Combines the arrays $Array1 and $Array2 into a single array where each element pairs an item from $Array1 with the corresponding item from $Array2.
 
    #>

    [CmdletBinding()]
    param (
        $Object,
        $ObjectToAdd
    )

    $Value = for ($i = 0; $i -lt $Object.Count; $i++) {
        "$($Object[$i]) ($($ObjectToAdd[$i]))"
    }
    return $Value
}
Function Convert-UAC {
    <#
    .SYNOPSIS
        Converts values from Events into proper format
 
    .DESCRIPTION
        Converts values from Events into proper format
 
    .PARAMETER UAC
        Parameter description
 
    .EXAMPLE
        Convert-UAC -UAC '%%1793'
        Convert-UAC -UAC '1793'
        Output: TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, RESERVED
 
        Convert-UAC -UAC '1793', '1794'
 
        Convert-UAC -UAC '121793'
        Output: PASSWD_CANT_CHANGE, ENCRYPTED_TEXT_PWD_ALLOWED, TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, INTERDOMAIN_TRUST_ACCOUNT, WORKSTATION_TRUST_ACCOUNT, RESERVED, RESERVED, DONT_EXPIRE_PASSWORD
 
        Convert-UAC -UAC 'C:\Onet33'
        Output: Same input as output
 
        Convert-UAC -UAC '121793' -OutputPerLine
        Output: One entry per line
            PASSWD_CANT_CHANGE
            ENCRYPTED_TEXT_PWD_ALLOWED
            TEMP_DUPLICATE_ACCOUNT
            NORMAL_ACCOUNT
            INTERDOMAIN_TRUST_ACCOUNT
            WORKSTATION_TRUST_ACCOUNT
            RESERVED
            RESERVED
            DONT_EXPIRE_PASSWORD
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string[]] $UAC,
        [string] $Separator
    )
    $Output = foreach ($String in $UAC) {
        $NumberAsString = $String.Replace('%', '') -as [int]
        if ($null -eq $NumberAsString) {
            return $UAC
        }

        $PropertyFlags = @(
            "SCRIPT",
            "ACCOUNTDISABLE",
            "RESERVED",
            "HOMEDIR_REQUIRED",
            "LOCKOUT",
            "PASSWD_NOTREQD",
            "PASSWD_CANT_CHANGE",
            "ENCRYPTED_TEXT_PWD_ALLOWED",
            "TEMP_DUPLICATE_ACCOUNT",
            "NORMAL_ACCOUNT",
            "RESERVED",
            "INTERDOMAIN_TRUST_ACCOUNT",
            "WORKSTATION_TRUST_ACCOUNT",
            "SERVER_TRUST_ACCOUNT",
            "RESERVED",
            "RESERVED",
            "DONT_EXPIRE_PASSWORD",
            "MNS_LOGON_ACCOUNT",
            "SMARTCARD_REQUIRED",
            "TRUSTED_FOR_DELEGATION",
            "NOT_DELEGATED",
            "USE_DES_KEY_ONLY",
            "DONT_REQ_PREAUTH",
            "PASSWORD_EXPIRED",
            "TRUSTED_TO_AUTH_FOR_DELEGATION",
            "RESERVED",
            "PARTIAL_SECRETS_ACCOUNT"
            "RESERVED"
            "RESERVED"
            "RESERVED"
            "RESERVED"
            "RESERVED"
        )
        1..($PropertyFlags.Length) | Where-Object { $NumberAsString -bAnd [math]::Pow(2, $_) } | ForEach-Object { $PropertyFlags[$_] }
    }
    if ($Separator -eq '') {
        $Output
    }
    else {
        $Output -join $Separator
    }
}
function Convert-UserAccountControl {
    <#
    .SYNOPSIS
    Converts the UserAccountControl flags to their corresponding names.
 
    .DESCRIPTION
    This function takes a UserAccountControl value and converts it into a human-readable format by matching the flags to their corresponding names.
 
    .PARAMETER UserAccountControl
    Specifies the UserAccountControl value to be converted.
 
    .PARAMETER Separator
    Specifies the separator to use when joining the converted flags. If not provided, the flags will be returned as a list.
 
    .EXAMPLE
    Convert-UserAccountControl -UserAccountControl 66048
    Outputs: "DONT_EXPIRE_PASSWORD, PASSWORD_EXPIRED"
 
    .EXAMPLE
    Convert-UserAccountControl -UserAccountControl 512 -Separator ', '
    Outputs: "NORMAL_ACCOUNT"
 
    #>

    [cmdletBinding()]
    param(
        [alias('UAC')][int] $UserAccountControl,
        [string] $Separator
    )
    $UserAccount = [ordered] @{
        "SCRIPT"                         = 1
        "ACCOUNTDISABLE"                 = 2
        "HOMEDIR_REQUIRED"               = 8
        "LOCKOUT"                        = 16
        "PASSWD_NOTREQD"                 = 32
        "ENCRYPTED_TEXT_PWD_ALLOWED"     = 128
        "TEMP_DUPLICATE_ACCOUNT"         = 256
        "NORMAL_ACCOUNT"                 = 512
        "INTERDOMAIN_TRUST_ACCOUNT"      = 2048
        "WORKSTATION_TRUST_ACCOUNT"      = 4096
        "SERVER_TRUST_ACCOUNT"           = 8192
        "DONT_EXPIRE_PASSWORD"           = 65536
        "MNS_LOGON_ACCOUNT"              = 131072
        "SMARTCARD_REQUIRED"             = 262144
        "TRUSTED_FOR_DELEGATION"         = 524288
        "NOT_DELEGATED"                  = 1048576
        "USE_DES_KEY_ONLY"               = 2097152
        "DONT_REQ_PREAUTH"               = 4194304
        "PASSWORD_EXPIRED"               = 8388608
        "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216
        "PARTIAL_SECRETS_ACCOUNT"        = 67108864
    }
    $Output = foreach ($_ in $UserAccount.Keys) {
        $binaryAnd = $UserAccount[$_] -band $UserAccountControl
        if ($binaryAnd -ne "0") {
            $_
        }
    }
    if ($Separator) {
        $Output -join $Separator
    }
    else {
        $Output
    }
}
function ConvertFrom-DistinguishedName {
    <#
    .SYNOPSIS
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .DESCRIPTION
    Converts a Distinguished Name to CN, OU, Multiple OUs or DC
 
    .PARAMETER DistinguishedName
    Distinguished Name to convert
 
    .PARAMETER ToOrganizationalUnit
    Converts DistinguishedName to Organizational Unit
 
    .PARAMETER ToDC
    Converts DistinguishedName to DC
 
    .PARAMETER ToDomainCN
    Converts DistinguishedName to Domain Canonical Name (CN)
 
    .PARAMETER ToCanonicalName
    Converts DistinguishedName to Canonical Name
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz'
    ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName
 
    Output:
    Przemyslaw Klys
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent
 
    Output:
    OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit
 
    Output:
    OU=Production,DC=ad,DC=evotec,DC=xyz
 
    .EXAMPLE
    $Con = @(
        'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz'
        'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz'
        'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz'
        'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl'
        'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz'
    )
 
    ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName
 
    Output:
    Windows Authorization Access Group
    Mmm
    e6d5fd00-385d-4e65-b02d-9da3493ed850
    Domain Controllers
    Microsoft Exchange Security Groups
 
    .EXAMPLEE
    ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
    ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
    ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName
 
    Output:
    ad.evotec.xyz
    ad.evotec.xyz\Production\Users
    ad.evotec.xyz\Production\Users\test
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(ParameterSetName = 'ToOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')]
        [Parameter(ParameterSetName = 'ToDC')]
        [Parameter(ParameterSetName = 'ToDomainCN')]
        [Parameter(ParameterSetName = 'Default')]
        [Parameter(ParameterSetName = 'ToLastName')]
        [Parameter(ParameterSetName = 'ToCanonicalName')]
        [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName,
        [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit,
        [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent,
        [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC,
        [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN,
        [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName,
        [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName
    )
    Process {
        foreach ($Distinguished in $DistinguishedName) {
            if ($ToDomainCN) {
                $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                $CN = $DN -replace ',DC=', '.' -replace "DC="
                if ($CN) {
                    $CN
                }
            }
            elseif ($ToOrganizationalUnit) {
                $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value
                if ($Value) {
                    $Value
                }
            }
            elseif ($ToMultipleOrganizationalUnit) {
                if ($IncludeParent) {
                    $Distinguished
                }
                while ($true) {

                    $Distinguished = $Distinguished -replace '^.+?,(?=..=)'
                    if ($Distinguished -match '^DC=') {
                        break
                    }
                    $Distinguished
                }
            }
            elseif ($ToDC) {

                $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1'
                if ($Value) {
                    $Value
                }
            }
            elseif ($ToLastName) {

                $NewDN = $Distinguished -split ",DC="
                if ($NewDN[0].Contains(",OU=")) {
                    [Array] $ChangedDN = $NewDN[0] -split ",OU="
                }
                elseif ($NewDN[0].Contains(",CN=")) {
                    [Array] $ChangedDN = $NewDN[0] -split ",CN="
                }
                else {
                    [Array] $ChangedDN = $NewDN[0]
                }
                if ($ChangedDN[0].StartsWith('CN=')) {
                    $ChangedDN[0] -replace 'CN=', ''
                }
                else {
                    $ChangedDN[0] -replace 'OU=', ''
                }
            }
            elseif ($ToCanonicalName) {
                $Domain = $null
                $Rest = $null
                foreach ($O in $Distinguished -split '(?<!\\),') {
                    if ($O -match '^DC=') {
                        $Domain += $O.Substring(3) + '.'
                    }
                    else {
                        $Rest = $O.Substring(3) + '\' + $Rest
                    }
                }
                if ($Domain -and $Rest) {
                    $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',')
                }
                elseif ($Domain) {
                    $Domain.Trim('.')
                }
                elseif ($Rest) {
                    $Rest.TrimEnd('\') -replace '\\,', ','
                }
            }
            else {
                $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$'

                $Found = $Distinguished -match $Regex
                if ($Found) {
                    $Matches.cn
                }
            }
        }
    }
}

function ConvertFrom-ErrorRecord {
    <#
    .SYNOPSIS
    Converts error records into a custom object with selected properties.
 
    .DESCRIPTION
    This function takes error records as input and converts them into a custom object with selected properties, making error records more readable.
 
    .PARAMETER ErrorRecord
    The error record(s) to convert. This parameter is mandatory when the input is an error record.
 
    .PARAMETER Exception
    The special stop exception raised by cmdlets with -ErrorAction Stop. This parameter is mandatory when the input is a stop exception.
 
    .EXAMPLE
    Get-ChildItem -Path 'C:\NonExistentFolder' -ErrorAction Stop | ConvertFrom-ErrorRecord
 
    This example will convert the error record generated by attempting to access a non-existent folder into a custom object with properties like Exception message, Reason, Target, Script name, Line number, and Column offset.
 
    .EXAMPLE
    $error[0] | ConvertFrom-ErrorRecord
 
    This example will convert the first error record in the $error automatic variable into a custom object with selected properties.
 
    #>

    param (
        # we receive either a legit error record...
        [Management.Automation.ErrorRecord[]]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ErrorRecord')]
        $ErrorRecord,

        # ...or a special stop exception which is raised by
        # cmdlets with -ErrorAction Stop
        [Management.Automation.ActionPreferenceStopException[]]
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'StopException')]
        $Exception
    )

    process {

        if ($PSCmdlet.ParameterSetName -eq 'StopException') {
            $ErrorRecord = $Exception.ErrorRecord
        }

        $ErrorRecord | ForEach-Object {
            [PSCustomObject]@{
                Exception = $_.Exception.Message
                Reason    = $_.CategoryInfo.Reason
                Target    = $_.CategoryInfo.TargetName
                Script    = $_.InvocationInfo.ScriptName
                Line      = $_.InvocationInfo.ScriptLineNumber
                Column    = $_.InvocationInfo.OffsetInLine
            }
        }
    }
}
function ConvertFrom-LanguageCode {
    <#
    .SYNOPSIS
    Converts a language code to its corresponding language name.
 
    .DESCRIPTION
    This function takes a language code as input and returns the corresponding language name.
 
    .PARAMETER LanguageCode
    The language code to convert to a language name.
 
    .EXAMPLE
    ConvertFrom-LanguageCode -LanguageCode 1033
    Returns: "English (United States)"
 
    .EXAMPLE
    ConvertFrom-LanguageCode -LanguageCode 1041
    Returns: "Japanese"
    #>

    [cmdletBinding()]
    param(
        [string] $LanguageCode
    )
    $LanguageCodeDictionary = @{
        '1'     = "Arabic"
        '4'     = "Chinese (Simplified)?? China"
        '9'     = "English"
        '1025'  = "Arabic (Saudi Arabia)"
        '1026'  = "Bulgarian"
        '1027'  = "Catalan"
        '1028'  = "Chinese (Traditional) Taiwan"
        '1029'  = "Czech"
        '1030'  = "Danish"
        '1031'  = "German (Germany)"
        '1032'  = "Greek"
        '1033'  = "English (United States)"
        '1034'  = "Spanish (Traditional Sort)"
        '1035'  = "Finnish"
        '1036'  = "French (France)"
        '1037'  = "Hebrew"
        '1038'  = "Hungarian"
        '1039'  = "Icelandic"
        '1040'  = "Italian (Italy)"
        '1041'  = "Japanese"
        '1042'  = "Korean"
        '1043'  = "Dutch (Netherlands)"
        '1044'  = "Norwegian (Bokmal)"
        '1045'  = "Polish"
        '1046'  = "Portuguese (Brazil)"
        '1047'  = "Rhaeto-Romanic"
        '1048'  = "Romanian"
        '1049'  = "Russian"
        '1050'  = "Croatian"
        '1051'  = "Slovak"
        '1052'  = "Albanian"
        '1053'  = "Swedish"
        '1054'  = "Thai"
        '1055'  = "Turkish"
        '1056'  = "Urdu"
        '1057'  = "Indonesian"
        '1058'  = "Ukrainian"
        '1059'  = "Belarusian"
        '1060'  = "Slovenian"
        '1061'  = "Estonian"
        '1062'  = "Latvian"
        '1063'  = "Lithuanian"
        '1065'  = "Persian"
        '1066'  = "Vietnamese"
        '1069'  = "Basque (Basque)"
        '1070'  = "Serbian"
        '1071'  = "Macedonian (FYROM)"
        '1072'  = "Sutu"
        '1073'  = "Tsonga"
        '1074'  = "Tswana"
        '1076'  = "Xhosa"
        '1077'  = "Zulu"
        '1078'  = "Afrikaans"
        '1080'  = "Faeroese"
        '1081'  = "Hindi"
        '1082'  = "Maltese"
        '1084'  = "Scottish Gaelic (United Kingdom)"
        '1085'  = "Yiddish"
        '1086'  = "Malay (Malaysia)"
        '2049'  = "Arabic (Iraq)"
        '2052'  = "Chinese (Simplified) PRC"
        '2055'  = "German (Switzerland)"
        '2057'  = "English (United Kingdom)"
        '2058'  = "Spanish (Mexico)"
        '2060'  = "French (Belgium)"
        '2064'  = "Italian (Switzerland)"
        '2067'  = "Dutch (Belgium)"
        '2068'  = "Norwegian (Nynorsk)"
        '2070'  = "Portuguese (Portugal)"
        '2072'  = "Romanian (Moldova)"
        '2073'  = "Russian (Moldova)"
        '2074'  = "Serbian (Latin)"
        '2077'  = "Swedish (Finland)"
        '3073'  = "Arabic (Egypt)"
        '3076'  = "Chinese Traditional (Hong Kong SAR)"
        '3079'  = "German (Austria)"
        '3081'  = "English (Australia)"
        '3082'  = "Spanish (International Sort)"
        '3084'  = "French (Canada)"
        '3098'  = "Serbian (Cyrillic)"
        '4097'  = "Arabic (Libya)"
        '4100'  = "Chinese Simplified (Singapore)"
        '4103'  = "German (Luxembourg)"
        '4105'  = "English (Canada)"
        '4106'  = "Spanish (Guatemala)"
        '4108'  = "French (Switzerland)"
        '5121'  = "Arabic (Algeria)"
        '5127'  = "German (Liechtenstein)"
        '5129'  = "English (New Zealand)"
        '5130'  = "Spanish (Costa Rica)"
        '5132'  = "French (Luxembourg)"
        '6145'  = "Arabic (Morocco)"
        '6153'  = "English (Ireland)"
        '6154'  = "Spanish (Panama)"
        '7169'  = "Arabic (Tunisia)"
        '7177'  = "English (South Africa)"
        '7178'  = "Spanish (Dominican Republic)"
        '8193'  = "Arabic (Oman)"
        '8201'  = "English (Jamaica)"
        '8202'  = "Spanish (Venezuela)"
        '9217'  = "Arabic (Yemen)"
        '9226'  = "Spanish (Colombia)"
        '10241' = "Arabic (Syria)"
        '10249' = "English (Belize)"
        '10250' = "Spanish (Peru)"
        '11265' = "Arabic (Jordan)"
        '11273' = "English (Trinidad)"
        '11274' = "Spanish (Argentina)"
        '12289' = "Arabic (Lebanon)"
        '12298' = "Spanish (Ecuador)"
        '13313' = "Arabic (Kuwait)"
        '13322' = "Spanish (Chile)"
        '14337' = "Arabic (U.A.E.)"
        '14346' = "Spanish (Uruguay)"
        '15361' = "Arabic (Bahrain)"
        '15370' = "Spanish (Paraguay)"
        '16385' = "Arabic (Qatar)"
        '16394' = "Spanish (Bolivia)"
        '17418' = "Spanish (El Salvador)"
        '18442' = "Spanish (Honduras)"
        '19466' = "Spanish (Nicaragua)"
        '20490' = "Spanish (Puerto Rico)"
    }
    $Output = $LanguageCodeDictionary[$LanguageCode]
    if ($Output) {
        $Output
    }
    else {
        "Unknown (Undocumented)"
    }
}
function ConvertFrom-NetbiosName {
    <#
    .SYNOPSIS
    Converts a NetBIOS name to its corresponding domain name and object name.
 
    .DESCRIPTION
    This function takes a NetBIOS name in the format 'Domain\Object' and converts it to the corresponding domain name and object name.
 
    .PARAMETER Identity
    Specifies the NetBIOS name(s) to convert.
 
    .EXAMPLE
    'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins' | ConvertFrom-NetbiosName
    Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names.
 
    .EXAMPLE
    ConvertFrom-NetbiosName -Identity 'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins'
    Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names.
 
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)]
        [string[]] $Identity
    )
    process {
        foreach ($Ident in $Identity) {
            if ($Ident -like '*\*') {
                $NetbiosWithObject = $Ident -split "\\"
                if ($NetbiosWithObject.Count -eq 2) {
                    $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])")
                    $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN
                    [PSCustomObject] @{
                        DomainName = $DomainName
                        Name       = $NetbiosWithObject[1]
                    }
                }
                else {

                    [PSCustomObject] @{
                        DomainName = ''
                        Name       = $Ident
                    }
                }
            }
            else {

                [PSCustomObject] @{
                    DomainName = ''
                    Name       = $Ident
                }
            }
        }
    }
}

function ConvertFrom-ObjectToString {
    <#
    .SYNOPSIS
    Helps with converting given objects to their string representation.
 
    .DESCRIPTION
     Helps with converting given objects to their string representation.
 
    .PARAMETER Objects
    Objects to convert to string representation.
 
    .PARAMETER IncludeProperties
    Properties to include in the string representation.
 
    .PARAMETER ExcludeProperties
    Properties to exclude from the string representation.
 
    .PARAMETER OutputType
    Type of the output object. Options are: Hashtable, Ordered, PSCustomObject. If not specified, the output type is hashtable (string)
 
    .PARAMETER NumbersAsString
    If specified, numbers are converted to strings. Default is number are presented in their (unquoted) numerica form
 
    .PARAMETER QuotePropertyNames
    If specified, all property names are quoted. Default: property names are quoted only if they contain spaces.
 
    .PARAMETER DateTimeFormat
    Format for DateTime values. Default: 'yyyy-MM-dd HH:mm:ss'
 
    .EXAMPLE
    Get-Process -Name "PowerShell" | ConvertFrom-ObjectToString -IncludeProperties 'ProcessName', 'Id', 'Handles'
 
    OUTPUT:
    @{
        'Handles' = '543'
        'Id' = '8092'
        'ProcessName' = 'powershell'
    }
 
    @{
        'Handles' = '636'
        'Id' = '11360'
        'ProcessName' = 'powershell'
    }
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Array] $Objects,
        [string[]] $IncludeProperties,
        [string[]] $ExcludeProperties,
        [ValidateSet('Hashtable', 'Ordered', 'PSCustomObject')][string] $OutputType = 'Hashtable',
        [switch] $NumbersAsString,
        [switch] $QuotePropertyNames,
        [string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss'
    )
    begin {
        if ($OutputType -eq 'Hashtable') {
            $Type = ''
        }
        elseif ($OutputType -eq 'Ordered') {
            $Type = '[Ordered] '
        }
        else {
            $Type = '[PSCustomObject] '
        }
    }
    process {
        filter IsNumeric() {
            return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64]  `
                -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] `
                -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
        }
        function GetFormattedPair () {
            # returns 'key' = <valuestring> or just <valuestring> if key is empty
            # valuestring is either $null, '<string>', or number
            param (
                [string] $Key,
                [object] $Value
            )
            if ($key -eq '') {
                $left = ''
            }
            elseif ($key -match '\s' -or $QuotePropertyNames) {
                $left = "'$Key' = "
            }
            else {
                $left = "$Key = "
            }
            if ($null -eq $value) {
                "$left`$null"
            }
            elseif ($Value -is [System.Collections.IList]) {
                $arrayStrings = foreach ($element in $Object.$Key) {
                    GetFormattedPair -Key '' -Value $element
                }
                "$left@(" + ($arrayStrings -join ', ') + ")"
            }
            elseif ($Value -is [System.Collections.IDictionary]) {
                if ($IncludeProperties -and $Key -notin $IncludeProperties) {
                    return
                }
                if ($Key -in $ExcludeProperties) {
                    return
                }
                $propertyString = foreach ($Key in $Value.Keys) {
                    GetFormattedPair -Key $key -Value $Value[$key]
                }
                "$left@{" + ($propertyString -join '; ') + "}"
            }
            elseif ($value -is [DateTime]) {
                "$left'$($Value.ToString($DateTimeFormat))'"
            }
            elseif (($value | IsNumeric) -and -not $NumbersAsString) {
                "$left$($Value)"
            }
            else {
                "$left'$($Value)'"
            }
        }

        foreach ($Object in $Objects) {
            if ($Object -is [System.Collections.IDictionary]) {
                Write-Host
                Write-Host -Object "$Type@{"
                foreach ($Key in $Object.Keys) {
                    if ($IncludeProperties -and $Key -notin $IncludeProperties) {
                        continue
                    }
                    if ($Key -in $ExcludeProperties) {
                        continue
                    }
                    Write-Host -Object " $(GetFormattedPair -Key $Key -Value $Object.$Key)" -ForegroundColor Cyan
                }
                Write-Host -Object "}"
            }
            elseif ($Object -is [Object]) {
                Write-Host
                Write-Host -Object "$Type@{"
                foreach ($Key in $Object.PSObject.Properties.Name) {
                    if ($IncludeProperties -and $Key -notin $IncludeProperties) {
                        continue
                    }
                    if ($Key -in $ExcludeProperties) {
                        continue
                    }
                    Write-Host -Object " $(GetFormattedPair -Key $Key -Value $Object.$Key)" -ForegroundColor Cyan
                }
                Write-Host -Object "}"
            }
            else {
                Write-Host -Object $Object
            }
        }
    }
}
Function ConvertFrom-OperationType {
    <#
    .SYNOPSIS
    Converts operation type codes to human-readable descriptions.
 
    .DESCRIPTION
    This function takes an operation type code and returns the corresponding human-readable description.
 
    .PARAMETER OperationType
    The operation type code to be converted.
 
    .EXAMPLE
    ConvertFrom-OperationType -OperationType '%%14674'
    Output: 'Value Added'
 
    .EXAMPLE
    ConvertFrom-OperationType -OperationType '%%14675'
    Output: 'Value Deleted'
 
    .EXAMPLE
    ConvertFrom-OperationType -OperationType '%%14676'
    Output: 'Unknown'
    #>

    param (
        [string] $OperationType
    )
    $Known = @{
        '%%14674' = 'Value Added'
        '%%14675' = 'Value Deleted'
        '%%14676' = 'Unknown'
    }
    foreach ($id in $OperationType) {
        if ($name = $Known[$id]) {
            return $name 
        }
    }
    return $OperationType
}
function ConvertFrom-ScriptBlock {
    <#
    .SYNOPSIS
    Converts a ScriptBlock into an array of strings, each representing a line of the script block.
 
    .DESCRIPTION
    This function takes a ScriptBlock as input and converts it into an array of strings, where each string represents a line of the script block.
 
    .PARAMETER ScriptBlock
    The ScriptBlock to be converted into an array of strings.
 
    .EXAMPLE
    ConvertFrom-ScriptBlock -ScriptBlock {
        $Variable1 = "Value1"
        $Variable2 = "Value2"
        Write-Host "Hello, World!"
    }
 
    This example will output an array containing the following strings:
    $Variable1 = "Value1"
    $Variable2 = "Value2"
    Write-Host "Hello, World!"
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [ScriptBlock] $ScriptBlock
    )
    [Array] $Output = foreach ($Line in $ScriptBlock.Ast.EndBlock.Statements.Extent) {
        [string] $Line + [System.Environment]::NewLine
    }
    return $Output
}
function ConvertFrom-SID {
    <#
    .SYNOPSIS
    Small command that can resolve SID values
 
    .DESCRIPTION
    Small command that can resolve SID values
 
    .PARAMETER SID
    Value to resolve
 
    .PARAMETER OnlyWellKnown
    Only resolve SID when it's well know SID. Otherwise return $null
 
    .PARAMETER OnlyWellKnownAdministrative
    Only resolve SID when it's administrative well know SID. Otherwise return $null
 
    .PARAMETER DoNotResolve
    Uses only dicrionary values without querying AD
 
    .EXAMPLE
    ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve
 
    .NOTES
    General notes
    #>

    [cmdletbinding(DefaultParameterSetName = 'Standard')]
    param(
        [Parameter(ParameterSetName = 'Standard')]
        [Parameter(ParameterSetName = 'OnlyWellKnown')]
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')]
        [string[]] $SID,
        [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown,
        [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative,
        [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve
    )

    $WellKnownAdministrative = @{
        'S-1-5-18'     = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\SYSTEM'
            SID        = 'S-1-5-18'
            DomainName = ''
            Type       = 'WellKnownAdministrative'
            Error      = ''
        }
        'S-1-5-32-544' = [PSCustomObject] @{
            Name       = 'BUILTIN\Administrators'
            SID        = 'S-1-5-32-544'
            DomainName = ''
            Type       = 'WellKnownAdministrative'
            Error      = ''
        }
    }
    $wellKnownSIDs = @{
        'S-1-0'                                                           = [PSCustomObject] @{
            Name       = 'Null AUTHORITY'
            SID        = 'S-1-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-0-0'                                                         = [PSCustomObject] @{
            Name       = 'NULL SID'
            SID        = 'S-1-0-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-1'                                                           = [PSCustomObject] @{
            Name       = 'WORLD AUTHORITY'
            SID        = 'S-1-1'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-1-0'                                                         = [PSCustomObject] @{
            Name       = 'Everyone'
            SID        = 'S-1-1-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-2'                                                           = [PSCustomObject] @{
            Name       = 'LOCAL AUTHORITY'
            SID        = 'S-1-2'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-2-0'                                                         = [PSCustomObject] @{
            Name       = 'LOCAL'
            SID        = 'S-1-2-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-2-1'                                                         = [PSCustomObject] @{
            Name       = 'CONSOLE LOGON'
            SID        = 'S-1-2-1'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-3'                                                           = [PSCustomObject] @{
            Name       = 'CREATOR AUTHORITY'
            SID        = 'S-1-3'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-3-0'                                                         = [PSCustomObject] @{
            Name       = 'CREATOR OWNER'
            SID        = 'S-1-3-0'
            DomainName = ''
            Type       = 'WellKnownAdministrative'
            Error      = ''
        }
        'S-1-3-1'                                                         = [PSCustomObject] @{
            Name       = 'CREATOR GROUP'
            SID        = 'S-1-3-1'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-3-2'                                                         = [PSCustomObject] @{
            Name       = 'CREATOR OWNER SERVER'
            SID        = 'S-1-3-2'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-3-3'                                                         = [PSCustomObject] @{
            Name       = 'CREATOR GROUP SERVER'
            SID        = 'S-1-3-3'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-3-4'                                                         = [PSCustomObject] @{
            Name       = 'OWNER RIGHTS'
            SID        = 'S-1-3-4'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-0'                                                      = [PSCustomObject] @{
            Name       = 'NT SERVICE\ALL SERVICES'
            SID        = 'S-1-5-80-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-4'                                                           = [PSCustomObject] @{
            Name       = 'Non-unique Authority'
            SID        = 'S-1-4'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5'                                                           = [PSCustomObject] @{
            Name       = 'NT AUTHORITY'
            SID        = 'S-1-5'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-1'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\DIALUP'
            SID        = 'S-1-5-1'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-2'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\NETWORK'
            SID        = 'S-1-5-2'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-3'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\BATCH'
            SID        = 'S-1-5-3'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-4'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\INTERACTIVE'
            SID        = 'S-1-5-4'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-6'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\SERVICE'
            SID        = 'S-1-5-6'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-7'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\ANONYMOUS LOGON'
            SID        = 'S-1-5-7'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-8'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\PROXY'
            SID        = 'S-1-5-8'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-9'                                                         = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS'
            SID        = 'S-1-5-9'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-10'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\SELF'
            SID        = 'S-1-5-10'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-11'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\Authenticated Users'
            SID        = 'S-1-5-11'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-12'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\RESTRICTED'
            SID        = 'S-1-5-12'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-13'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\TERMINAL SERVER USER'
            SID        = 'S-1-5-13'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-14'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON'
            SID        = 'S-1-5-14'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-15'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\This Organization'
            SID        = 'S-1-5-15'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-17'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\IUSR'
            SID        = 'S-1-5-17'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-18'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\SYSTEM'
            SID        = 'S-1-5-18'
            DomainName = ''
            Type       = 'WellKnownAdministrative'
            Error      = ''
        }
        'S-1-5-19'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\LOCAL SERVICE'
            SID        = 'S-1-5-19'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-20'                                                        = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\NETWORK SERVICE'
            SID        = 'S-1-5-20'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-544'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Administrators'
            SID        = 'S-1-5-32-544'
            DomainName = ''
            Type       = 'WellKnownAdministrative'
            Error      = ''
        }
        'S-1-5-32-545'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Users'
            SID        = 'S-1-5-32-545'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-546'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Guests'
            SID        = 'S-1-5-32-546'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-547'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Power Users'
            SID        = 'S-1-5-32-547'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-548'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Account Operators'
            SID        = 'S-1-5-32-548'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-549'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Server Operators'
            SID        = 'S-1-5-32-549'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-550'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Print Operators'
            SID        = 'S-1-5-32-550'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-551'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Backup Operators'
            SID        = 'S-1-5-32-551'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-552'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Replicators'
            SID        = 'S-1-5-32-552'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-64-10'                                                     = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\NTLM Authentication'
            SID        = 'S-1-5-64-10'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-64-14'                                                     = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\SChannel Authentication'
            SID        = 'S-1-5-64-14'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-64-21'                                                     = [PSCustomObject] @{
            Name       = 'NT AUTHORITY\Digest Authentication'
            SID        = 'S-1-5-64-21'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80'                                                        = [PSCustomObject] @{
            Name       = 'NT SERVICE'
            SID        = 'S-1-5-80'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-83-0'                                                      = [PSCustomObject] @{
            Name       = 'NT VIRTUAL MACHINE\Virtual Machines'
            SID        = 'S-1-5-83-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-0'                                                        = [PSCustomObject] @{
            Name       = 'Untrusted Mandatory Level'
            SID        = 'S-1-16-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-4096'                                                     = [PSCustomObject] @{
            Name       = 'Low Mandatory Level'
            SID        = 'S-1-16-4096'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-8192'                                                     = [PSCustomObject] @{
            Name       = 'Medium Mandatory Level'
            SID        = 'S-1-16-8192'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-8448'                                                     = [PSCustomObject] @{
            Name       = 'Medium Plus Mandatory Level'
            SID        = 'S-1-16-8448'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-12288'                                                    = [PSCustomObject] @{
            Name       = 'High Mandatory Level'
            SID        = 'S-1-16-12288'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-16384'                                                    = [PSCustomObject] @{
            Name       = 'System Mandatory Level'
            SID        = 'S-1-16-16384'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-20480'                                                    = [PSCustomObject] @{
            Name       = 'Protected Process Mandatory Level'
            SID        = 'S-1-16-20480'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-16-28672'                                                    = [PSCustomObject] @{
            Name       = 'Secure Process Mandatory Level'
            SID        = 'S-1-16-28672'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-554'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Pre-Windows 2000 Compatible Access'
            SID        = 'S-1-5-32-554'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-555'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Remote Desktop Users'
            SID        = 'S-1-5-32-555'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-556'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Network Configuration Operators'
            SID        = 'S-1-5-32-556'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-557'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Incoming Forest Trust Builders'
            SID        = 'S-1-5-32-557'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-558'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Performance Monitor Users'
            SID        = 'S-1-5-32-558'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-559'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Performance Log Users'
            SID        = 'S-1-5-32-559'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-560'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Windows Authorization Access Group'
            SID        = 'S-1-5-32-560'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-561'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Terminal Server License Servers'
            SID        = 'S-1-5-32-561'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-562'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Distributed COM Users'
            SID        = 'S-1-5-32-562'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-568'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\IIS_IUSRS'
            SID        = 'S-1-5-32-568'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-569'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Cryptographic Operators'
            SID        = 'S-1-5-32-569'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-573'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Event Log Readers'
            SID        = 'S-1-5-32-573'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-574'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Certificate Service DCOM Access'
            SID        = 'S-1-5-32-574'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-575'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\RDS Remote Access Servers'
            SID        = 'S-1-5-32-575'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-576'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\RDS Endpoint Servers'
            SID        = 'S-1-5-32-576'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-577'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\RDS Management Servers'
            SID        = 'S-1-5-32-577'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-578'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Hyper-V Administrators'
            SID        = 'S-1-5-32-578'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-579'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Access Control Assistance Operators'
            SID        = 'S-1-5-32-579'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-32-580'                                                    = [PSCustomObject] @{
            Name       = 'BUILTIN\Remote Management Users'
            SID        = 'S-1-5-32-580'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-90-0'                                                      = [PSCustomObject] @{
            Name       = 'Window Manager\Window Manager Group'
            SID        = 'S-1-5-90-0'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420'  = [PSCustomObject] @{
            Name       = 'NT SERVICE\WdiServiceHost'
            SID        = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' = [PSCustomObject] @{
            Name       = 'NT SERVICE\MSSQLSERVER'
            SID        = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430'  = [PSCustomObject] @{
            Name       = 'NT SERVICE\SQLSERVERAGENT'
            SID        = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' = [PSCustomObject] @{
            Name       = 'NT SERVICE\SQLTELEMETRY'
            SID        = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451'   = [PSCustomObject] @{
            Name       = 'NT SERVICE\ADSync'
            SID        = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
        'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' = [PSCustomObject] @{
            Name       = 'NT Service\himds'
            SID        = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083'
            DomainName = ''
            Type       = 'WellKnownGroup'
            Error      = ''
        }
    }
    foreach ($S in $SID) {
        if ($OnlyWellKnownAdministrative) {

            if ($WellKnownAdministrative[$S]) {
                $WellKnownAdministrative[$S]
            }
        }
        elseif ($OnlyWellKnown) {

            if ($wellKnownSIDs[$S]) {
                $wellKnownSIDs[$S]
            }
        }
        else {

            if ($wellKnownSIDs[$S]) {
                $wellKnownSIDs[$S]
            }
            else {
                if ($DoNotResolve) {
                    if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") {

                        [PSCustomObject] @{
                            Name       = $S
                            SID        = $S
                            DomainName = '' 
                            Type       = 'Administrative'
                            Error      = ''
                        }
                    }
                    else {

                        [PSCustomObject] @{
                            Name       = $S
                            SID        = $S
                            DomainName = ''
                            Error      = ''
                            Type       = 'NotAdministrative'
                        }
                    }
                }
                else {
                    if (-not $Script:LocalComputerSID) {
                        $Script:LocalComputerSID = Get-LocalComputerSid
                    }
                    try {
                        if ($S.Length -le 18) {
                            $Type = 'NotAdministrative'
                            $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                            [PSCustomObject] @{
                                Name       = $Name
                                SID        = $S
                                DomainName = ''
                                Type       = $Type
                                Error      = ''
                            }
                        }
                        else {
                            if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") {
                                $Type = 'Administrative'
                            }
                            else {
                                $Type = 'NotAdministrative'
                            }
                            $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value
                            [PSCustomObject] @{
                                Name       = $Name
                                SID        = $S
                                DomainName = if ($S -like "$Script:LocalComputerSID*") {
                                    '' 
                                }
                                else {
 (ConvertFrom-NetbiosName -Identity $Name).DomainName 
                                }
                                Type       = $Type
                                Error      = ''
                            }
                        }
                    }
                    catch {

                        [PSCustomObject] @{
                            Name       = $S
                            SID        = $S
                            DomainName = ''
                            Error      = $_.Exception.Message -replace [environment]::NewLine, ' '
                            Type       = 'Unknown'
                        }
                    }
                }
            }
        }
    }
}
function ConvertFrom-X500Address {
    <#
    .SYNOPSIS
    Converts an X500 address to a readable email address.
 
    .DESCRIPTION
    This function converts an X500 address to a readable email address by removing unnecessary characters and formatting it properly.
 
    .PARAMETER IMCEAEXString
    The X500 address string to be converted.
 
    .PARAMETER Full
    Indicates whether to return the full email address or just the username part.
 
    .EXAMPLE
    By default returns string without @evotec.pl in the end. This is because the string from NDR needs domain name removed to be able to add it back to Exchange
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=5209048016da47689b4421790ad1763f-EVOTEC+20PL+20Recepcja+20G@evotec.pl'
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=8bcad655e07c46788fe1f796162cd87f-EVOTEC+20PL+20Recepcja+20G@evotec.pl'
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl'
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl'
 
    .EXAMPLE
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=5209048016da47689b4421790ad1763f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' -Full
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=8bcad655e07c46788fe1f796162cd87f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' -Full
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' -Full
    ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' -Full
    #>

    param(
        [string] $IMCEAEXString,
        [switch] $Full
    )
    $Final = $IMCEAEXString.Replace("IMCEAEX-", "").Replace("_", "/").Replace("+20", " ").Replace("+28", "(").Replace("+29", ")").Replace("+2E", ".").Replace("+2C", ",").Replace("+5F", "_")
    if ($Full) {
        return $Final
    }
    else {
        return ($Final -split '@')[0]
    }
}
function ConvertTo-DistinguishedName {
    <#
    .SYNOPSIS
    Converts CanonicalName to DistinguishedName
 
    .DESCRIPTION
    Converts CanonicalName to DistinguishedName for 3 different options
 
    .PARAMETER CanonicalName
    One or multiple canonical names
 
    .PARAMETER ToOU
    Converts CanonicalName to OrganizationalUnit DistinguishedName
 
    .PARAMETER ToObject
    Converts CanonicalName to Full Object DistinguishedName
 
    .PARAMETER ToDomain
    Converts CanonicalName to Domain DistinguishedName
 
    .EXAMPLE
 
    $CanonicalObjects = @(
    'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins'
    'ad.evotec.xyz/Production/Accounts/Special/SADM Testing 2'
    )
    $CanonicalOU = @(
        'ad.evotec.xyz/Production/Groups/Security/NetworkAdministration'
        'ad.evotec.xyz/Production'
    )
 
    $CanonicalDomain = @(
        'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins'
        'ad.evotec.pl'
        'ad.evotec.xyz'
        'test.evotec.pl'
        'ad.evotec.xyz/Production'
    )
    $CanonicalObjects | ConvertTo-DistinguishedName -ToObject
    $CanonicalOU | ConvertTo-DistinguishedName -ToOU
    $CanonicalDomain | ConvertTo-DistinguishedName -ToDomain
 
    Output:
    CN=ITR03_AD Admins,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz
    CN=SADM Testing 2,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz
    Output2:
    OU=NetworkAdministration,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz
    OU=Production,DC=ad,DC=evotec,DC=xyz
    Output3:
    DC=ad,DC=evotec,DC=xyz
    DC=ad,DC=evotec,DC=pl
    DC=ad,DC=evotec,DC=xyz
    DC=test,DC=evotec,DC=pl
    DC=ad,DC=evotec,DC=xyz
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'ToDomain')]
    param(
        [Parameter(ParameterSetName = 'ToOU')]
        [Parameter(ParameterSetName = 'ToObject')]
        [Parameter(ParameterSetName = 'ToDomain')]
        [alias('Identity', 'CN')][Parameter(ValueFromPipeline, Mandatory, ValueFromPipelineByPropertyName, Position = 0)][string[]] $CanonicalName,
        [Parameter(ParameterSetName = 'ToOU')][switch] $ToOU,
        [Parameter(ParameterSetName = 'ToObject')][switch] $ToObject,
        [Parameter(ParameterSetName = 'ToDomain')][switch] $ToDomain
    )
    Process {
        foreach ($CN in $CanonicalName) {
            if ($ToObject) {
                $ADObject = $CN.Replace(',', '\,').Split('/')
                [string]$DN = "CN=" + $ADObject[$ADObject.count - 1]
                for ($i = $ADObject.count - 2; $i -ge 1; $i--) {
                    $DN += ",OU=" + $ADObject[$i]
                }
                $ADObject[0].split(".") | ForEach-Object {
                    $DN += ",DC=" + $_
                }
            }
            elseif ($ToOU) {
                $ADObject = $CN.Replace(',', '\,').Split('/')
                [string]$DN = "OU=" + $ADObject[$ADObject.count - 1]
                for ($i = $ADObject.count - 2; $i -ge 1; $i--) {
                    $DN += ",OU=" + $ADObject[$i]
                }
                $ADObject[0].split(".") | ForEach-Object {
                    $DN += ",DC=" + $_
                }
            }
            else {
                $ADObject = $CN.Replace(',', '\,').Split('/')

                $DN = 'DC=' + $ADObject[0].Replace('.', ',DC=')
            }
            $DN
        }
    }
}
function ConvertTo-FlatHashtable {
    <#
    .SYNOPSIS
    Converts nested hashtable into flat hashtable using delimiter
 
    .DESCRIPTION
    Converts nested hashtable into flat hashtable using delimiter
 
    .PARAMETER InputObject
    Ordered Dictionary or Hashtable
 
    .PARAMETER Delimiter
    Delimiter for key name when merging nested hashtables. By default colon is used
 
    .EXAMPLE
    ConvertTo-FlatHashTable -InputObject ([ordered] @{
        RootEntry = 'OK1'
        Parent = @{
            Child1 = 'OK2'
            Child2 = 'Ok3'
        }
        ParentDifferent = @{
            Child7 = 'NotOk'
            Child8 = @{
                Child10 = 'OKLetsSee'
                Child11 = @{
                    SpecialCase = 'Oooop'
                }
            }
        }
    }) | Format-Table *
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $InputObject,
        [string] $Delimiter = ':',
        [Parameter(DontShow)][string] $Name
    )
    Begin {
        $Output = [ordered] @{}
    }
    Process {
        foreach ($Key in $InputObject.Keys) {
            if ($Name) {
                $MergedName = "$Name$($Delimiter)$Key"
            }
            else {
                $MergedName = $Key
            }
            if ($InputObject[$Key] -is [System.Collections.IDictionary]) {

                $Found = ConvertTo-FlatHashtable -InputObject $InputObject[$Key] -Name $MergedName
                $Output = $Output + $Found
            }
            else {
                $Output[$MergedName] = $InputObject[$Key]
            }
        }
    }
    End {
        $Output
    }
}
Function ConvertTo-FlatObject {
    <#
    .SYNOPSIS
    Flattends a nested object into a single level object.
 
    .DESCRIPTION
    Flattends a nested object into a single level object.
 
    .PARAMETER Objects
    The object (or objects) to be flatten.
 
    .PARAMETER Separator
    The separator used between the recursive property names
 
    .PARAMETER Base
    The first index name of an embedded array:
    - 1, arrays will be 1 based: <Parent>.1, <Parent>.2, <Parent>.3, …
    - 0, arrays will be 0 based: <Parent>.0, <Parent>.1, <Parent>.2, …
    - "", the first item in an array will be unnamed and than followed with 1: <Parent>, <Parent>.1, <Parent>.2, …
 
    .PARAMETER Depth
    The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop.
 
    .PARAMETER Uncut
    The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop.
 
    .PARAMETER ExcludeProperty
    The propertys to be excluded from the output.
 
    .EXAMPLE
    $Object3 = [PSCustomObject] @{
        "Name" = "Przemyslaw Klys"
        "Age" = "30"
        "Address" = @{
            "Street" = "Kwiatowa"
            "City" = "Warszawa"
 
            "Country" = [ordered] @{
                "Name" = "Poland"
            }
            List = @(
                [PSCustomObject] @{
                    "Name" = "Adam Klys"
                    "Age" = "32"
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = "33"
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = 30
                }
                [PSCustomObject] @{
                    "Name" = "Justyna Klys"
                    "Age" = $null
                }
            )
        }
        ListTest = @(
            [PSCustomObject] @{
                "Name" = "Sława Klys"
                "Age" = "33"
            }
        )
    }
 
    $Object3 | ConvertTo-FlatObject
 
    .NOTES
    Based on https://powersnippets.com/convertto-flatobject/
    #>

    [CmdletBinding()]
    Param (
        [Parameter(ValueFromPipeLine)][Object[]]$Objects,
        [String]$Separator = ".",
        [ValidateSet("", 0, 1)]$Base = 1,
        [int]$Depth = 5,
        [string[]] $ExcludeProperty,
        [Parameter(DontShow)][String[]]$Path,
        [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject
    )
    Begin {
        $InputObjects = [System.Collections.Generic.List[Object]]::new()
    }
    Process {
        foreach ($O in $Objects) {
            if ($null -ne $O) {
                $InputObjects.Add($O)
            }
        }
    }
    End {
        If ($PSBoundParameters.ContainsKey("OutputObject")) {
            $Object = $InputObjects[0]
            $Iterate = [ordered] @{}
            if ($null -eq $Object) {
            }
            elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') {
                $Object = $Object.ToString()
            }
            elseif ($Depth) {
                $Depth--
                If ($Object -is [System.Collections.IDictionary]) {
                    $Iterate = $Object
                }
                elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) {
                    $i = $Base
                    foreach ($Item in $Object.GetEnumerator()) {
                        $NewObject = [ordered] @{}
                        If ($Item -is [System.Collections.IDictionary]) {
                            foreach ($Key in $Item.Keys) {
                                if ($Key -notin $ExcludeProperty) {
                                    $NewObject[$Key] = $Item[$Key]
                                }
                            }
                        }
                        elseif ($Item -isnot [Array] -and $Item -isnot [System.Collections.IEnumerable]) {
                            foreach ($Prop in $Item.PSObject.Properties) {
                                if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) {
                                    $NewObject["$($Prop.Name)"] = $Item.$($Prop.Name)
                                }
                            }
                        }
                        else {
                            $NewObject = $Item
                        }
                        $Iterate["$i"] = $NewObject
                        $i += 1
                    }
                }
                else {
                    foreach ($Prop in $Object.PSObject.Properties) {
                        if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) {
                            $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name)
                        }
                    }
                }
            }
            If ($Iterate.Keys.Count) {
                foreach ($Key in $Iterate.Keys) {
                    if ($Key -notin $ExcludeProperty) {
                        ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty
                    }
                }
            }
            else {
                $Property = $Path -Join $Separator
                if ($Property) {

                    if ($Object -is [System.Collections.IDictionary] -and $Object.Keys.Count -eq 0) {
                        $OutputObject[$Property] = $null
                    }
                    else {
                        $OutputObject[$Property] = $Object
                    }
                }
            }
        }
        elseif ($InputObjects.Count -gt 0) {
            foreach ($ItemObject in $InputObjects) {
                $OutputObject = [ordered]@{}
                ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty
                [PSCustomObject] $OutputObject
            }
        }
    }
}
function ConvertTo-Identity {
    <#
    .SYNOPSIS
    Converts an identity to its corresponding information.
 
    .DESCRIPTION
    This function converts an identity to its corresponding information, such as Name, SID, Type, and Class. It retrieves information from Active Directory based on the provided identity.
 
    .PARAMETER Identity
    Specifies the identity to convert.
 
    .PARAMETER ADAdministrativeGroups
    Specifies the Active Directory administrative groups.
 
    .PARAMETER Forest
    Specifies the forest name.
 
    .PARAMETER ExcludeDomains
    Specifies the domains to exclude.
 
    .PARAMETER IncludeDomains
    Specifies the domains to include.
 
    .PARAMETER ExtendedForestInformation
    Specifies additional information about the forest.
 
    .EXAMPLE
    ConvertTo-Identity -Identity "JohnDoe" -Forest "example.com" -IncludeDomains "domain1", "domain2" -ExcludeDomains "domain3" -ADAdministrativeGroups $ADGroups -ExtendedForestInformation $ExtendedInfo
    Converts the identity "JohnDoe" in the forest "example.com", including domains "domain1" and "domain2" while excluding "domain3", using the specified administrative groups and extended forest information.
 
    .NOTES
    File Name : ConvertTo-Identity.ps1
    Prerequisite : This function requires Active Directory PowerShell module.
    #>

    [cmdletBinding()]
    param(
        [string] $Identity,
        [System.Collections.IDictionary] $ADAdministrativeGroups,

        [alias('ForestName')][string] $Forest,
        [string[]] $ExcludeDomains,
        [alias('Domain', 'Domains')][string[]] $IncludeDomains,
        [System.Collections.IDictionary] $ExtendedForestInformation
    )
    Begin {
        if (-not $ExtendedForestInformation) {
            $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        else {
            $ForestInformation = $ExtendedForestInformation
        }
        if (-not $ADAdministrativeGroups) {
            $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation
        }
        if (-not $Script:GlobalCacheIdentity) {
            $Script:GlobalCacheIdentity = @{ }
        }
    }
    Process {
        $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($Identity)"]
        if ($AdministrativeGroup) {
            [PSCustomObject] @{
                Name  = $Identity
                SID   = $AdministrativeGroup.SID.Value
                Type  = 'Administrative'
                Class = $AdministrativeGroup.ObjectClass
                Error = ''
            }
        }
        else {
            if ($Identity -like '*@*') {
                Write-Warning "ConvertTo-Identity - Not implemented."
            }
            elseif ($Identity -like '*\*') {
                if ($Script:GlobalCacheIdentity[$Identity]) {
                    $Script:GlobalCacheIdentity[$Identity]
                }
                else {
                    $MyIdentity = $Identity.Split("\")
                    $DNSRoot = $ForestInformation['DomainsExtendedNetBIOS'][$($MyIdentity[0])]['DNSRoot']
                    $QueryServer = $ForestInformation['QueryServers'][$DNSRoot]['HostName'][0]
                    $ADObject = Get-ADObject -Filter "SamAccountName -eq '$($MyIdentity[1])'" -Server $QueryServer -Properties AdminCount, CanonicalName, Name, sAMAccountName, DisplayName, DistinguishedName, ObjectClass, objectSid

                    if ($ADObject) {
                        $Script:GlobalCacheIdentity[$Identity] = [PSCustomObject] @{
                            Name  = $Identity
                            SID   = $ADObject.objectSid.Value
                            Type  = 'NotAdministrative'
                            Class = $AdObject.ObjectClass
                            Error = ''
                        }
                        $Script:GlobalCacheIdentity[$Identity]
                    }
                    else {
                        [PSCustomObject] @{
                            Name  = $Identity
                            SID   = $Identity
                            Type  = 'Unknown'
                            Class = 'unknown'
                            Error = 'Object not found.'
                        }
                    }
                }
            }
            elseif ($Identity -like '*-*-*-*') {
                $Data = ConvertFrom-SID -SID $Identity
                if ($Data) {
                    if ($Data.Error) {
                        [PSCustomObject] @{
                            Name  = $Data.Name
                            SID   = $Data.Sid
                            Type  = $Data.Type
                            Class = 'unknown'
                            Error = $Data.Error
                        }
                    }
                    else {

                        $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($Data.Name)"]
                        if ($AdministrativeGroup) {
                            [PSCustomObject] @{
                                Name  = $Data.Name
                                SID   = $AdministrativeGroup.SID.Value
                                Type  = 'Administrative'
                                Class = $AdministrativeGroup.ObjectClass
                                Error = ''
                            }
                        }
                        else {
                            [PSCustomObject] @{
                                Name  = $Data.Name
                                SID   = $Data.Sid
                                Type  = $Data.Type
                                Class = ''
                                Error = $Data.Error
                            }
                        }
                    }
                }
                else {
                    [PSCustomObject] @{
                        Name  = $Identity
                        SID   = $Identity
                        Type  = 'Unknown'
                        Class = 'unknown'
                        Error = 'SID not found'
                    }
                }
            }
            else {
                [PSCustomObject] @{
                    Name  = $Identity
                    SID   = $Identity
                    Type  = 'Unknown'
                    Class = 'unknown'
                    Error = 'Identity unknown'
                }
            }
        }
    }
    End {
    }
}
function ConvertTo-ImmutableID {
    <#
    .SYNOPSIS
    Converts an Active Directory user's ObjectGUID to an ImmutableID.
 
    .DESCRIPTION
    This function takes an Active Directory user object or a GUID as input and converts the ObjectGUID to an ImmutableID, which is commonly used in Azure AD.
 
    .PARAMETER User
    Specifies the Active Directory user object to convert. This parameter is mutually exclusive with the 'ObjectGUID' parameter.
 
    .PARAMETER ObjectGUID
    Specifies the GUID to convert to ImmutableID. This parameter is mutually exclusive with the 'User' parameter.
 
    .EXAMPLE
    ConvertTo-ImmutableID -User $ADUser
    Converts the ObjectGUID of the specified Active Directory user to an ImmutableID.
 
    .EXAMPLE
    ConvertTo-ImmutableID -ObjectGUID "12345678-1234-1234-1234-1234567890AB"
    Converts the specified GUID to an ImmutableID.
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ParameterSetName = 'User')]
        [alias('ADuser')]
        [Microsoft.ActiveDirectory.Management.ADAccount] $User,

        [Parameter(Mandatory = $false, ParameterSetName = 'Guid')]
        [alias('GUID')]
        [string] $ObjectGUID
    )
    if ($User) {
        if ($User.ObjectGUID) {
            $ObjectGUID = $User.ObjectGuid
        }
    }
    if ($ObjectGUID) {
        $ImmutableID = [System.Convert]::ToBase64String(($User.ObjectGUID).ToByteArray())
        return $ImmutableID
    }
    return
}
function ConvertTo-JsonLiteral {
    <#
    .SYNOPSIS
    Converts an object to a JSON-formatted string.
 
    .DESCRIPTION
    The ConvertTo-Json cmdlet converts any object to a string in JavaScript Object Notation (JSON) format. The properties are converted to field names, the field values are converted to property values, and the methods are removed.
 
    .PARAMETER Object
    Specifies the objects to convert to JSON format. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe an object to ConvertTo-JsonLiteral
 
    .PARAMETER Depth
    Specifies how many levels of contained objects are included in the JSON representation. The default value is 0.
 
    .PARAMETER AsArray
    Outputs the object in array brackets, even if the input is a single object.
 
    .PARAMETER DateTimeFormat
    Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss"
 
    .PARAMETER NumberAsString
    Provides an alternative serialization option that converts all numbers to their string representation.
 
    .PARAMETER BoolAsString
    Provides an alternative serialization option that converts all bool to their string representation.
 
    .PARAMETER PropertyName
    Uses PropertyNames provided by user (only works with Force)
 
    .PARAMETER NewLineFormat
    Provides a way to configure how new lines are converted for property names
 
    .PARAMETER NewLineFormatProperty
    Provides a way to configure how new lines are converted for values
 
    .PARAMETER PropertyName
    Allows passing property names to be used for custom objects (hashtables and alike are unaffected)
 
    .PARAMETER ArrayJoin
    Forces any array to be a string regardless of depth level
 
    .PARAMETER ArrayJoinString
    Uses defined string or char for array join. By default it uses comma with a space when used.
 
    .PARAMETER Force
    Forces using property names from first object or given thru PropertyName parameter
 
    .EXAMPLE
    Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral
 
    .EXAMPLE
    Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -Depth 3
 
    .EXAMPLE
    Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{
        NewLineCarriage = '\r\n'
        NewLine = "\n"
        Carriage = "\r"
    } -NumberAsString -BoolAsString
 
    .EXAMPLE
    Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NumberAsString -BoolAsString -DateTimeFormat "yyyy-MM-dd HH:mm:ss"
 
    .EXAMPLE
    # Keep in mind this advanced replace will break ConvertFrom-Json, but it's sometimes useful for projects like PSWriteHTML
    Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{
        NewLineCarriage = '\r\n'
        NewLine = "\n"
        Carriage = "\r"
    } -NumberAsString -BoolAsString -AdvancedReplace @{ '.' = '\.'; '$' = '\$' }
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('InputObject')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, Mandatory)][Array] $Object,
        [int] $Depth,
        [switch] $AsArray,
        [string] $DateTimeFormat = "yyyy-MM-dd HH:mm:ss",
        [switch] $NumberAsString,
        [switch] $BoolAsString,
        [System.Collections.IDictionary] $NewLineFormat = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $NewLineFormatProperty = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $AdvancedReplace,
        [string] $ArrayJoinString,
        [switch] $ArrayJoin,
        [string[]]$PropertyName,
        [switch] $Force
    )
    Begin {
        $TextBuilder = [System.Text.StringBuilder]::new()
        $CountObjects = 0
        filter IsNumeric() {
            return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64]  `
                -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] `
                -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
        }
        filter IsOfType() {
            return $_ -is [bool] -or $_ -is [char] -or $_ -is [datetime] -or $_ -is [string] `
                -or $_ -is [timespan] -or $_ -is [URI] `
                -or $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] `
                -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] `
                -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
        }
        [int] $MaxDepth = $Depth
        [int] $InitialDepth = 0
    }
    Process {
        for ($a = 0; $a -lt $Object.Count; $a++) {
            $CountObjects++
            if ($CountObjects -gt 1) {
                $null = $TextBuilder.Append(',')
            }
            if ($Object[$a] -is [System.Collections.IDictionary]) {

                $null = $TextBuilder.AppendLine("{")
                for ($i = 0; $i -lt ($Object[$a].Keys).Count; $i++) {
                    $Property = ([string[]]$Object[$a].Keys)[$i] 
                    $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $null = $TextBuilder.Append("`"$DisplayProperty`":")
                    $Value = ConvertTo-StringByType -Value $Object[$a][$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace
                    $null = $TextBuilder.Append("$Value")
                    if ($i -ne ($Object[$a].Keys).Count - 1) {
                        $null = $TextBuilder.AppendLine(',')
                    }
                }
                $null = $TextBuilder.Append("}")
            }
            elseif ($Object[$a] | IsOfType) {
                $Value = ConvertTo-StringByType -Value $Object[$a] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace
                $null = $TextBuilder.Append($Value)
            }
            else {
                $null = $TextBuilder.AppendLine("{")
                if ($Force -and -not $PropertyName) {
                    $PropertyName = $Object[0].PSObject.Properties.Name
                }
                elseif ($Force -and $PropertyName) {
                }
                else {
                    $PropertyName = $Object[$a].PSObject.Properties.Name
                }
                $PropertyCount = 0
                foreach ($Property in $PropertyName) {
                    $PropertyCount++
                    $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $null = $TextBuilder.Append("`"$DisplayProperty`":")
                    $Value = ConvertTo-StringByType -Value $Object[$a].$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace

                    $null = $TextBuilder.Append("$Value")
                    if ($PropertyCount -ne $PropertyName.Count) {
                        $null = $TextBuilder.AppendLine(',')
                    }
                }
                $null = $TextBuilder.Append("}")
            }
            $InitialDepth = 0
        }
    }
    End {
        if ($CountObjects -gt 1 -or $AsArray) {
            "[$($TextBuilder.ToString())]"
        }
        else {
            $TextBuilder.ToString()
        }
    }
}
function ConvertTo-NormalizedString {
    <#
    .SYNOPSIS
    Converts a string to a normalized string
 
    .DESCRIPTION
    Converts a string to a normalized string
 
    .PARAMETER String
    The string to convert
 
    .EXAMPLE
    ConvertTo-NormalizedString -String "café"
 
    .EXAMPLE
    "café" | ConvertTo-NormalizedString
 
    .EXAMPLE
    ConvertTo-NormalizedString -String "café"
    "café" | ConvertTo-NormalizedString
    'Helène' | ConvertTo-NormalizedString
    "Przemysław Kłys and Helène" | ConvertTo-NormalizedString
 
    .EXAMPLE
    "äöüß" | ConvertTo-NormalizedString
    ConvertTo-NormalizedString -String "café"
    "café" | ConvertTo-NormalizedString
    'Helène' | ConvertTo-NormalizedString
    "Przemysław Kłys and Helène" | ConvertTo-NormalizedString
    "kłys" | ConvertTo-NormalizedString
    "ąęćśł" | ConvertTo-NormalizedString
    "Michael Roßbach" | ConvertTo-NormalizedString
    "öüóőúéáűí" | ConvertTo-NormalizedString
    "ß" | ConvertTo-NormalizedString
    "Un été de Raphaël" | ConvertTo-NormalizedString
    ("äâûê", "éèà", "ùçä") | ConvertTo-NormalizedString
    "Fore ðære mærðe…" | ConvertTo-NormalizedString
    "ABC-abc-ČŠŽ-čšž" | ConvertTo-NormalizedString
    "Æ×Þ°±ß…" | ConvertTo-NormalizedString
 
    .NOTES
    General notes
    #>
#
    [CmdletBinding()]
    param(
        [parameter(ValueFromPipeline)][string[]] $String,
        [switch] $Simplify
    )
    Begin {
        $SpecialCasesGerman = @{
            [char] "ä" = "a"
            [char] "ö" = "o"
            [char] "ü" = "u"
            [char] "ß" = "ss"
            [char] "Ö" = "O"
            [char] "Ü" = "U"
            [char] "Ä" = "A"
        }
        $SpecialCasesGermanGramatical = @{
            [char] "ä" = "ae"
            [char] "ö" = "oe"
            [char] "ü" = "ue"
            [char] "ß" = "ss"
            [char] "Ö" = "Oe"
            [char] "Ü" = "Ue"
            [char] "Ä" = "Ae"
        }

        $SpecialCases = @{

            [char]"ø" = "o"
            [char]"Ø" = "O"
            [char]"Å" = "A"
            [char]'ð' = 'd'
            [char]'Æ' = 'AE'
            [char]'æ' = 'ae'
            [char]'Þ' = 'TH'
            [char]'þ' = 'th'
            [char]'×' = 'X'
            [char]'°' = 'o'
            [char]'±' = 'p'
            [char]'ç' = 'c'
            [char]'Ç' = 'C'
            [char]"…" = "..."
            [char]"ï" = "i"
            [char]"Ï" = "I"
            [char]"ű" = "u"
            [char]"ő" = "o"
            [char]"á" = "a"
            [char]"é" = "e"
            [char]"í" = "i"
            [char]"ó" = "o"
            [char]"ú" = "u"
            [char]"ý" = "y"
            [char]"ĺ" = "l"
            [char]"ŕ" = "r"
            [char]"č" = "c"
            [char]"ď" = "d"
            [char]"ľ" = "l"
            [char]"ň" = "n"
            [char]"š" = "s"
            [char]"ť" = "t"
            [char]"ž" = "z"
            [char]"ô" = "o"
            [char]"ą" = "a"
            [char]"ę" = "e"
            [char]"è" = "e"
            [char]"ć" = "c"
            [char]"ś" = "s"
            [char]"ź" = "z"
            [char]"ł" = "l"
            [char]"ń" = "n"
            [char]"Ű" = "U"
            [char]"Ő" = "O"
            [char]"Á" = "A"
            [char]"É" = "E"
            [char]"Í" = "I"
            [char]"Ó" = "O"
            [char]"Ú" = "U"
            [char]"Ý" = "Y"
            [char]"Ĺ" = "L"
            [char]"Ŕ" = "R"
            [char]"Č" = "C"
            [char]"Ď" = "D"
            [char]"Ľ" = "L"
            [char]"Ň" = "N"
            [char]"Š" = "S"
            [char]"Ť" = "T"
            [char]"Ž" = "Z"
            [char]"Ô" = "O"
            [char]"Ą" = "A"
            [char]"Ę" = "E"
            [char]"Ć" = "C"
            [char]"Ś" = "S"
            [char]"Ź" = "Z"
            [char]"Ł" = "L"
            [char]"Ń" = "N"
            "_"       = " "
        }
    }
    Process {
        foreach ($S in $String) {

            $sb = [System.Text.StringBuilder]::new()
            foreach ($Char in $S.ToCharArray()) {
                if ($Simplify -and $SpecialCasesGerman.ContainsKey($Char)) {
                    [void] $sb.Append($SpecialCasesGerman[$Char])
                }
                elseif ($SpecialCasesGermanGramatical.ContainsKey($Char)) {
                    [void] $sb.Append($SpecialCasesGermanGramatical[$Char])
                }
                elseif ($SpecialCases.ContainsKey($Char)) {
                    [void] $sb.Append($SpecialCases[$Char])
                }
                else {
                    [void] $sb.Append($Char)
                }
            }
            $S = $sb.ToString()

            $normalizedString = $S.Normalize([System.Text.NormalizationForm]::FormD)

            $sb = [System.Text.StringBuilder]::new()
            for ($i = 0; $i -lt $normalizedString.Length; $i++) {
                $c = $normalizedString[$i]
                if ([Globalization.CharUnicodeInfo]::GetUnicodeCategory($c) -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
                    [void]$sb.Append($c)
                }
            }
            $S = $sb.ToString().Normalize([System.Text.NormalizationForm]::FormC)

            $sb = [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($S))
            $sb
        }
    }
}
function ConvertTo-OperatingSystem {
    <#
    .SYNOPSIS
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .DESCRIPTION
    Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD
 
    .PARAMETER OperatingSystem
    Operating System as returned by Active Directory
 
    .PARAMETER OperatingSystemVersion
    Operating System Version as returned by Active Directory
 
    .EXAMPLE
    $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object {
        $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion
        Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force
        $_
    }
    $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table
 
    .EXAMPLE
    $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
    ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [string] $OperatingSystem,
        [string] $OperatingSystemVersion
    )

    if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') {
        $Systems = @{

            '10.0 (22621)' = 'Windows 11 22H2'
            '10.0 (22000)' = 'Windows 11 21H2'
            '10.0 (19045)' = 'Windows 10 22H2'
            '10.0 (19044)' = 'Windows 10 21H2'
            '10.0 (19043)' = 'Windows 10 21H1'
            '10.0 (19042)' = 'Windows 10 20H2'
            '10.0 (19041)' = 'Windows 10 2004'
            '10.0 (18898)' = 'Windows 10 Insider Preview'
            '10.0 (18363)' = "Windows 10 1909"
            '10.0 (18362)' = "Windows 10 1903"
            '10.0 (17763)' = "Windows 10 1809"
            '10.0 (17134)' = "Windows 10 1803"
            '10.0 (16299)' = "Windows 10 1709"
            '10.0 (15063)' = "Windows 10 1703"
            '10.0 (14393)' = "Windows 10 1607"
            '10.0 (10586)' = "Windows 10 1511"
            '10.0 (10240)' = "Windows 10 1507"

            '10.0.22621'   = 'Windows 11 22H2'
            '10.0.22000'   = 'Windows 11 21H2'
            '10.0.19045'   = 'Windows 10 22H2'
            '10.0.19044'   = 'Windows 10 21H2'
            '10.0.19043'   = 'Windows 10 21H1'
            '10.0.19042'   = 'Windows 10 20H2'
            '10.0.19041'   = 'Windows 10 2004'
            '10.0.18898'   = 'Windows 10 Insider Preview'
            '10.0.18363'   = "Windows 10 1909"
            '10.0.18362'   = "Windows 10 1903"
            '10.0.17763'   = "Windows 10 1809"
            '10.0.17134'   = "Windows 10 1803"
            '10.0.16299'   = "Windows 10 1709"
            '10.0.15063'   = "Windows 10 1703"
            '10.0.14393'   = "Windows 10 1607"
            '10.0.10586'   = "Windows 10 1511"
            '10.0.10240'   = "Windows 10 1507"

            '22621'        = 'Windows 11 22H2'
            '22000'        = 'Windows 11 21H2'
            '19045'        = 'Windows 10 22H2'
            '19044'        = 'Windows 10 21H2'
            '19043'        = 'Windows 10 21H1'
            '19042'        = 'Windows 10 20H2'
            '19041'        = 'Windows 10 2004'
            '18898'        = 'Windows 10 Insider Preview'
            '18363'        = "Windows 10 1909"
            '18362'        = "Windows 10 1903"
            '17763'        = "Windows 10 1809"
            '17134'        = "Windows 10 1803"
            '16299'        = "Windows 10 1709"
            '15063'        = "Windows 10 1703"
            '14393'        = "Windows 10 1607"
            '10586'        = "Windows 10 1511"
            '10240'        = "Windows 10 1507"
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) {
            $System = $OperatingSystemVersion
        }
    }
    elseif ($OperatingSystem -like 'Windows Server*') {

        $Systems = @{

            '10.0 (20348)' = 'Windows Server 2022'
            '10.0 (19042)' = 'Windows Server 2019 20H2'
            '10.0 (19041)' = 'Windows Server 2019 2004'
            '10.0 (18363)' = 'Windows Server 2019 1909'
            '10.0 (18362)' = "Windows Server 2019 1903" 
            '10.0 (17763)' = "Windows Server 2019 1809" 
            '10.0 (17134)' = "Windows Server 2016 1803" 
            '10.0 (14393)' = "Windows Server 2016 1607"
            '6.3 (9600)'   = 'Windows Server 2012 R2'
            '6.1 (7601)'   = 'Windows Server 2008 R2'
            '5.2 (3790)'   = 'Windows Server 2003'

            '10.0.20348'   = 'Windows Server 2022'
            '10.0.19042'   = 'Windows Server 2019 20H2'
            '10.0.19041'   = 'Windows Server 2019 2004'
            '10.0.18363'   = 'Windows Server 2019 1909'
            '10.0.18362'   = "Windows Server 2019 1903" 
            '10.0.17763'   = "Windows Server 2019 1809"  
            '10.0.17134'   = "Windows Server 2016 1803" 
            '10.0.14393'   = "Windows Server 2016 1607"
            '6.3.9600'     = 'Windows Server 2012 R2'
            '6.1.7601'     = 'Windows Server 2008 R2' 
            '5.2.3790'     = 'Windows Server 2003' 

            '20348'        = 'Windows Server 2022'
            '19042'        = 'Windows Server 2019 20H2'
            '19041'        = 'Windows Server 2019 2004'
            '18363'        = 'Windows Server 2019 1909'
            '18362'        = "Windows Server 2019 1903" 
            '17763'        = "Windows Server 2019 1809" 
            '17134'        = "Windows Server 2016 1803" 
            '14393'        = "Windows Server 2016 1607"
            '9600'         = 'Windows Server 2012 R2'
            '7601'         = 'Windows Server 2008 R2'
            '3790'         = 'Windows Server 2003'
        }
        $System = $Systems[$OperatingSystemVersion]
        if (-not $System) {
            $System = $OperatingSystemVersion
        }
    }
    else {
        $System = $OperatingSystem
    }
    if ($System) {
        $System
    }
    else {
        'Unknown'
    }
}

function ConvertTo-OrderedDictionary {
    <#
    .SYNOPSIS
    Converts a hashtable into an ordered dictionary.
 
    .DESCRIPTION
    This function takes a hashtable as input and converts it into an ordered dictionary. The ordered dictionary maintains the order of elements as they were added to the hashtable.
 
    .PARAMETER HashTable
    Specifies the hashtable to be converted into an ordered dictionary.
 
    .EXAMPLE
    $HashTable = @{
        "Key3" = "Value3"
        "Key1" = "Value1"
        "Key2" = "Value2"
    }
    ConvertTo-OrderedDictionary -HashTable $HashTable
    # Outputs an ordered dictionary where the keys are in the order they were added to the hashtable.
 
    #>

    [CmdletBinding()]
    Param (
        [parameter(Mandatory = $true, ValueFromPipeline = $true)] $HashTable
    )

    $OrderedDictionary = [ordered]@{ }
    if ($HashTable -is [System.Collections.IDictionary]) {
        $Keys = $HashTable.Keys | Sort-Object
        foreach ($_ in $Keys) {
            $OrderedDictionary.Add($_, $HashTable[$_])
        }
    }
    elseif ($HashTable -is [System.Collections.ICollection]) {
        for ($i = 0; $i -lt $HashTable.count; $i++) {
            $OrderedDictionary.Add($i, $HashTable[$i])
        }
    }
    else {
        Write-Error "ConvertTo-OrderedDictionary - Wrong input type."
    }

    return $OrderedDictionary
}
function ConvertTo-PrettyObject {
    <#
    .SYNOPSIS
    Command to help with converting standard objects that could be nested objects into single level PSCustomObject
 
    .DESCRIPTION
    Command to help with converting standard objects that could be nested objects into single level PSCustomObject
    This is a help command for PSWriteHTML module and probably PSWriteOffice module to create tables from objects
    and make sure those tables are not nested and can be easily converted to HTML or Office tables without having to manually flatten them
 
    .PARAMETER Object
     Specifies the objects to convert to pretty format. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe an object to ConvertTo-JsonLiteral
 
    .PARAMETER DateTimeFormat
    Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss"
 
    .PARAMETER NumberAsString
    Provides an alternative serialization option that converts all numbers to their string representation.
 
    .PARAMETER BoolAsString
    Provides an alternative serialization option that converts all bool to their string representation.
 
    .PARAMETER PropertyName
    Uses PropertyNames provided by user (only works with Force)
 
    .PARAMETER NewLineFormat
    Provides a way to configure how new lines are converted for property names
 
    .PARAMETER NewLineFormatProperty
    Provides a way to configure how new lines are converted for values
 
    .PARAMETER PropertyName
    Allows passing property names to be used for custom objects (hashtables and alike are unaffected)
 
    .PARAMETER ArrayJoin
    Forces any array to be a string regardless of depth level
 
    .PARAMETER ArrayJoinString
    Uses defined string or char for array join. By default it uses comma with a space when used.
 
    .PARAMETER Force
    Forces using property names from first object or given thru PropertyName parameter
 
    .EXAMPLE
    $Test1 = [PSCustomObject] @{
        Number = 1
        Number2 = 2.2
        Bool = $false
        Array = @(
            'C:\Users\1Password.exe'
            "C:\Users\Ooops.exe"
            "\\EvoWin\c$\Users\przemyslaw klys\AppData\Local\1password\This is other\7\1Password.exe"
            "\\EvoWin\c$\Users\przemyslaw.klys\AppData\Local\1password\This is other\7\1Password.exe"
        )
        EmptyArray = @()
        EmptyList = [System.Collections.Generic.List[string]]::new()
        HashTable = @{
            NumberAgain = 2
            OrderedDictionary = [ordered] @{
                String = 'test'
                HashTable = @{
                    StringAgain = "oops"
                }
            }
            Array = @(
                'C:\Users\1Password.exe'
                "C:\Users\Ooops.exe"
                "\\EvoWin\c$\Users\przemyslaw klys\AppData\Local\1password\This is other\7\1Password.exe"
                "\\EvoWin\c$\Users\przemyslaw.klys\AppData\Local\1password\This is other\7\1Password.exe"
            )
        }
        DateTime = Get-Date
    }
 
    $Test1 | ConvertTo-PrettyObject -ArrayJoinString "," -ArrayJoin | ConvertTo-Json | ConvertFrom-Json
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [alias('InputObject')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, Mandatory)][Array] $Object,
        [int] $Depth,
        [switch] $AsArray,
        [string] $DateTimeFormat = "yyyy-MM-dd HH:mm:ss",
        [switch] $NumberAsString,
        [switch] $BoolAsString,
        [System.Collections.IDictionary] $NewLineFormat = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $NewLineFormatProperty = @{
            NewLineCarriage = '\r\n'
            NewLine         = "\n"
            Carriage        = "\r"
        },
        [System.Collections.IDictionary] $AdvancedReplace,
        [string] $ArrayJoinString,
        [switch] $ArrayJoin,
        [string[]]$PropertyName,
        [switch] $Force
    )
    Begin {
        filter IsNumeric() {
            return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64]  `
                -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] `
                -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
        }
        filter IsOfType() {
            return $_ -is [bool] -or $_ -is [char] -or $_ -is [datetime] -or $_ -is [string] `
                -or $_ -is [timespan] -or $_ -is [URI] `
                -or $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] `
                -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] `
                -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal]
        }
    }
    Process {
        for ($a = 0; $a -lt $Object.Count; $a++) {
            $NewObject = [ordered] @{}
            if ($Object[$a] -is [System.Collections.IDictionary]) {

                for ($i = 0; $i -lt ($Object[$a].Keys).Count; $i++) {
                    $Property = ([string[]]$Object[$a].Keys)[$i]
                    $DisplayProperty = $Property.Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $Value = $Object[$a].$Property

                    if ($null -eq $Value) {
                        $NewObject[$DisplayProperty] = ""
                    }
                    elseif ($Value -is [string]) {
                        foreach ($Key in $AdvancedReplace.Keys) {
                            $Value = $Value.Replace($Key, $AdvancedReplace[$Key])
                        }
                        $NewObject[$DisplayProperty] = $Value.Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                    }
                    elseif ($Value -is [DateTime]) {
                        $NewObject[$DisplayProperty] = $Object[$a].$Property.ToString($DateTimeFormat)
                    }
                    elseif ($Value -is [bool]) {
                        if ($BoolAsString) {
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {
                            $NewObject[$DisplayProperty] = $Value
                        }
                    }
                    elseif ($Value -is [System.Collections.IDictionary]) {

                        $NewObject[$DisplayProperty] = "$Value"
                    }
                    elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) {
                        if ($ArrayJoin) {
                            $Value = $Value -join $ArrayJoinString
                            $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {

                            $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                    }
                    elseif ($Value -is [System.Enum]) {
                        $NewObject[$DisplayProperty] = ($Value).ToString()
                    }
                    elseif (($Value | IsNumeric) -eq $true) {
                        $Value = $($Value).ToString().Replace(',', '.')
                        if ($NumberAsString) {
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {
                            $NewObject[$DisplayProperty] = $Value
                        }
                    }
                    elseif ($Value -is [PSObject]) {

                        $NewObject[$DisplayProperty] = "$Value"
                    }
                    else {
                        $Value = $Value.ToString().Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                        $NewObject[$DisplayProperty] = "$Value"
                    }
                }
                [PSCustomObject] $NewObject
            }
            elseif ($Object[$a] | IsOfType) {

                $Object[$a]
            }
            else {
                if ($Force -and -not $PropertyName) {
                    $PropertyName = $Object[0].PSObject.Properties.Name
                }
                elseif ($Force -and $PropertyName) {
                }
                else {
                    $PropertyName = $Object[$a].PSObject.Properties.Name
                }
                foreach ($Property in $PropertyName) {
                    $DisplayProperty = $Property.Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage)
                    $Value = $Object[$a].$Property
                    if ($null -eq $Value) {
                        $NewObject[$DisplayProperty] = ""
                    }
                    elseif ($Value -is [string]) {
                        foreach ($Key in $AdvancedReplace.Keys) {
                            $Value = $Value.Replace($Key, $AdvancedReplace[$Key])
                        }
                        $NewObject[$DisplayProperty] = $Value.Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                    }
                    elseif ($Value -is [DateTime]) {
                        $NewObject[$DisplayProperty] = $Object[$a].$Property.ToString($DateTimeFormat)
                    }
                    elseif ($Value -is [bool]) {
                        if ($BoolAsString) {
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {
                            $NewObject[$DisplayProperty] = $Value
                        }
                    }
                    elseif ($Value -is [System.Collections.IDictionary]) {

                        $NewObject[$DisplayProperty] = "$Value"
                    }
                    elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) {
                        if ($ArrayJoin) {
                            $Value = $Value -join $ArrayJoinString
                            $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {

                            $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                    }
                    elseif ($Value -is [System.Enum]) {
                        $NewObject[$DisplayProperty] = ($Value).ToString()
                    }
                    elseif (($Value | IsNumeric) -eq $true) {

                        if ($NumberAsString) {
                            $NewObject[$DisplayProperty] = "$Value"
                        }
                        else {
                            $NewObject[$DisplayProperty] = $Value
                        }
                    }
                    elseif ($Value -is [PSObject]) {

                        $NewObject[$DisplayProperty] = "$Value"
                    }
                    else {
                        $Value = $Value.ToString().Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage)
                        $NewObject[$DisplayProperty] = "$Value"
                    }
                }
                [PSCustomObject] $NewObject
            }
        }
    }
}
function ConvertTo-SID {
    <#
    .SYNOPSIS
    Converts a given identity to a Security Identifier (SID).
 
    .DESCRIPTION
    This function takes one or more identity strings and converts them to their corresponding Security Identifiers (SIDs). It caches the results for faster lookup.
 
    .PARAMETER Identity
    Specifies the identity strings to be converted to SIDs.
 
    .EXAMPLE
    ConvertTo-SID -Identity 'Administrator'
    Converts the 'Administrator' identity to its corresponding SID.
 
    .EXAMPLE
    ConvertTo-SID -Identity 'Guest', 'User1'
    Converts the 'Guest' and 'User1' identities to their corresponding SIDs.
 
    #>

    [cmdletBinding()]
    param(
        [string[]] $Identity
    )
    Begin {
        if (-not $Script:GlobalCacheSidConvert) {
            $Script:GlobalCacheSidConvert = @{ }
        }
    }
    Process {
        foreach ($Ident in $Identity) {
            if ($Script:GlobalCacheSidConvert[$Ident]) {
                $Script:GlobalCacheSidConvert[$Ident]
            }
            else {
                try {
                    $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{
                        Name  = $Ident
                        Sid   = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value
                        Error = ''
                    }
                }
                catch {
                    [PSCustomObject] @{
                        Name  = $Ident
                        Sid   = ''
                        Error = $_.Exception.Message
                    }
                }
                $Script:GlobalCacheSidConvert[$Ident]
            }
        }
    }
    End {
    }
}
function Find-DatesCurrentDayMinusDayX ($days) {
    <#
    .SYNOPSIS
    Finds the date range for the current day minus a specified number of days.
 
    .DESCRIPTION
    This function calculates the start and end dates for the current day minus a specified number of days.
 
    .PARAMETER days
    Specifies the number of days to subtract from the current day.
 
    .EXAMPLE
    Find-DatesCurrentDayMinusDayX -days 1
    Returns the date range for yesterday.
 
    .EXAMPLE
    Find-DatesCurrentDayMinusDayX -days 7
    Returns the date range for a week ago.
 
    #>

    $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays( - $Days)
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddDays( - $Days).AddMilliseconds(-1)

    $DateParameters = @{
        DateFrom = $DateTodayStart
        DateTo   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesCurrentDayMinuxDaysX ($days) {
    <#
    .SYNOPSIS
    Finds the date range for the current day minus a specified number of days.
 
    .DESCRIPTION
    This function calculates the start and end dates for the current day minus a specified number of days.
 
    .PARAMETER days
    Specifies the number of days to subtract from the current day.
 
    .EXAMPLE
    Find-DatesCurrentDayMinuxDaysX -days 1
    Returns the date range for yesterday.
 
    .EXAMPLE
    Find-DatesCurrentDayMinuxDaysX -days 7
    Returns the date range for a week ago.
    #>

    $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays( - $Days)
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1)

    $DateParameters = @{
        DateFrom = $DateTodayStart
        DateTo   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesCurrentHour () {
    <#
    .SYNOPSIS
    Finds the start and end dates for the current hour.
 
    .DESCRIPTION
    This function calculates the start and end dates for the current hour.
 
    .EXAMPLE
    PS C:\> Find-DatesCurrentHour
    DateFrom DateTo
    -------- ------
    10/20/2021 12:00:00 AM 10/20/2021 1:00:00 AM
    #>

    $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0)
    $DateTodayEnd = $DateTodayStart.AddHours(1)

    $DateParameters = @{
        DateFrom = $DateTodayStart
        DateTo   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesDayPrevious () {
    <#
    .SYNOPSIS
    Finds the date parameters for the previous day.
 
    .DESCRIPTION
    This function calculates the date parameters for the previous day based on the current date.
 
    .EXAMPLE
    Find-DatesDayPrevious
    Returns the date parameters for the previous day.
 
    #>

    $DateToday = (Get-Date).Date
    $DateYesterday = $DateToday.AddDays(-1)

    $DateParameters = @{
        DateFrom = $DateYesterday
        DateTo   = $dateToday
    }
    return $DateParameters
}
function Find-DatesDayToday () {
    <#
    .SYNOPSIS
    Finds the start and end dates of the current day.
 
    .DESCRIPTION
    This function calculates the start and end dates of the current day based on the current date.
    #>

    $DateToday = (Get-Date).Date
    $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1)

    $DateParameters = @{
        DateFrom = $DateToday
        DateTo   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesMonthCurrent () {
    <#
    .SYNOPSIS
    Finds the start and end dates of the current month.
 
    .DESCRIPTION
    This function calculates the start and end dates of the current month based on the current date.
 
    .EXAMPLE
    Find-DatesMonthCurrent
    Returns the start and end dates of the current month.
 
    #>

    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1)

    $DateParameters = @{
        DateFrom = $DateMonthFirstDay
        DateTo   = $DateMonthLastDay
    }
    return $DateParameters
}
function Find-DatesMonthPast ([bool] $Force) {
    <#
    .SYNOPSIS
    Finds the dates for the previous month based on the current date.
 
    .DESCRIPTION
    This function calculates the date range for the previous month based on the current date. It returns the start and end dates of the previous month.
 
    .PARAMETER Force
    If set to $true, the function will always return the date range for the previous month, regardless of the current date.
 
    .EXAMPLE
    Find-DatesMonthPast -Force $false
    Returns $null if the current date is not the first day of the month.
 
    .EXAMPLE
    Find-DatesMonthPast -Force $true
    Returns the date range for the previous month even if the current date is not the first day of the month.
    #>

    $DateToday = (Get-Date).Date
    $DateMonthFirstDay = (Get-Date -Day 1).Date
    $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1)

    if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) {
        $DateParameters = @{
            DateFrom = $DateMonthPreviousFirstDay
            DateTo   = $DateMonthFirstDay
        }
        return $DateParameters
    }
    else {
        return $null
    }
}
function Find-DatesPastHour () {
    <#
    .SYNOPSIS
    Finds the date range for the past hour.
 
    .DESCRIPTION
    This function calculates the date range for the past hour, starting from the beginning of the previous hour up to the current hour.
 
    .EXAMPLE
    Find-DatesPastHour
    Returns a hashtable with DateFrom and DateTo keys representing the date range for the past hour.
 
    #>

    $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0
    $DateTodayStart = $DateTodayEnd.AddHours(-1)

    $DateParameters = @{
        DateFrom = $DateTodayStart
        DateTo   = $DateTodayEnd
    }
    return $DateParameters
}
function Find-DatesPastWeek($DayName) {
    <#
    .SYNOPSIS
    Finds the date range for the past week based on the specified day.
 
    .DESCRIPTION
    This function calculates the date range for the past week based on the specified day of the week.
 
    .PARAMETER DayName
    The day of the week to use as a reference for finding the past week's date range.
 
    .EXAMPLE
    Find-DatesPastWeek -DayName "Monday"
    Returns the date range for the past week starting from the previous Monday.
 
    .EXAMPLE
    Find-DatesPastWeek -DayName "Friday"
    Returns the date range for the past week starting from the previous Friday.
 
    #>

    $DateTodayStart = Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0
    if ($DateTodayStart.DayOfWeek -ne $DayName) {
        return $null
    }
    $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7)
    $DateParameters = @{
        DateFrom = $DateTodayEnd
        DateTo   = $DateTodayStart
    }
    return $DateParameters
}
function Find-DatesQuarterCurrent ([bool] $Force) {
    <#
    .SYNOPSIS
    Finds the start and end dates of the current quarter.
 
    .DESCRIPTION
    This function calculates the start and end dates of the current quarter based on the current date.
 
    .PARAMETER Force
    If set to $true, forces the function to recalculate the dates even if they have been previously calculated.
 
    .EXAMPLE
    Find-DatesQuarterCurrent -Force $false
    Returns the start and end dates of the current quarter without recalculating if already calculated.
 
    .EXAMPLE
    Find-DatesQuarterCurrent -Force $true
    Forces the function to recalculate and returns the start and end dates of the current quarter.
 
    #>

    $Today = (Get-Date)
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)
    $DateParameters = @{
        DateFrom = $StartDate
        DateTo   = $EndDate
    }
    return $DateParameters
}
function Find-DatesQuarterLast ([bool] $Force) {
    <#
    .SYNOPSIS
    Finds the start and end dates of the last quarter.
 
    .DESCRIPTION
    This function calculates the start and end dates of the last quarter based on the current date or a specified date.
 
    .PARAMETER Force
    If set to $true, forces the function to return the last quarter dates even if they were previously retrieved.
 
    .EXAMPLE
    Find-DatesQuarterLast -Force
    Returns the start and end dates of the last quarter regardless of previous retrieval.
 
    .EXAMPLE
    Find-DatesQuarterLast -Force $false
    Returns $null if the last quarter dates were already retrieved.
 
    #>

    #https://blogs.technet.microsoft.com/dsheehan/2017/09/21/use-powershell-to-determine-the-first-day-of-the-current-calendar-quarter/
    $Today = (Get-Date).AddDays(-90)
    $Yesterday = ((Get-Date).AddDays(-1))
    $Quarter = [Math]::Ceiling($Today.Month / 3)
    $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3))
    $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date
    $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1)

    if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) {
        $DateParameters = @{
            DateFrom = $StartDate
            DateTo   = $EndDate
        }
        return $DateParameters
    }
    else {
        return $null
    }
}
function Set-DnsServerIpAddress {
    <#
    .SYNOPSIS
    Sets the DNS server IP addresses on a specified computer for a given network interface.
 
    .DESCRIPTION
    This function allows you to set the DNS server IP addresses on a specified computer for a given network interface. It checks if the computer is online before attempting to set the DNS server addresses.
 
    .PARAMETER ComputerName
    Specifies the name of the computer where the DNS server IP addresses will be set.
 
    .PARAMETER NicName
    Specifies the name of the network interface (NIC) where the DNS server IP addresses will be set. Supports wildcard characters.
 
    .PARAMETER IpAddresses
    Specifies one or more IP addresses of the DNS servers to be set on the specified network interface.
 
    .EXAMPLE
    Set-DnsServerIpAddress -ComputerName "Server01" -NicName "Ethernet*" -IpAddresses '8.8.8.8','8.8.4.4','8.8.8.1'
    Sets the DNS server IP addresses '8.8.8.8', '8.8.4.4', and '8.8.8.1' on the network interface starting with "Ethernet" on the computer "Server01".
 
    .NOTES
    This function may require further enhancements for specific use cases.
    #>

    [CmdletBinding()]
    param(
        [string] $ComputerName,
        [string] $NicName,
        [string] $IpAddresses
    )
    if (Test-Connection -ComputerName $ComputerName -Count 2 -Quiet) {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock { param ($ComputerName, $NicName, $IpAddresses)
            Write-Host "Setting on $ComputerName on interface $NicName a new set of DNS Servers $IpAddresses"
            Set-DnsClientServerAddress -InterfaceAlias $NicName -ServerAddresses $IpAddresses
        } -ArgumentList $ComputerName, $NicName, $IpAddresses
    }
    else {
        Write-Warning "Set-DnsServerIpAddress - Can't access $ComputerName. Computer is not online."
    }
}
function Get-HTML {
    <#
    .SYNOPSIS
    Splits the input text by carriage return and outputs each line.
 
    .DESCRIPTION
    This function takes a string input and splits it by carriage return (`r) to output each line separately.
 
    .PARAMETER text
    The input text to be split and displayed line by line.
 
    .EXAMPLE
    Get-HTML -text "Line 1`rLine 2`rLine 3"
    This example splits the input text by carriage return and outputs each line separately.
 
    #>

    [CmdletBinding()]
    param (
        [string] $text
    )
    $text = $text.Split("`r")
    foreach ($t in $text) {
        Write-Host $t
    }
}
function Send-Email {
    <#
    .SYNOPSIS
    Sends an email with specified parameters.
 
    .DESCRIPTION
    This function sends an email using the provided parameters. It supports sending emails with attachments and inline attachments.
 
    .PARAMETER Email
    Specifies the email parameters including sender, recipients, server settings, and encoding.
 
    .PARAMETER Body
    Specifies the body of the email.
 
    .PARAMETER Attachment
    Specifies an array of file paths to be attached to the email.
 
    .PARAMETER InlineAttachments
    Specifies a dictionary of inline attachments to be included in the email.
 
    .PARAMETER Subject
    Specifies the subject of the email.
 
    .PARAMETER To
    Specifies an array of email addresses to send the email to.
 
    .PARAMETER Logger
    Specifies a custom object for logging purposes.
 
    .EXAMPLE
    Send-Email -Email $EmailParams -Body "Hello, this is a test email" -Attachment "C:\Files\attachment.txt" -Subject "Test Email" -To "recipient@example.com" -Logger $Logger
 
    .EXAMPLE
    $EmailParams = @{
        From = "sender@example.com"
        To = "recipient@example.com"
        Subject = "Test Email"
        Body = "Hello, this is a test email"
        Server = "smtp.example.com"
        Port = 587
        Password = "password"
        Encoding = "UTF8"
    }
    Send-Email -Email $EmailParams -Attachment "C:\Files\attachment.txt" -To "recipient@example.com" -Logger $Logger
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [alias('EmailParameters')][System.Collections.IDictionary] $Email,
        [string] $Body,
        [string[]] $Attachment,
        [System.Collections.IDictionary] $InlineAttachments,
        [string] $Subject,
        [string[]] $To,
        [PSCustomObject] $Logger
    )
    try {

        if ($Email.EmailTo) {
            $EmailParameters = $Email.Clone()
            $EmailParameters.EmailEncoding = $EmailParameters.EmailEncoding -replace "-", ''
            $EmailParameters.EmailEncodingSubject = $EmailParameters.EmailEncodingSubject -replace "-", ''
            $EmailParameters.EmailEncodingBody = $EmailParameters.EmailEncodingSubject -replace "-", ''
            $EmailParameters.EmailEncodingAlternateView = $EmailParameters.EmailEncodingAlternateView -replace "-", ''
        }
        else {
            $EmailParameters = @{
                EmailFrom                   = $Email.From
                EmailTo                     = $Email.To
                EmailCC                     = $Email.CC
                EmailBCC                    = $Email.BCC
                EmailReplyTo                = $Email.ReplyTo
                EmailServer                 = $Email.Server
                EmailServerPassword         = $Email.Password
                EmailServerPasswordAsSecure = $Email.PasswordAsSecure
                EmailServerPasswordFromFile = $Email.PasswordFromFile
                EmailServerPort             = $Email.Port
                EmailServerLogin            = $Email.Login
                EmailServerEnableSSL        = $Email.EnableSsl
                EmailEncoding               = $Email.Encoding -replace "-", ''
                EmailEncodingSubject        = $Email.EncodingSubject -replace "-", ''
                EmailEncodingBody           = $Email.EncodingBody -replace "-", ''
                EmailEncodingAlternateView  = $Email.EncodingAlternateView -replace "-", ''
                EmailSubject                = $Email.Subject
                EmailPriority               = $Email.Priority
                EmailDeliveryNotifications  = $Email.DeliveryNotifications
                EmailUseDefaultCredentials  = $Email.UseDefaultCredentials
            }
        }
    }
    catch {
        return @{
            Status = $False
            Error  = $($_.Exception.Message)
            SentTo = ''
        }
    }
    $SmtpClient = [System.Net.Mail.SmtpClient]::new()
    if ($EmailParameters.EmailServer) {
        $SmtpClient.Host = $EmailParameters.EmailServer
    }
    else {
        return @{
            Status = $False
            Error  = "Email Server Host is not set."
            SentTo = ''
        }
    }

    if ($EmailParameters.EmailServerPort) {
        $SmtpClient.Port = $EmailParameters.EmailServerPort
    }
    else {
        return @{
            Status = $False
            Error  = "Email Server Port is not set."
            SentTo = ''
        }
    }

    if ($EmailParameters.EmailServerLogin) {

        $Credentials = Request-Credentials -UserName $EmailParameters.EmailServerLogin `
            -Password $EmailParameters.EmailServerPassword `
            -AsSecure:$EmailParameters.EmailServerPasswordAsSecure `
            -FromFile:$EmailParameters.EmailServerPasswordFromFile `
            -NetworkCredentials 
        $SmtpClient.Credentials = $Credentials
    }
    if ($EmailParameters.EmailServerEnableSSL) {
        $SmtpClient.EnableSsl = $EmailParameters.EmailServerEnableSSL
    }
    $MailMessage = [System.Net.Mail.MailMessage]::new()
    $MailMessage.From = $EmailParameters.EmailFrom
    if ($To) {
        foreach ($T in $To) {
            $MailMessage.To.add($($T)) 
        }
    }
    else {
        if ($EmailParameters.Emailto) {
            foreach ($To in $EmailParameters.Emailto) {
                $MailMessage.To.add($($To)) 
            }
        }
    }
    if ($EmailParameters.EmailCC) {
        foreach ($CC in $EmailParameters.EmailCC) {
            $MailMessage.CC.add($($CC)) 
        }
    }
    if ($EmailParameters.EmailBCC) {
        foreach ($BCC in $EmailParameters.EmailBCC) {
            $MailMessage.BCC.add($($BCC)) 
        }
    }
    if ($EmailParameters.EmailReplyTo) {
        $MailMessage.ReplyTo = $EmailParameters.EmailReplyTo
    }
    $MailMessage.IsBodyHtml = $true
    if ($Subject -eq '') {
        $MailMessage.Subject = $EmailParameters.EmailSubject
    }
    else {
        $MailMessage.Subject = $Subject
    }

    $MailMessage.Priority = [System.Net.Mail.MailPriority]::$($EmailParameters.EmailPriority)

    if ($EmailParameters.EmailEncodingSubject) {
        $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingSubject)
    }
    elseif ($EmailParameters.EmailEncoding) {
        $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding)
    }
    if ($EmailParameters.EmailEncodingBody) {
        $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingBody)
    }
    elseif ($EmailParameters.EmailEncoding) {
        $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding)
    }

    if ($EmailParameters.EmailUseDefaultCredentials) {
        $SmtpClient.UseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials
    }
    if ($EmailParameters.EmailDeliveryNotifications) {
        $MailMessage.DeliveryNotificationOptions = $EmailParameters.EmailDeliveryNotifications
    }

    if ($PSBoundParameters.ContainsKey('InlineAttachments')) {

        if ($EmailParameters.EmailEncodingAlternateView) {
            $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::$($EmailParameters.EmailEncodingAlternateView) , 'text/html' )
        }
        else {
            $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::UTF8, 'text/html' )
        }

        $MailMessage.AlternateViews.Add($BodyPart)
        foreach ($Entry in $InlineAttachments.GetEnumerator()) {
            try {
                $FilePath = $Entry.Value
                Write-Verbose $FilePath
                if ($Entry.Value.StartsWith('http', [System.StringComparison]::CurrentCultureIgnoreCase)) {
                    $FileName = $Entry.Value.Substring($Entry.Value.LastIndexOf("/") + 1)
                    $FilePath = Join-Path $env:temp $FileName
                    Invoke-WebRequest -Uri $Entry.Value -OutFile $FilePath
                }
                $ContentType = Get-MimeType -FileName $FilePath
                $InAttachment = [Net.Mail.LinkedResource]::new($FilePath, $ContentType )
                $InAttachment.ContentId = $Entry.Key
                $BodyPart.LinkedResources.Add( $InAttachment )
            }
            catch {
                $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                Write-Error "Error inlining attachments: $ErrorMessage"
            }
        }
    }
    else {
        $MailMessage.Body = $Body
    }

    if ($PSBoundParameters.ContainsKey('Attachment')) {
        foreach ($Attach in $Attachment) {
            if (Test-Path -LiteralPath $Attach) {
                try {
                    $File = [Net.Mail.Attachment]::new($Attach)

                    $MailMessage.Attachments.Add($File)
                }
                catch {

                    $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
                    if ($Logger) {
                        $Logger.AddErrorRecord("Error attaching file $Attach`: $ErrorMessage")
                    }
                    else {
                        Write-Error "Error attaching file $Attach`: $ErrorMessage"
                    }
                }
            }
        }
    }

    try {
        $MailSentTo = "$($MailMessage.To) $($MailMessage.CC) $($MailMessage.BCC)".Trim()
        if ($pscmdlet.ShouldProcess("$MailSentTo", "Send-Email")) {
            $SmtpClient.Send($MailMessage)

            $MailMessage.Dispose();
            return [PSCustomObject] @{
                Status = $True
                Error  = ""
                SentTo = $MailSentTo
            }
        }
        else {
            return [PSCustomObject] @{
                Status = $False
                Error  = 'Email not sent (WhatIf)'
                SentTo = $MailSentTo
            }
        }
    }
    catch {
        $MailMessage.Dispose();
        return [PSCustomObject] @{
            Status = $False
            Error  = $($_.Exception.Message)
            SentTo = ""
        }
    }
}
function Set-EmailBody {
    <#
    .SYNOPSIS
    Sets the email body content with a welcome message and table data.
 
    .DESCRIPTION
    This function sets the email body content by combining a welcome message and table data into a single HTML string. If there is no data in the table, a default message is displayed.
 
    .PARAMETER TableData
    The data to be included in the email body table.
 
    .PARAMETER TableMessageWelcome
    The welcome message to be displayed at the beginning of the email body.
 
    .PARAMETER TableMessageNoData
    The message to be displayed when there is no data in the table.
 
    .EXAMPLE
    $tableData = @("Name", "Age", "Location"), @("Alice", "30", "New York"), @("Bob", "25", "Los Angeles")
    $welcomeMessage = "Welcome to our platform!"
    Set-EmailBody -TableData $tableData -TableMessageWelcome $welcomeMessage
 
    This example sets the email body with a welcome message "Welcome to our platform!" and table data. If there is no data in the table, the default message "No changes happened during that period." is displayed.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $TableData,
        [alias('TableWelcomeMessage')][string] $TableMessageWelcome,
        [string] $TableMessageNoData = 'No changes happened during that period.'
    )
    $Body = "<p><i><u>$TableMessageWelcome</u></i></p>"
    if ($($TableData | Measure-Object).Count -gt 0) {
        $Body += $TableData | ConvertTo-Html -Fragment | Out-String
    }
    else {
        $Body += "<p><i>$TableMessageNoData</i></p>"
    }
    return $body
}
function Set-EmailBodyPreparedTable ($TableData, $TableWelcomeMessage) {
    <#
    .SYNOPSIS
    Prepares the email body with a welcome message and table data.
 
    .DESCRIPTION
    This function prepares the email body by combining a welcome message and table data into a single HTML string.
 
    .PARAMETER TableData
    The data to be included in the email body table.
 
    .PARAMETER TableWelcomeMessage
    The welcome message to be displayed at the beginning of the email body.
 
    .EXAMPLE
    $tableData = "<table><tr><td>John</td><td>Doe</td></tr></table>"
    $welcomeMessage = "Welcome to our platform!"
    Set-EmailBodyPreparedTable -TableData $tableData -TableWelcomeMessage $welcomeMessage
 
    This example prepares the email body with a welcome message "Welcome to our platform!" and table data "<table><tr><td>John</td><td>Doe</td></tr></table>".
 
    #>

    $body = "<p><i><u>$TableWelcomeMessage</u></i></p>"
    $body += $TableData
    return $body
}
function Set-EmailBodyReplacement {
    <#
    .SYNOPSIS
    Replaces specified text in the email body based on the provided replacement table and type.
 
    .DESCRIPTION
    This function replaces text in the email body with specified formatting based on the replacement table and type provided.
 
    .PARAMETER Body
    The original email body text.
 
    .PARAMETER ReplacementTable
    A hashtable containing the text to be replaced as keys and the replacement values as values.
 
    .PARAMETER Type
    The type of replacement to be performed. Valid values are 'Colors' and 'Bold'.
 
    .EXAMPLE
    Set-EmailBodyReplacement -Body "This is a test email." -ReplacementTable @{ 'test' = 'green' } -Type Colors
    This example replaces the text 'test' in the email body with green color.
 
    .EXAMPLE
    Set-EmailBodyReplacement -Body "This is a test email." -ReplacementTable @{ 'test' = $true } -Type Bold
    This example makes the text 'test' bold in the email body.
 
    #>

    [CmdletBinding()]
    param(
        [string] $Body,
        [hashtable] $ReplacementTable,
        [ValidateSet('Colors', 'Bold')][string] $Type
    )
    switch ($Type) {
        'Colors' {
            foreach ($Field in $ReplacementTable.Keys) {
                $Value = $ReplacementTable.$Field
                $Body = $Body -replace $Field, "<font color=`"$Value`">$Field</font>"
            }
        }
        'Bold' {
            foreach ($Field in $ReplacementTable.Keys) {
                $Value = $ReplacementTable.$Field
                if ($Value -eq $true) {
                    $Body = $Body -replace $Field, "<b>$Field</b>"
                }
            }
        }
    }
    return $Body
}

function Set-EmailBodyReplacementTable {
    <#
    .SYNOPSIS
    Replaces a placeholder in the email body with an HTML table.
 
    .DESCRIPTION
    This function replaces a specified placeholder in the email body with an HTML table generated from the provided table data.
 
    .PARAMETER Body
    The original email body containing the placeholder to be replaced.
 
    .PARAMETER TableName
    The placeholder text to be replaced with the HTML table.
 
    .PARAMETER TableData
    An array of data to be converted into an HTML table.
 
    .EXAMPLE
    $body = "Hello, <<TablePlaceholder>>!"
    $tableName = "TablePlaceholder"
    $tableData = @("Name", "Age", "Location"), @("Alice", "30", "New York"), @("Bob", "25", "Los Angeles")
    Set-EmailBodyReplacementTable -Body $body -TableName $tableName -TableData $tableData
 
    This example replaces the placeholder "<<TablePlaceholder>>" in the email body with an HTML table containing the provided data.
 
    #>

    [CmdletBinding()]
    [alias('Set-EmailBodyTableReplacement')]
    param (
        [string] $Body,
        [string] $TableName,
        [Array] $TableData
    )
    $TableData = $TableData | ConvertTo-Html -Fragment | Out-String
    $Body = $Body -replace "<<$TableName>>", $TableData
    return $Body
}
function Set-EmailFormatting {
    <#
    .SYNOPSIS
    Sets the formatting for an email template.
 
    .DESCRIPTION
    This function sets the formatting for an email template by applying specified styles and parameters.
 
    .PARAMETER Template
    The email template to be formatted.
 
    .PARAMETER FormattingParameters
    A dictionary containing the styles to be applied to the template.
 
    .PARAMETER ConfigurationParameters
    A dictionary containing configuration parameters for formatting.
 
    .PARAMETER Logger
    An object used for logging information.
 
    .PARAMETER SkipNewLines
    Switch to skip adding new lines to the template.
 
    .PARAMETER AddAfterOpening
    An array of strings to add after the opening of the template.
 
    .PARAMETER AddBeforeClosing
    An array of strings to add before the closing of the template.
 
    .PARAMETER Image
    An optional image to be included in the template.
 
    .EXAMPLE
    Set-EmailFormatting -Template "Hello, <<Name>>!" -FormattingParameters @{ Styles = @{ Name = "b" } } -ConfigurationParameters @{ DisplayConsole = $true } -Logger $logger -Image "logo.png"
 
    Description:
    Sets the formatting for an email template with a bold style applied to the name placeholder and includes a logo image.
 
    #>

    [CmdletBinding()]
    param (
        $Template,
        [System.Collections.IDictionary] $FormattingParameters,
        [System.Collections.IDictionary] $ConfigurationParameters,
        [PSCustomObject] $Logger,
        [switch] $SkipNewLines,
        [string[]] $AddAfterOpening,
        [string[]] $AddBeforeClosing,
        [string] $Image
    )
    if ($ConfigurationParameters) {
        $WriteParameters = $ConfigurationParameters.DisplayConsole
    }
    else {
        $WriteParameters = @{ ShowTime = $true; LogFile = ""; TimeFormat = "yyyy-MM-dd HH:mm:ss" }
    }

    if ($Image) {
        $Template = $Template -replace '<<Image>>', $Image
    }

    $Body = "<body>"
    if ($AddAfterOpening) {
        $Body += $AddAfterOpening
    }

    if (-not $SkipNewLines) {
        $Template = $Template.Split("`n") 
        if ($Logger) {
            $Logger.AddInfoRecord("Preparing template - adding HTML <BR> tags...")
        }
        else {
            Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "<BR>", " tags." -Color White, Yellow, White, Yellow
        }
        foreach ($t in $Template) {
            $Body += "$t<br>"
        }
    }
    else {
        $Body += $Template
    }
    foreach ($style in $FormattingParameters.Styles.GetEnumerator()) {
        foreach ($value in $style.Value) {
            if ($value -eq "") {
                continue 
            }
            if ($Logger) {
                $Logger.AddInfoRecord("Preparing template - adding HTML $($style.Name) tag for $value.")
            }
            else {
                Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($style.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow
            }
            $Body = $Body.Replace($value, "<$($style.Name)>$value</$($style.Name)>")
        }
    }

    foreach ($color in $FormattingParameters.Colors.GetEnumerator()) {
        foreach ($value in $color.Value) {
            if ($value -eq "") {
                continue 
            }
            if ($Logger) {
                $Logger.AddInfoRecord("Preparing template - adding HTML $($color.Name) tag for $value.")
            }
            else {
                Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($color.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow
            }
            $Body = $Body.Replace($value, "<span style=color:$($color.Name)>$value</span>")
        }
    }
    foreach ($links in $FormattingParameters.Links.GetEnumerator()) {
        foreach ($link in $links.Value) {
            if ($link.Link -like "*@*") {
                if ($Logger) {
                    $Logger.AddInfoRecord("Preparing template - adding EMAIL Links for $($links.Key).")
                }
                else {
                    Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " EMAIL ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White
                }
                $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='mailto:$($link.Link)?subject=$($Link.Subject)'>$($Link.Text)</a></span>"
            }
            else {
                if ($Logger) {
                    $Logger.AddInfoRecord("[i] Preparing template - adding HTML Links for $($links.Key)")
                }
                else {
                    Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White
                }
                $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='$($link.Link)'>$($Link.Text)</a></span>"
            }
        }
    }
    if ($AddAfterOpening) {
        $Body += $AddBeforeClosing
    }
    $Body += '</body>'
    if ($ConfigurationParameters) {
        if ($ConfigurationParameters.DisplayTemplateHTML -eq $true) {
            Get-HTML($Body) 
        }
    }
    return $Body
}
function Set-EmailHead {
    <#
    .SYNOPSIS
    Sets the HTML head section for an email with specified formatting options.
 
    .DESCRIPTION
    The Set-EmailHead function generates the HTML head section for an email with customizable formatting options. It includes meta tags for content type, viewport settings, and description. Additionally, it defines styles for the body, tables, headings, lists, and more.
 
    .PARAMETER FormattingOptions
    Specifies the formatting options to be applied to the email content.
 
    .EXAMPLE
    $formatting = @{
        FontFamily = 'Arial';
        FontSize = '12px';
        FontTableDataFamily = 'Arial';
        FontTableDataSize = '10px';
        FontTableHeadingFamily = 'Arial';
        FontTableHeadingSize = '12px';
    }
    Set-EmailHead -FormattingOptions $formatting
    #>

    [cmdletBinding()]
    param(
        [System.Collections.IDictionary] $FormattingOptions
    )
    $head = @"
<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="description" content="Password Expiration Email">
    <style>
    BODY {
        background-color: white;
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    TABLE {
        border-width: 1px;
        border-style: solid;
        border-color: black;
        border-collapse: collapse;
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    TH {
        border-width: 1px;
        padding: 3px;
        border-style: solid;
        border-color: black;
        background-color: #00297A;
        color: white;
        font-family: $($FormattingOptions.FontTableHeadingFamily);
        font-size: $($FormattingOptions.FontTableHeadingSize);
    }
    TR {
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    UL {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    LI {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
 
    TD {
        border-width: 1px;
        padding-right: 2px;
        padding-left: 2px;
        padding-top: 0px;
        padding-bottom: 0px;
        border-style: solid;
        border-color: black;
        background-color: white;
        font-family: $($FormattingOptions.FontTableDataFamily);
        font-size: $($FormattingOptions.FontTableDataSize);
    }
 
    H2 {
        font-family: $($FormattingOptions.FontHeadingFamily);
        font-size: $($FormattingOptions.FontHeadingSize);
    }
 
    P {
        font-family: $($FormattingOptions.FontFamily);
        font-size: $($FormattingOptions.FontSize);
    }
</style>
</head>
"@

    return $Head
}
function Set-EmailReportBranding {
    <#
    .SYNOPSIS
    Sets the branding for the email report.
 
    .DESCRIPTION
    This function sets the branding for the email report by customizing the company logo and link.
 
    .PARAMETER FormattingParameters
    Specifies the formatting options for the email report branding.
 
    .EXAMPLE
    $brandingParams = @{
        CompanyBranding = @{
            Link = "https://www.example.com"
            Inline = $true
            Logo = "C:\CompanyLogo.png"
            Width = "200px"
            Height = "100px"
        }
    }
    Set-EmailReportBranding -FormattingParameters $brandingParams
    #>

    [cmdletBinding()]
    param(
        [alias('FormattingOptions')] $FormattingParameters
    )
    if ($FormattingParameters.CompanyBranding.Link) {
        $Report = "<a style=`"text-decoration:none`" href=`"$($FormattingParameters.CompanyBranding.Link)`" class=`"clink logo-container`">"
    }
    else {
        $Report = ''
    }
    if ($FormattingParameters.CompanyBranding.Inline) {
        $Report += "<img width=<fix> height=<fix> src=`"cid:logo`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>"
    }
    else {
        $Report += "<img width=<fix> height=<fix> src=`"$($FormattingParameters.CompanyBranding.Logo)`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>"
    }
    if ($FormattingParameters.CompanyBranding.Width -ne "") {
        $Report = $Report -replace "width=<fix>", "width=$($FormattingParameters.CompanyBranding.Width)"
    }
    else {
        $Report = $Report -replace "width=<fix>", ""
    }
    if ($FormattingParameters.CompanyBranding.Height -ne "") {
        $Report = $Report -replace "height=<fix>", "height=$($FormattingParameters.CompanyBranding.Height)"
    }
    else {
        $Report = $Report -replace "height=<fix>", ""
    }
    return $Report
}
function Set-EmailWordReplacements($Body, $Replace, $ReplaceWith, [switch] $RegEx) {
    <#
    .SYNOPSIS
    Replaces words or patterns in an email body with specified replacements.
 
    .DESCRIPTION
    This function replaces words or patterns in the email body with specified replacements. It provides the option to use regular expressions for more complex replacements.
 
    .PARAMETER Body
    The email body where the word replacements will be applied.
 
    .PARAMETER Replace
    The word or pattern to be replaced in the email body.
 
    .PARAMETER ReplaceWith
    The replacement for the word or pattern.
 
    .PARAMETER RegEx
    Indicates whether to use regular expressions for replacements.
 
    .EXAMPLE
    $body = "Hello, my name is John."
    Set-EmailWordReplacements -Body $body -Replace 'John' -ReplaceWith 'Jane'
    # This will replace "John" with "Jane" in the email body.
 
    .EXAMPLE
    $body = "The cat sat on the mat."
    Set-EmailWordReplacements -Body $body -Replace 'cat' -ReplaceWith 'dog' -RegEx
    # This will replace "cat" with "dog" using regular expressions in the email body.
 
    #>

    if ($RegEx) {
        $Body = $Body -Replace $Replace, $ReplaceWith
    }
    else {
        $Body = $Body.Replace($Replace, $ReplaceWith)
    }
    return $Body
}
function Set-EmailWordReplacementsHash {
    <#
    .SYNOPSIS
    Replaces words in an email body based on a given hash table of substitutions.
 
    .DESCRIPTION
    This function replaces words in the email body with specified substitutions using a hash table.
 
    .PARAMETER Body
    The email body where the word replacements will be applied.
 
    .PARAMETER Substitute
    A hash table containing the words to be replaced as keys and their corresponding substitutions as values.
 
    .EXAMPLE
    $body = "Hello, my name is John."
    $substitutions = @{
        "John" = "Jane"
    }
    Set-EmailWordReplacementsHash -Body $body -Substitute $substitutions
    # This will replace "John" with "Jane" in the email body.
 
    #>

    [CmdletBinding()]
    param (
        $Body,
        $Substitute
    )
    foreach ($Key in $Substitute.Keys) {
        Write-Verbose "Set-EmailWordReplacementsHash - Key: $Key Value: $($Substitute.$Key)"
        $Body = Set-EmailWordReplacements -Body $Body -Replace $Key -ReplaceWith $Substitute.$Key
    }
    return $Body
}
function Get-FileEncoding {
    <#
    .SYNOPSIS
    Get the encoding of a file (ASCII, UTF8, UTF8BOM, Unicode, BigEndianUnicode, UTF7)
 
    .DESCRIPTION
    Get the encoding of a file (ASCII, UTF8, UTF8BOM, Unicode, BigEndianUnicode, UTF7).
    Encoding is determined by the first few bytes of the file or by the presence of non-ASCII characters.
 
    .PARAMETER Path
    Path to the file to check
 
    .EXAMPLE
    Get-FileEncoding -Path 'C:\Users\pklys\Desktop\test.txt'
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $Path
    )
    if (-not (Test-Path -Path $Path)) {
        if ($ErrorActionPreference -eq 'Stop') {
            throw "Get-FileEncoding - File not found: $Path"
        }
        Write-Warning -Message "Get-FileEncoding - File not found: $Path"
        return
    }
    $fileStream = [System.IO.FileStream]::new($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $byte = [byte[]]::new(4)
    $null = $fileStream.Read($byte, 0, 4)
    $fileStream.Close()

    if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) {
        return 'UTF8BOM'
    }
    elseif ($byte[0] -eq 0xff -and $byte[1] -eq 0xfe) {
        return 'Unicode'
    }
    elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) {
        return 'BigEndianUnicode'
    }
    elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) {
        return 'UTF7'
    }
    else {

        $fileStream = [System.IO.FileStream]::new($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
        $byte = [byte[]]::new(1)
        while ($fileStream.Read($byte, 0, 1) -gt 0) {
            if ($byte[0] -gt 0x7F) {
                $fileStream.Close()
                return 'UTF8'
            }
        }
        $fileStream.Close()
        return 'ASCII'
    }
}

function Get-FileInformation {
    <#
    .SYNOPSIS
    Get information about file such as Name, FullName and Size
 
    .DESCRIPTION
    Get information about file such as Name, FullName and Size
 
    .PARAMETER File
    File to get information about
 
    .EXAMPLE
    Get-FileInformation -File 'C:\Support\GitHub\PSSharedGoods\Public\FilesFolders\Get-FileInformation.ps1'
 
    #>

    [CmdletBinding()]
    param(
        [alias('LiteralPath', 'Path')][string] $File
    )
    if (Test-Path -LiteralPath $File) {
        $Item = Get-Item -LiteralPath $File
        [PSCustomObject] @{
            Name          = $Item.Name
            FullName      = $Item.FullName
            Size          = Get-FileSize -Bytes $Item.Length
            IsReadOnly    = $Item.IsReadOnly
            LastWriteTime = $Item.LastWriteTime
        }
    }
}
function Get-FileMetaData {
    <#
    .SYNOPSIS
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file
 
    .DESCRIPTION
    Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file
 
    .PARAMETER File
    FileName or FileObject
 
    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties
 
    .EXAMPLE
    Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties
 
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, ValueFromPipeline)][Object] $File,
        [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None',
        [switch] $Signature,
        [switch] $AsHashTable
    )
    Process {
        foreach ($F in $File) {
            $MetaDataObject = [ordered] @{}
            if ($F -is [string]) {
                if ($F -and (Test-Path -LiteralPath $F)) {
                    $FileInformation = Get-ItemProperty -Path $F
                    if ($FileInformation -is [System.IO.DirectoryInfo]) {
                        continue
                    }
                }
                else {
                    Write-Warning "Get-FileMetaData - Doesn't exists. Skipping $F."
                    continue
                }
            }
            elseif ($F -is [System.IO.DirectoryInfo]) {

                continue
            }
            elseif ($F -is [System.IO.FileInfo]) {
                $FileInformation = $F
            }
            else {
                Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F."
                continue
            }
            $ShellApplication = New-Object -ComObject Shell.Application
            $ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName)
            $ShellFile = $ShellFolder.ParseName($FileInformation.Name)
            $MetaDataProperties = [ordered] @{}
            0..400 | ForEach-Object -Process {
                $DataValue = $ShellFolder.GetDetailsOf($null, $_)
                $PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ', '')
                if ($PropertyValue -ne '') {
                    $MetaDataProperties["$_"] = $PropertyValue
                }
            }
            foreach ($Key in $MetaDataProperties.Keys) {
                $Property = $MetaDataProperties[$Key]
                $Value = $ShellFolder.GetDetailsOf($ShellFile, [int] $Key)
                if ($Property -in 'Attributes', 'Folder', 'Type', 'SpaceFree', 'TotalSize', 'SpaceUsed') {
                    continue
                }
                If (($null -ne $Value) -and ($Value -ne '')) {
                    $MetaDataObject["$Property"] = $Value
                }
            }
            if ($FileInformation.VersionInfo) {
                $SplitInfo = ([string] $FileInformation.VersionInfo).Split([char]13)
                foreach ($Item in $SplitInfo) {
                    $Property = $Item.Split(":").Trim()
                    if ($Property[0] -and $Property[1] -ne '') {
                        if ($Property[1] -in 'False', 'True') {
                            $MetaDataObject["$($Property[0])"] = [bool] $Property[1]
                        }
                        else {
                            $MetaDataObject["$($Property[0])"] = $Property[1]
                        }
                    }
                }
            }
            $MetaDataObject["Attributes"] = $FileInformation.Attributes
            $MetaDataObject['IsReadOnly'] = $FileInformation.IsReadOnly
            $MetaDataObject['IsHidden'] = $FileInformation.Attributes -like '*Hidden*'
            $MetaDataObject['IsSystem'] = $FileInformation.Attributes -like '*System*'
            if ($Signature) {
                $DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.Fullname
                $MetaDataObject['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject
                $MetaDataObject['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer
                $MetaDataObject['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber
                $MetaDataObject['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore
                $MetaDataObject['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter
                $MetaDataObject['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint
                $MetaDataObject['SignatureStatus'] = $DigitalSignature.Status
                $MetaDataObject['IsOSBinary'] = $DigitalSignature.IsOSBinary
            }
            if ($HashAlgorithm -ne 'None') {
                $MetaDataObject[$HashAlgorithm] = (Get-FileHash -LiteralPath $FileInformation.FullName -Algorithm $HashAlgorithm).Hash
            }
            if ($AsHashTable) {
                $MetaDataObject
            }
            else {
                [PSCustomObject] $MetaDataObject
            }
        }
    }
}

function Get-FileName {
    <#
    .SYNOPSIS
    Generates a temporary file name with the specified extension.
 
    .DESCRIPTION
    This function generates a temporary file name based on the provided extension. It can generate a temporary file name in the system's temporary folder or just the file name itself.
 
    .PARAMETER Extension
    Specifies the extension for the temporary file name. Default is 'tmp'.
 
    .PARAMETER Temporary
    Indicates whether to generate a temporary file name in the system's temporary folder.
 
    .PARAMETER TemporaryFileOnly
    Indicates whether to generate only the temporary file name without the path.
 
    .EXAMPLE
    Get-FileName -Temporary
    Generates a temporary file name in the system's temporary folder. Example output: 3ymsxvav.tmp
 
    .EXAMPLE
    Get-FileName -Temporary
    Generates a temporary file name without the path. Example output: tmpD74C.tmp
 
    .EXAMPLE
    Get-FileName -Temporary -Extension 'xlsx'
    Generates a temporary file name with the specified extension in the system's temporary folder. Example output: tmp45B6.xlsx
 
    .NOTES
    These examples demonstrate how to use the Get-FileName function to generate temporary file names.
    #>

    [CmdletBinding()]
    param(
        [string] $Extension = 'tmp',
        [switch] $Temporary,
        [switch] $TemporaryFileOnly
    )

    if ($Temporary) {

        return [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension")
    }
    if ($TemporaryFileOnly) {

        return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension"
    }
}
function Get-FileOwner {
    <#
    .SYNOPSIS
    Retrieves the owner of the specified file or folder.
 
    .DESCRIPTION
    This function retrieves the owner of the specified file or folder. It provides options to resolve the owner's identity and output the results as a hashtable or custom object.
 
    .PARAMETER Path
    Specifies the path to the file or folder.
 
    .PARAMETER Recursive
    Indicates whether to search for files recursively in subdirectories.
 
    .PARAMETER JustPath
    Specifies if only the path information should be returned.
 
    .PARAMETER Resolve
    Indicates whether to resolve the owner's identity.
 
    .PARAMETER AsHashTable
    Specifies if the output should be in hashtable format.
 
    .EXAMPLE
    Get-FileOwner -Path "C:\Example\File.txt"
    Retrieves the owner of the specified file "File.txt".
 
    .EXAMPLE
    Get-FileOwner -Path "C:\Example" -Recursive
    Retrieves the owners of all files in the "Example" directory and its subdirectories.
 
    .EXAMPLE
    Get-FileOwner -Path "C:\Example\File.txt" -Resolve
    Retrieves the owner of the specified file "File.txt" and resolves the owner's identity.
 
    .EXAMPLE
    Get-FileOwner -Path "C:\Example\File.txt" -AsHashTable
    Retrieves the owner of the specified file "File.txt" and outputs the result as a hashtable.
 
    #>

    [cmdletBinding()]
    param(
        [Array] $Path,
        [switch] $Recursive,
        [switch] $JustPath,
        [switch] $Resolve,
        [switch] $AsHashTable
    )
    Begin {
    }
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) {
                $FullPath = $P.FullName
            }
            elseif ($P -is [string]) {
                $FullPath = $P
            }
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process {

                        $ACL = Get-Acl -Path $_
                        $Object = [ordered]@{
                            FullName = $_
                            Owner    = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            }
                            else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) {
                            $Object
                        }
                        else {
                            [PSCustomObject] $Object
                        }
                    }
                }
                else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process {
                        $File = $_
                        $ACL = Get-Acl -Path $File.FullName
                        $Object = [ordered] @{
                            FullName       = $_.FullName
                            Extension      = $_.Extension
                            CreationTime   = $_.CreationTime
                            LastAccessTime = $_.LastAccessTime
                            LastWriteTime  = $_.LastWriteTime
                            Attributes     = $_.Attributes
                            Owner          = $ACL.Owner
                        }
                        if ($Resolve) {
                            $Identity = Convert-Identity -Identity $ACL.Owner
                            if ($Identity) {
                                $Object['OwnerName'] = $Identity.Name
                                $Object['OwnerSid'] = $Identity.SID
                                $Object['OwnerType'] = $Identity.Type
                            }
                            else {
                                $Object['OwnerName'] = ''
                                $Object['OwnerSid'] = ''
                                $Object['OwnerType'] = ''
                            }
                        }
                        if ($AsHashTable) {
                            $Object
                        }
                        else {
                            [PSCustomObject] $Object
                        }
                    }
                }
            }
        }
    }
    End {
    }
}
function Get-FilePermission {
    <#
    .SYNOPSIS
    Retrieves and displays file permissions for the specified file or folder.
 
    .DESCRIPTION
    This function retrieves and displays the file permissions for the specified file or folder. It provides options to filter permissions based on inheritance, resolve access control types, and include extended information.
 
    .EXAMPLE
    Get-FilePermission -Path "C:\Example\File.txt"
    Description:
    Retrieves and displays the permissions for the "File.txt" file.
 
    .EXAMPLE
    Get-FilePermission -Path "D:\Folder" -Inherited
    Description:
    Retrieves and displays only the inherited permissions for the "Folder" directory.
 
    .EXAMPLE
    Get-FilePermission -Path "E:\Document.docx" -ResolveTypes -Extended
    Description:
    Retrieves and displays the resolved access control types and extended information for the "Document.docx" file.
 
    .NOTES
    This function supports various options to customize the output and handle different permission scenarios.
    #>

    [alias('Get-PSPermissions', 'Get-FilePermissions')]
    [cmdletBinding()]
    param(
        [Array] $Path,
        [switch] $Inherited,
        [switch] $NotInherited,
        [switch] $ResolveTypes,
        [switch] $Extended,
        [switch] $IncludeACLObject,
        [switch] $AsHashTable,
        [System.Security.AccessControl.FileSystemSecurity] $ACLS
    )
    foreach ($P in $Path) {
        if ($P -is [System.IO.FileSystemInfo]) {
            $FullPath = $P.FullName
        }
        elseif ($P -is [string]) {
            $FullPath = $P
        }
        $TestPath = Test-Path -Path $FullPath
        if ($TestPath) {
            if (-not $ACLS) {
                try {
                    $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop)
                }
                catch {
                    Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)"
                    continue
                }
            }
            $Output = foreach ($ACL in $ACLS.Access) {
                if ($Inherited) {
                    if ($ACL.IsInherited -eq $false) {

                        continue
                    }
                }
                if ($NotInherited) {
                    if ($ACL.IsInherited -eq $true) {
                        continue
                    }
                }
                $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights
                $ReturnObject = [ordered] @{ }
                $ReturnObject['Path' ] = $FullPath
                $ReturnObject['AccessControlType'] = $ACL.AccessControlType
                if ($ResolveTypes) {
                    $Identity = Convert-Identity -Identity $ACL.IdentityReference
                    if ($Identity) {
                        $ReturnObject['Principal'] = $ACL.IdentityReference
                        $ReturnObject['PrincipalName'] = $Identity.Name
                        $ReturnObject['PrincipalSid'] = $Identity.Sid
                        $ReturnObject['PrincipalType'] = $Identity.Type
                    }
                    else {

                        $ReturnObject['Principal'] = $Identity
                        $ReturnObject['PrincipalName'] = ''
                        $ReturnObject['PrincipalSid'] = ''
                        $ReturnObject['PrincipalType'] = ''
                    }
                }
                else {
                    $ReturnObject['Principal'] = $ACL.IdentityReference.Value
                }
                $ReturnObject['FileSystemRights'] = $TranslateRights
                $ReturnObject['IsInherited'] = $ACL.IsInherited
                if ($Extended) {
                    $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags
                    $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags
                }
                if ($IncludeACLObject) {
                    $ReturnObject['ACL'] = $ACL
                    $ReturnObject['AllACL'] = $ACLS
                }
                if ($AsHashTable) {
                    $ReturnObject
                }
                else {
                    [PSCustomObject] $ReturnObject
                }
            }
            $Output
        }
        else {
            Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping."
        }
    }
}

function Get-FilesInFolder {
    <#
    .SYNOPSIS
    Retrieves a list of files in a specified folder with the option to filter by extension.
 
    .DESCRIPTION
    This function retrieves a list of files in the specified folder. By default, it includes all files with the '.evtx' extension, but you can specify a different extension using the $Extension parameter.
 
    .PARAMETER Folder
    Specifies the folder path from which to retrieve files.
 
    .PARAMETER Extension
    Specifies the file extension to filter by. Default value is '*.evtx'.
 
    .EXAMPLE
    Get-FilesInFolder -Folder "C:\Logs"
 
    Description:
    Retrieves all files with the '.evtx' extension in the "C:\Logs" folder.
 
    .EXAMPLE
    Get-FilesInFolder -Folder "D:\Documents" -Extension '*.txt'
 
    Description:
    Retrieves all files with the '.txt' extension in the "D:\Documents" folder.
 
    #>

    [CmdletBinding()]
    param(
        [string] $Folder,
        [string] $Extension = '*.evtx'
    )

    $Files = Get-ChildItem -Path $Folder -Filter $Extension -Recurse
    $ReturnFiles = foreach ($File in $Files) {
        $File.FullName
    }
    return $ReturnFiles
}
function Get-FileSize {
    <#
    .SYNOPSIS
    Get-FileSize function calculates the file size in human-readable format.
 
    .DESCRIPTION
    This function takes a file size in bytes and converts it into a human-readable format (e.g., KB, MB, GB, etc.).
 
    .PARAMETER Bytes
    Specifies the size of the file in bytes.
 
    .EXAMPLE
    Get-FileSize -Bytes 1024
    Output: 1 KB
 
    .EXAMPLE
    Get-FileSize -Bytes 1048576
    Output: 1 MB
    #>

    [CmdletBinding()]
    param(
        $Bytes
    )
    $sizes = 'Bytes,KB,MB,GB,TB,PB,EB,ZB' -split ','
    for ($i = 0; ($Bytes -ge 1kb) -and ($i -lt $sizes.Count); $i++) {
        $Bytes /= 1kb
    }
    $N = 2;
    if ($i -eq 0) {
        $N = 0
    }
    return "{0:N$($N)} {1}" -f $Bytes, $sizes[$i]
}

function Get-PathSeparator {
    <#
    .SYNOPSIS
    Gets the path separator character used by the operating system.
 
    .DESCRIPTION
    This function retrieves the path separator character used by the operating system. It can be useful for handling file paths in a platform-independent manner.
 
    .EXAMPLE
    Get-PathSeparator
    Output:
    \
 
    .NOTES
    The function uses [System.IO.Path]::PathSeparator to get the path separator character.
    #>

    [CmdletBinding()]
    param()
    return [IO.Path]::PathSeparator
}
function Get-PathTemporary {
    <#
    .SYNOPSIS
    Gets the path to the temporary directory.
 
    .DESCRIPTION
    This function retrieves the path to the system's temporary directory.
 
    .EXAMPLE
    Get-PathTemporary
    Output:
    C:\Users\Username\AppData\Local\Temp
 
    .NOTES
    The function uses [System.IO.Path]::GetTempPath() to get the temporary directory path.
    #>

    [CmdletBinding()]
    param()
    return [IO.path]::GetTempPath()
}
function Get-TemporaryDirectory {
    <#
    .SYNOPSIS
    Creates a temporary directory and returns its path.
 
    .DESCRIPTION
    This function generates a temporary directory with a unique name and returns the full path to the directory.
 
    .EXAMPLE
    $tempDir = Get-TemporaryDirectory
    $tempDir
    Output:
    C:\Users\Username\AppData\Local\Temp\abcde12345
 
    .NOTES
    The temporary directory is created using a random string name with specified characteristics.
    #>

    param(

    )
    $TemporaryFolder = Get-RandomStringName -Size 13 -LettersOnly -ToLower
    $TemporaryPath = [system.io.path]::GetTempPath()
    $Output = New-Item -ItemType Directory -Path $TemporaryPath -Name $TemporaryFolder -Force
    if (Test-Path -LiteralPath $Output.FullName) {
        $Output
    }
}
function Remove-FilePermission {
    <#
    .SYNOPSIS
    Removes specific or all access rules for a user or group from a file or folder.
 
    .DESCRIPTION
    This function removes specific or all access rules for a specified user or group from a file or folder. It can be used to manage file permissions effectively.
 
    .PARAMETER Path
    Specifies the path of the file or folder from which to remove access rules.
 
    .PARAMETER UserOrGroup
    Specifies the user or group for which access rules should be removed. If not specified, all access rules will be removed.
 
    .PARAMETER All
    Indicates whether all access rules for the specified file or folder should be removed.
 
    .EXAMPLE
    Remove-FilePermission -Path "C:\Example\File.txt" -UserOrGroup "User1"
    Removes access rules for "User1" from the file "File.txt".
 
    .EXAMPLE
    Remove-FilePermission -Path "C:\Example\Folder" -All
    Removes all access rules from the folder "Folder".
 
    #>

    [cmdletBinding()]
    param(
        [string] $Path,
        [string] $UserOrGroup = "",
        [switch] $All
    )
    $ACL = Get-Acl -Path $Path
    if ($UserOrGroup -ne "") {
        foreach ($access in $ACL.Access) {
            if ($access.IdentityReference.Value -eq $UserOrGroup) {
                $ACL.RemoveAccessRule($access) | Out-Null
            }
        }
    }
    if ($All -eq $true) {
        foreach ($access in $ACL.Access) {
            $ACL.RemoveAccessRule($access) | Out-Null
        }
    }
    Set-Acl -Path $Path -AclObject $ACL
}
function Set-FileInheritance {
    <#
    .SYNOPSIS
    Sets or removes inheritance for a specified directory.
 
    .DESCRIPTION
    This function allows you to set or remove inheritance for a specified directory. You can choose to disable inheritance and optionally keep the inherited ACL.
 
    .PARAMETER StartingDir
    Specifies the directory for which to set or remove inheritance.
 
    .PARAMETER DisableInheritance
    Switch parameter to disable inheritance for the specified directory.
 
    .PARAMETER KeepInheritedAcl
    Switch parameter to keep the inherited ACL when disabling inheritance.
 
    .EXAMPLE
    Set-FileInheritance -StartingDir "C:\Example" -DisableInheritance
    Disables inheritance for the directory "C:\Example".
 
    .EXAMPLE
    Set-FileInheritance -StartingDir "D:\Data" -DisableInheritance -KeepInheritedAcl
    Disables inheritance for the directory "D:\Data" and keeps the inherited ACL.
 
    #>

    [cmdletBinding()]
    param(
        [string] $StartingDir,
        [switch] $DisableInheritance,
        [switch] $KeepInheritedAcl
    )
    $acl = Get-Acl -Path $StartingDir
    $acl.SetAccessRuleProtection($DisableInheritance, $KeepInheritedAcl)
    $acl | Set-Acl -Path $StartingDir
}
function Set-FileOwner {
    <#
    .SYNOPSIS
    Sets the owner of a file or folder.
 
    .DESCRIPTION
    This function sets the owner of a specified file or folder to the provided owner.
 
    .PARAMETER Path
    Specifies the path to the file or folder.
 
    .PARAMETER Recursive
    Indicates whether to process the items in the specified path recursively.
 
    .PARAMETER Owner
    Specifies the new owner for the file or folder.
 
    .PARAMETER Exclude
    Specifies an array of owners to exclude from ownership change.
 
    .PARAMETER JustPath
    Indicates whether to only change the owner of the specified path without recursing into subfolders.
 
    .EXAMPLE
    Set-FileOwner -Path "C:\Example\File.txt" -Owner "DOMAIN\User1"
 
    Description:
    Sets the owner of the file "File.txt" to "DOMAIN\User1".
 
    .EXAMPLE
    Set-FileOwner -Path "C:\Example\Folder" -Owner "DOMAIN\User2" -Recursive
 
    Description:
    Sets the owner of the folder "Folder" and all its contents to "DOMAIN\User2" recursively.
 
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [Array] $Path,
        [switch] $Recursive,
        [string] $Owner,
        [string[]] $Exlude,
        [switch] $JustPath
    )

    Begin { 
    }
    Process {
        foreach ($P in $Path) {
            if ($P -is [System.IO.FileSystemInfo]) {
                $FullPath = $P.FullName
            }
            elseif ($P -is [string]) {
                $FullPath = $P
            }
            $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner)
            if ($FullPath -and (Test-Path -Path $FullPath)) {
                if ($JustPath) {
                    $FullPath | ForEach-Object -Process {
                        $File = $_
                        try {
                            $ACL = Get-Acl -Path $File -ErrorAction Stop
                        }
                        catch {
                            Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)"
                        }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File -AclObject $ACL -ErrorAction Stop
                                }
                                catch {
                                    Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)"
                                }
                            }
                        }
                    }
                }
                else {
                    Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process {
                        $File = $_
                        try {
                            $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop
                        }
                        catch {
                            Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)"
                        }
                        if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) {
                            if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) {
                                try {
                                    $ACL.SetOwner($OwnerTranslated)
                                    Set-Acl -Path $File.FullName -AclObject $ACL -ErrorAction Stop
                                }
                                catch {
                                    Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)"
                                }
                            }
                        }
                    }
                    foreach ($e in $err) {
                        Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))"
                    }
                }
            }
        }
    }
    End {
    }
}
function Set-FilePermission {
    <#
    .SYNOPSIS
    Sets file permissions for a specified user or group on a given path.
 
    .DESCRIPTION
    This function sets file permissions for a specified user or group on a given path. It allows you to define the type of access control, inheritance flags, and propagation flags.
 
    .PARAMETER Path
    The path to the file or directory for which permissions need to be set.
 
    .PARAMETER Principal
    Specifies the user or group for which permissions are being set. Use the format 'domain\username'.
 
    .PARAMETER InheritedFolderPermissions
    Specifies the inheritance flags for folder permissions. Default values are ContainerInherit and ObjectInherit.
 
    .PARAMETER AccessControlType
    Specifies the type of access control to be allowed or denied. Default is Allow.
 
    .PARAMETER PropagationFlags
    Specifies how the access control entries are propagated to child objects. Default is None.
 
    .PARAMETER AclRightsToAssign
    Specifies the file system rights to assign to the user or group.
 
    .EXAMPLE
    Set-FilePermission -Path "C:\Example\File.txt" -Principal "domain\username" -AclRightsToAssign "Modify"
    Sets Modify permissions for the user 'domain\username' on the file "File.txt" located at "C:\Example".
 
    .EXAMPLE
    Set-FilePermission -Path "D:\Data" -Principal "domain\group" -AclRightsToAssign "FullControl" -InheritedFolderPermissions @("ContainerInherit")
    Sets FullControl permissions for the group 'domain\group' on the directory "Data" located at "D:\" with inheritance only for subfolders.
 
    .NOTES
    File permissions are set using the Set-Acl cmdlet from the System.Security.AccessControl module.
    #>

    [cmdletBinding()]
    param (
        [string] $Path,
        [alias('UserOrGroup')][string] $Principal,
        [System.Security.AccessControl.InheritanceFlags] $InheritedFolderPermissions = @(
            [System.Security.AccessControl.InheritanceFlags]::ContainerInherit,
            [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
        ),
        [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow,
        [System.Security.AccessControl.PropagationFlags] $PropagationFlags = [System.Security.AccessControl.PropagationFlags]::None,
        [System.Security.AccessControl.FileSystemRights] $AclRightsToAssign
    )

    if ($Principal) {
        $User = [System.Security.Principal.NTAccount]::new($Principal)

        $ACL = Get-Acl -Path $Path

        $Rule = [System.Security.AccessControl.FileSystemAccessRule]::new($User, $AclRightsToAssign, $InheritedFolderPermissions, $PropagationFlags, $AccessControlType)
        $ACL.SetAccessRule($Rule)
        Try {
            Set-Acl -Path $Path -AclObject $ACL
        }
        catch {
            Write-Warning "Set-FilePermission - Setting permission $AclRightsToAssign failed with error: $($_.Exception.Message)"
        }
    }
}

function Get-GitHubLatestRelease {
    <#
    .SYNOPSIS
    Gets one or more releases from GitHub repository
 
    .DESCRIPTION
    Gets one or more releases from GitHub repository
 
    .PARAMETER Url
    Url to github repository
 
    .EXAMPLE
    Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases" | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param(
        [parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url
    )
    $ProgressPreference = 'SilentlyContinue'

    $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1
    if ($Responds) {
        Try {
            [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json)
            foreach ($JsonContent in $JsonOutput) {
                [PSCustomObject] @{
                    PublishDate = [DateTime]  $JsonContent.published_at
                    CreatedDate = [DateTime] $JsonContent.created_at
                    PreRelease  = [bool] $JsonContent.prerelease
                    Version     = [version] ($JsonContent.name -replace 'v', '')
                    Tag         = $JsonContent.tag_name
                    Branch      = $JsonContent.target_commitish
                    Errors      = ''
                }
            }
        }
        catch {
            [PSCustomObject] @{
                PublishDate = $null
                CreatedDate = $null
                PreRelease  = $null
                Version     = $null
                Tag         = $null
                Branch      = $null
                Errors      = $_.Exception.Message
            }
        }
    }
    else {
        [PSCustomObject] @{
            PublishDate = $null
            CreatedDate = $null
            PreRelease  = $null
            Version     = $null
            Tag         = $null
            Branch      = $null
            Errors      = "No connection (ping) to $($Url.Host)"
        }
    }
    $ProgressPreference = 'Continue'
}
function Get-GitHubVersion {
    <#
    .SYNOPSIS
    Get the latest version of a GitHub repository and compare with local version
 
    .DESCRIPTION
    Get the latest version of a GitHub repository and compare with local version
 
    .PARAMETER Cmdlet
    Cmdlet to find module for
 
    .PARAMETER RepositoryOwner
    Repository owner
 
    .PARAMETER RepositoryName
    Repository name
 
    .EXAMPLE
    Get-GitHubVersion -Cmdlet 'Start-DelegationModel' -RepositoryOwner 'evotecit' -RepositoryName 'DelegationModel'
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $Cmdlet,
        [Parameter(Mandatory)][string] $RepositoryOwner,
        [Parameter(Mandatory)][string] $RepositoryName
    )
    $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue
    if ($App) {
        [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false)
        $LatestVersion = $GitHubReleases[0]
        if (-not $LatestVersion.Errors) {
            if ($App.Version -eq $LatestVersion.Version) {
                "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)"
            }
            elseif ($App.Version -lt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?"
            }
            elseif ($App.Version -gt $LatestVersion.Version) {
                "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!"
            }
        }
        else {
            "Current: $($App.Version)"
        }
    }
    else {
        "Current: Unknown"
    }
}
function Get-IPAddressRangeInformation {
    <#
    .SYNOPSIS
    Provides information about IP Address range
 
    .DESCRIPTION
    Provides information about IP Address range
 
    .PARAMETER Network
    Network in form of IP/NetworkLength (e.g. 10.2.10.0/24')
 
    .PARAMETER IPAddress
    IP Address to use
 
    .PARAMETER NetworkLength
    Network length to use
 
    .PARAMETER CIDRObject
    CIDRObject to use
 
    .EXAMPLE
    $CidrObject = @{
        Ip = '10.2.10.0'
        NetworkLength = 24
    }
    Get-IPAddressRangeInformation -CIDRObject $CidrObject | Format-Table
 
    .EXAMPLE
    Get-IPAddressRangeInformation -Network '10.2.10.0/24' | Format-Table
 
    .EXAMPLE
    Get-IPAddressRangeInformation -IPAddress '10.2.10.0' -NetworkLength 24 | Format-Table
 
    .NOTES
    General notes
    #>

    [cmdletBinding(DefaultParameterSetName = 'Network')]
    param(
        [Parameter(ParameterSetName = 'Network', Mandatory)][string] $Network,
        [Parameter(ParameterSetName = 'IPAddress', Mandatory)][string] $IPAddress,
        [Parameter(ParameterSetName = 'IPAddress', Mandatory)][int] $NetworkLength,
        [Parameter(ParameterSetName = 'CIDR', Mandatory)][psobject] $CIDRObject
    )
    $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)'

    if ($Network) {
        $CIDRObject = @{
            Ip            = $Network.Split('/')[0]
            NetworkLength = $Network.Split('/')[1]
        }
    }
    elseif ($IPAddress -and $NetworkLength) {
        $CIDRObject = @{
            Ip            = $IPAddress
            NetworkLength = $NetworkLength
        }
    }
    elseif ($CIDRObject) {
    }
    else {
        Write-Error "Get-IPAddressRangeInformation - Invalid parameters specified"
        return
    }

    $o = [ordered] @{}
    $o.IP = [string] $CIDRObject.IP
    $o.BinaryIP = Convert-IPToBinary $o.IP
    if (-not $o.BinaryIP) {
        return
    }
    $o.NetworkLength = [int32] $CIDRObject.NetworkLength
    $o.SubnetMask = Convert-BinaryToIP ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinarySubnetMask = ('1' * $o.NetworkLength).PadRight(32, '0')
    $o.BinaryNetworkAddress = $o.BinaryIP.SubString(0, $o.NetworkLength).PadRight(32, '0')
    if ($Contains) {
        if ($Contains -match "\A${IPv4Regex}\z") {

            return Test-IPIsInNetwork $Contains $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1')
        }
        else {
            Write-Error "Get-IPAddressRangeInformation - Invalid IPv4 address specified with -Contains"
            return
        }
    }
    $o.NetworkAddress = Convert-BinaryToIP $o.BinaryNetworkAddress
    if ($o.NetworkLength -eq 32 -or $o.NetworkLength -eq 31) {
        $o.HostMin = $o.IP
    }
    else {
        $o.HostMin = Convert-BinaryToIP ([System.Convert]::ToString(([System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1), 2)).PadLeft(32, '0')
    }
    [string] $BinaryBroadcastIP = $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') 
    $o.BinaryBroadcast = $BinaryBroadcastIP
    [int64] $DecimalHostMax = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) - 1
    [string] $BinaryHostMax = [System.Convert]::ToString($DecimalHostMax, 2).PadLeft(32, '0')
    $o.HostMax = Convert-BinaryToIP $BinaryHostMax
    $o.TotalHosts = [int64][System.Convert]::ToString(([System.Convert]::ToInt64($BinaryBroadcastIP, 2) - [System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1))
    $o.UsableHosts = $o.TotalHosts - 2

    if ($o.NetworkLength -eq 32) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 1
        $o.TotalHosts = [int64] 1
        $o.HostMax = $o.IP
    }
    elseif ($o.NetworkLength -eq 31) {
        $o.Broadcast = $Null
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 2

        [int64] $DecimalHostMax2 = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) 
        [string] $BinaryHostMax2 = [System.Convert]::ToString($DecimalHostMax2, 2).PadLeft(32, '0')
        $o.HostMax = Convert-BinaryToIP $BinaryHostMax2
    }
    elseif ($o.NetworkLength -eq 30) {
        $o.UsableHosts = [int64] 2
        $o.TotalHosts = [int64] 4
        $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP
    }
    else {
        $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP
    }
    if ($Enumerate) {
        $IPRange = @(Get-IPRange $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1'))
        if ((31, 32) -notcontains $o.NetworkLength ) {
            $IPRange = $IPRange[1..($IPRange.Count - 1)] 
            $IPRange = $IPRange[0..($IPRange.Count - 2)] 
        }
        $o.IPEnumerated = $IPRange
    }
    else {
        $o.IPEnumerated = @()
    }
    [PSCustomObject]$o
}
function Get-Logger {
    <#
    .SYNOPSIS
    Returns an instance of the logger object.
 
    .DESCRIPTION
    This function creates a logger object that can be used to log messages to a file or console. It allows customization of log file path, log directory, log filename, time display, and time format.
 
    .PARAMETER LogPath
    Specifies the full path of the log file.
 
    .PARAMETER LogsDir
    Specifies the directory where the log file will be stored.
 
    .PARAMETER Filename
    Specifies the name of the log file.
 
    .PARAMETER ShowTime
    Indicates whether to display timestamps in the log messages.
 
    .PARAMETER TimeFormat
    Specifies the format of the timestamp to be displayed in the log messages.
 
    .EXAMPLE
    Creates a logger with a full log name:
    $Logger = Get-Logger -ShowTime -LogPath 'C:\temp\test.log'
    $Logger.AddErrorRecord("test error")
    $Logger.AddInfoRecord("test info")
    $Logger.AddSuccessRecord("test success")
    $Logger.AddRecord("test record")
 
    .EXAMPLE
    Creates a logger with a directory name and auto-generated log name:
    $Logger = Get-Logger -ShowTime -LogsDir 'C:\temp'
    $Logger.AddErrorRecord("test error")
 
    .EXAMPLE
    Creates a logger with a directory name and a separately defined log filename:
    $Logger = Get-Logger -ShowTime -LogsDir 'C:\temp' -Filename 'test.log'
    $Logger.AddErrorRecord("test error")
 
    .EXAMPLE
    Creates a logger without a log file, only for console output:
    $Logger = Get-Logger -ShowTime
    $Logger.AddErrorRecord("test error")
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = "All")]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = 'Logpath')][string] $LogPath,
        [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $LogsDir,
        [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $Filename,
        [switch] $ShowTime,
        [string] $TimeFormat = 'yyyy-MM-dd HH:mm:ss'
    )

    if ($PSCmdlet.ParameterSetName -eq 'Complexpath') {
        if (-not $Filename) {
            $CallerName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path $MyInvocation.PSCommandPath -Leaf))
            $Filename = "$([DateTime]::Now.ToString($TimeFormat) -replace('[^.\-\w]', '_'))_$CallerName.log"
        }
        $LogPath = Join-Path $LogsDir $Filename
    }

    if ($LogPath) {
        $LogsDir = [System.IO.Path]::GetDirectoryName($LogPath)
        New-Item $LogsDir -ItemType Directory -Force | Out-Null
        New-Item $LogPath -ItemType File -Force | Out-Null
    }

    $Logger = [PSCustomObject]@{
        LogPath    = $LogPath
        ShowTime   = $ShowTime
        TimeFormat = $TimeFormat
    }

    Add-Member -InputObject $Logger -MemberType ScriptMethod AddErrorRecord -Value {
        param(
            [Parameter(Mandatory = $true)]
            [string]$String
        )
        if (-not $this.LogPath) {
            Write-Color -Text "[Error] ", $String -Color Red, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
        else {
            Write-Color -Text "[Error] ", $String -Color Red, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
    }

    Add-Member -InputObject $Logger -MemberType ScriptMethod AddInfoRecord -Value {
        param(
            [Parameter(Mandatory = $true)]
            [string]$String
        )
        if (-not $this.LogPath) {
            Write-Color -Text "[Info] ", $String -Color Yellow, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
        else {
            Write-Color -Text "[Info] ", $String -Color Yellow, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
    }

    Add-Member -InputObject $Logger -MemberType ScriptMethod AddWarningRecord -Value {
        param(
            [Parameter(Mandatory = $true)]
            [string]$String
        )
        if (-not $this.LogPath) {
            Write-Color -Text "[Warning] ", $String -Color Magenta, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
        else {
            Write-Color -Text "[Warning] ", $String -Color Magenta, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
    }

    Add-Member -InputObject $Logger -MemberType ScriptMethod AddRecord -Value {
        param(
            [Parameter(Mandatory = $true)]
            [string]$String
        )
        if (-not $this.LogPath) {
            Write-Color -Text " $String" -Color White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
        else {
            Write-Color -Text " $String" -Color White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
    }
    Add-Member -InputObject $Logger -MemberType ScriptMethod AddSuccessRecord -Value {
        param(
            [Parameter(Mandatory = $true)]
            [string]$String
        )
        if (-not $this.LogPath) {
            Write-Color -Text "[Success] ", $String -Color Green, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
        else {
            Write-Color -Text "[Success] ", $String -Color Green, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat
        }
    }
    return $Logger
}
function Add-ToArray {
    <#
    .SYNOPSIS
    Adds an element to an ArrayList.
 
    .DESCRIPTION
    This function adds an element to the specified ArrayList.
 
    .PARAMETER List
    The ArrayList to which the element will be added.
 
    .PARAMETER Element
    The element to be added to the ArrayList.
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    Add-ToArray -List $myList -Element "Apple"
    # Adds the string "Apple" to the ArrayList $myList.
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    Add-ToArray -List $myList -Element 42
    # Adds the integer 42 to the ArrayList $myList.
    #>

    [CmdletBinding()]
    param(
        [System.Collections.ArrayList] $List,
        [Object] $Element
    )

    [void] $List.Add($Element) 
}

function Add-ToArrayAdvanced {
    <#
    .SYNOPSIS
    Adds an element to an ArrayList with advanced options.
 
    .DESCRIPTION
    The Add-ToArrayAdvanced function adds an element to an ArrayList with various options such as skipping null elements, requiring uniqueness, performing full comparison, and merging elements.
 
    .PARAMETER List
    The ArrayList to which the element will be added.
 
    .PARAMETER Element
    The element to be added to the ArrayList.
 
    .PARAMETER SkipNull
    If specified, skips adding null elements to the ArrayList.
 
    .PARAMETER RequireUnique
    If specified, ensures that the element is unique in the ArrayList.
 
    .PARAMETER FullComparison
    If specified with RequireUnique, performs a full comparison of elements before adding.
 
    .PARAMETER Merge
    If specified, merges the element into the ArrayList.
 
    .EXAMPLE
    Add-ToArrayAdvanced -List $myList -Element "Apple"
 
    Description:
    Adds the string "Apple" to the ArrayList $myList.
 
    .EXAMPLE
    Add-ToArrayAdvanced -List $myList -Element "Banana" -RequireUnique -FullComparison
 
    Description:
    Adds the string "Banana" to the ArrayList $myList only if it is not already present, performing a full comparison.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.ArrayList] $List,
        [Object] $Element,
        [switch] $SkipNull,
        [switch] $RequireUnique,
        [switch] $FullComparison,
        [switch] $Merge
    )
    if ($SkipNull -and $null -eq $Element) {

        return
    }
    if ($RequireUnique) {
        if ($FullComparison) {
            foreach ($ListElement in $List) {
                if ($ListElement -eq $Element) {
                    $TypeLeft = Get-ObjectType -Object $ListElement
                    $TypeRight = Get-ObjectType -Object $Element
                    if ($TypeLeft.ObjectTypeName -eq $TypeRight.ObjectTypeName) {

                        return
                    }
                }
            }
        }
        else {
            if ($List -contains $Element) {

                return
            }
        }
    }

    if ($Merge) {
        [void] $List.AddRange($Element) 
    }
    else {
        [void] $List.Add($Element) 
    }
}
function Add-ToHashTable($Hashtable, $Key, $Value) {
    <#
    .SYNOPSIS
    Adds a key-value pair to a hashtable.
 
    .DESCRIPTION
    This function adds a key-value pair to a given hashtable. If the value is not null or empty, it is added to the hashtable.
 
    .PARAMETER Hashtable
    The hashtable to which the key-value pair will be added.
 
    .PARAMETER Key
    The key of the key-value pair to be added.
 
    .PARAMETER Value
    The value of the key-value pair to be added.
 
    .EXAMPLE
    $myHashtable = @{}
    Add-ToHashTable -Hashtable $myHashtable -Key "Name" -Value "John"
    # Adds the key-value pair "Name"-"John" to $myHashtable.
 
    .EXAMPLE
    $myHashtable = @{}
    Add-ToHashTable -Hashtable $myHashtable -Key "Age" -Value 25
    # Adds the key-value pair "Age"-25 to $myHashtable.
    #>

    if ($null -ne $Value -and $Value -ne '') {
        $Hashtable.Add($Key, $Value)
    }
}
function Clear-DataInformation {
    <#
    .SYNOPSIS
    Cleans up data information by removing empty values and specified keys.
 
    .DESCRIPTION
    The Clear-DataInformation function removes empty values and specified keys from the provided data. It iterates through the data structure and removes keys with null values or keys not in the specified types required array. It also removes empty domains and the 'FoundDomains' key if it becomes empty.
 
    .PARAMETER Data
    The data structure containing information to be cleaned.
 
    .PARAMETER TypesRequired
    An array of types that are required to be kept in the data structure.
 
    .PARAMETER DontRemoveSupportData
    A switch parameter to indicate whether to skip removing keys not in TypesRequired.
 
    .PARAMETER DontRemoveEmpty
    A switch parameter to indicate whether to skip removing keys with null values.
 
    .EXAMPLE
    $data = @{
        FoundDomains = @{
            Domain1 = @{
                Key1 = 'Value1'
                Key2 = $null
            }
            Domain2 = @{
                Key1 = 'Value1'
                Key2 = 'Value2'
            }
        }
    }
    Clear-DataInformation -Data $data -TypesRequired @('Key1') -DontRemoveSupportData
 
    This example removes keys with null values from the 'FoundDomains' data structure, keeping only keys of type 'Key1'.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Data,
        [Array] $TypesRequired,
        [switch] $DontRemoveSupportData,
        [switch] $DontRemoveEmpty
    )

    foreach ($Domain in $Data.FoundDomains.Keys) {
        $RemoveDomainKeys = foreach ($Key in $Data.FoundDomains.$Domain.Keys) {
            if ($null -eq $Data.FoundDomains.$Domain.$Key) {
                if (-not $DontRemoveEmpty) {
                    $Key
                }
                continue
            }
            if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) {
                $Key
            }
        }
        foreach ($Key in $RemoveDomainKeys) {
            $Data.FoundDomains.$Domain.Remove($Key)
        }
    }

    $RemoveDomains = foreach ($Domain in $Data.FoundDomains.Keys) {
        if ($Data.FoundDomains.$Domain.Count -eq 0) {
            $Domain
        }
    }
    foreach ($Domain in $RemoveDomains) {
        $Data.FoundDomains.Remove($Domain)
    }

    if ($Data.FoundDomains.Count -eq 0) {
        $Data.Remove('FoundDomains')
    }

    $RemoveKeys = foreach ($Key in $Data.Keys) {
        if ($Key -eq 'FoundDomains') {

            continue
        }
        if ($null -eq $Data.$Key) {
            if (-not $DontRemoveEmpty) {
                $Key
            }
            continue
        }
        if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) {
            $Key
        }
    }
    foreach ($Key in $RemoveKeys) {
        $Data.Remove($Key)
    }
}
function Compare-MultipleObjects {
    <#
    .SYNOPSIS
    Compares multiple objects based on specified properties and displays differences.
 
    .DESCRIPTION
    The Compare-MultipleObjects function compares multiple objects based on specified properties and displays differences in a structured format. It provides options to customize the comparison output and handle various scenarios.
 
    .PARAMETER Objects
    Specifies the list of objects to compare.
 
    .PARAMETER ObjectsName
    Specifies an array of names for the objects being compared.
 
    .PARAMETER CompareSorted
    Indicates whether to compare objects in a sorted manner.
 
    .PARAMETER FormatOutput
    Indicates whether to format the output for better readability.
 
    .PARAMETER FormatDifferences
    Indicates whether to format and highlight the differences in the output.
 
    .PARAMETER Summary
    Indicates whether to display a summary of the comparison results.
 
    .PARAMETER Splitter
    Specifies the delimiter to use when joining property values.
 
    .PARAMETER Property
    Specifies the properties to compare across objects.
 
    .PARAMETER ExcludeProperty
    Specifies properties to exclude from the comparison.
 
    .PARAMETER AllProperties
    Indicates whether to compare all properties of the objects.
 
    .PARAMETER SkipProperties
    Indicates whether to skip comparing properties.
 
    .PARAMETER First
    Specifies the number of first objects to consider for comparison.
 
    .PARAMETER Last
    Specifies the number of last objects to consider for comparison.
 
    .PARAMETER Replace
    Specifies replacement values for specific properties.
 
    .PARAMETER FlattenObject
    Indicates whether to flatten the object structure for comparison.
 
    .EXAMPLE
    Compare-MultipleObjects -Objects $objects -Property 'Name', 'Age' -FormatOutput
 
    Description:
    Compares the objects in the $objects array based on the 'Name' and 'Age' properties and formats the output for better readability.
 
    .EXAMPLE
    Compare-MultipleObjects -Objects $objects -Property 'Status' -FormatDifferences
 
    Description:
    Compares the objects in the $objects array based on the 'Status' property and highlights the differences in the output.
 
    #>

    [CmdLetBinding()]
    param(
        [System.Collections.IList] $Objects,
        [Array] $ObjectsName = @(),
        [switch] $CompareSorted,
        [switch] $FormatOutput,
        [switch] $FormatDifferences,
        [switch] $Summary,
        [string] $Splitter = ', ',
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [switch] $SkipProperties,
        [int] $First,
        [int] $Last,
        [Array] $Replace,
        [switch] $FlattenObject
    )
    if ($null -eq $Objects -or $Objects.Count -eq 1) {
        Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($($Objects.Count))."
        return
    }
    if (-not $ObjectsName) {
        $ObjectsName = @()
    }
    if ($ObjectsName.Count -gt 0 -and $Objects.Count -gt $ObjectsName.Count) {

        Write-Warning -Message "Compare-MultipleObjects - Unable to rename objects. ObjectsName small then amount of Objects ($($Objects.Count))."
    }

    function Compare-TwoArrays {
        [CmdLetBinding()]
        param(
            [string] $FieldName,
            [Array] $Object1,
            [Array] $Object2,
            [Array] $Replace
        )
        $Result = [ordered] @{
            Status = $false
            Same   = [System.Collections.Generic.List[string]]::new()
            Add    = [System.Collections.Generic.List[string]]::new()
            Remove = [System.Collections.Generic.List[string]]::new()
        }

        if ($Replace) {
            foreach ($R in $Replace) {

                if (($($R.Keys[0]) -eq '') -or ($($R.Keys[0]) -eq $FieldName)) {
                    if ($null -ne $Object1) {
                        $Object1 = $Object1 -replace $($R.Values)[0], $($R.Values)[1]
                    }
                    if ($null -ne $Object2) {
                        $Object2 = $Object2 -replace $($R.Values)[0], $($R.Values)[1]
                    }
                }
            }
        }

        if ($null -eq $Object1 -and $null -eq $Object2) {
            $Result['Status'] = $true
        }
        elseif (($null -eq $Object1) -or ($null -eq $Object2)) {
            $Result['Status'] = $false
            foreach ($O in $Object1) {
                $Result['Add'].Add($O)
            }
            foreach ($O in $Object2) {
                $Result['Remove'].Add($O)
            }
        }
        else {
            $ComparedObject = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -IncludeEqual
            foreach ($_ in $ComparedObject) {
                if ($_.SideIndicator -eq '==') {
                    $Result['Same'].Add($_.InputObject)
                }
                elseif (($_.SideIndicator -eq '<=')) {
                    $Result['Add'].Add($_.InputObject)
                }
                elseif (($_.SideIndicator -eq '=>')) {
                    $Result['Remove'].Add($_.InputObject)
                }
            }
            IF ($Result['Add'].Count -eq 0 -and $Result['Remove'].Count -eq 0) {
                $Result['Status'] = $true
            }
            else {
                $Result['Status'] = $false
            }
        }
        $Result
    }

    if ($ObjectsName[0]) {
        $ValueSourceName = $ObjectsName[0]
    }
    else {
        $ValueSourceName = "Source"
    }

    [Array] $Objects = foreach ($Object in $Objects) {
        if ($null -eq $Object) {
            [PSCustomObject] @{}
        }
        else {
            $Object
        }
    }

    if ($FlattenObject) {
        try {
            [Array] $Objects = ConvertTo-FlatObject -Objects $Objects -ExcludeProperty $ExcludeProperty
        }
        catch {
            Write-Warning "Compare-MultipleObjects - Unable to flatten objects. ($($_.Exception.Message))"
        }
    }

    if ($First -or $Last) {
        [int] $TotalCount = $First + $Last
        if ($TotalCount -gt 1) {
            $Objects = $Objects | Select-Object -First $First -Last $Last
        }
        else {
            Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($TotalCount)."
            return
        }
    }
    $ReturnValues = @(
        $FirstElement = [ordered] @{ }
        $FirstElement['Name'] = 'Properties'
        if ($Summary) {
            $FirstElement['Same'] = $null
            $FirstElement['Different'] = $null
        }
        $FirstElement['Status'] = $false

        $FirstObjectProperties = Select-Properties -Objects $Objects -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties
        if (-not $SkipProperties) {
            if ($FormatOutput) {
                $FirstElement[$ValueSourceName] = $FirstObjectProperties -join $Splitter
            }
            else {
                $FirstElement[$ValueSourceName] = $FirstObjectProperties
            }
            [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) {
                if ($ObjectsName[$i]) {
                    $ValueToUse = $ObjectsName[$i]
                }
                else {
                    $ValueToUse = $i
                }
                if ($Objects[0] -is [System.Collections.IDictionary]) {
                    [string[]] $CompareObjectProperties = $Objects[$i].Keys
                }
                else {
                    [string[]] $CompareObjectProperties = $Objects[$i].PSObject.Properties.Name
                    [string[]] $CompareObjectProperties = Select-Properties -Objects $Objects[$i] -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties
                }

                if ($FormatOutput) {
                    $FirstElement["$ValueToUse"] = $CompareObjectProperties -join $Splitter
                }
                else {
                    $FirstElement["$ValueToUse"] = $CompareObjectProperties
                }
                if ($CompareSorted) {
                    $Value1 = $FirstObjectProperties | Sort-Object
                    $Value2 = $CompareObjectProperties | Sort-Object
                }
                else {
                    $Value1 = $FirstObjectProperties
                    $Value2 = $CompareObjectProperties
                }

                $Status = Compare-TwoArrays -FieldName 'Properties' -Object1 $Value1 -Object2 $Value2 -Replace $Replace
                if ($FormatDifferences) {
                    $FirstElement["$ValueToUse-Add"] = $Status['Add'] -join $Splitter
                    $FirstElement["$ValueToUse-Remove"] = $Status['Remove'] -join $Splitter
                    $FirstElement["$ValueToUse-Same"] = $Status['Same'] -join $Splitter
                }
                else {
                    $FirstElement["$ValueToUse-Add"] = $Status['Add']
                    $FirstElement["$ValueToUse-Remove"] = $Status['Remove']
                    $FirstElement["$ValueToUse-Same"] = $Status['Same']
                }
                $Status
            }
            if ($IsSame.Status -notcontains $false) {
                $FirstElement['Status'] = $true
            }
            else {
                $FirstElement['Status'] = $false
            }
            if ($Summary) {
                [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split')
                if ($FormatDifferences) {
                    $FirstElement['Same'] = ($Collection[0] | ForEach-Object {
                            $Count = $_ + 1
                            if ($ObjectsName[$Count]) {
                                $ObjectsName[$Count]
                            }
                            else {
                                $Count
                            }
                        }
                    ) -join $Splitter
                    $FirstElement['Different'] = ($Collection[1] | ForEach-Object {
                            $Count = $_ + 1
                            if ($ObjectsName[$Count]) {
                                $ObjectsName[$Count]
                            }
                            else {
                                $Count
                            }
                        }
                    ) -join $Splitter
                }
                else {
                    $FirstElement['Same'] = $Collection[0] | ForEach-Object {
                        $Count = $_ + 1
                        if ($ObjectsName[$Count]) {
                            $ObjectsName[$Count]
                        }
                        else {
                            $Count
                        }
                    }
                    $FirstElement['Different'] = $Collection[1] | ForEach-Object {
                        $Count = $_ + 1
                        if ($ObjectsName[$Count]) {
                            $ObjectsName[$Count]
                        }
                        else {
                            $Count
                        }
                    }
                }
            }
            [PSCustomObject] $FirstElement
        }

        foreach ($NameProperty in $FirstObjectProperties) {
            $EveryOtherElement = [ordered] @{ }
            $EveryOtherElement['Name'] = $NameProperty
            if ($Summary) {
                $EveryOtherElement['Same'] = $null
                $EveryOtherElement['Different'] = $null
            }
            $EveryOtherElement.Status = $false

            if ($FormatOutput) {
                $EveryOtherElement[$ValueSourceName] = $Objects[0].$NameProperty -join $Splitter
            }
            else {
                $EveryOtherElement[$ValueSourceName] = $Objects[0].$NameProperty
            }

            [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) {
                $Skip = $false

                if ($ObjectsName[$i]) {
                    $ValueToUse = $ObjectsName[$i]
                }
                else {
                    $ValueToUse = $i
                }

                if ($Objects[$i] -is [System.Collections.IDictionary]) {
                    if ($Objects[$i].Keys -notcontains $NameProperty) {
                        $Status = [ordered] @{
                            Status = $false
                            Same   = @()
                            Add    = @()
                            Remove = @()
                        }
                        $Skip = $true
                    }
                }
                elseif ($Objects[$i].PSObject.Properties.Name -notcontains $NameProperty) {
                    $Status = [ordered] @{
                        Status = $false;
                        Same   = @()
                        Add    = @()
                        Remove = @()
                    }
                    $Skip = $true
                }

                if ($FormatOutput) {
                    $EveryOtherElement["$ValueToUse"] = $Objects[$i].$NameProperty -join $Splitter
                }
                else {
                    $EveryOtherElement["$ValueToUse"] = $Objects[$i].$NameProperty
                }

                if ($CompareSorted) {
                    $Value1 = $Objects[0].$NameProperty | Sort-Object
                    $Value2 = $Objects[$i].$NameProperty | Sort-Object
                }
                else {
                    $Value1 = $Objects[0].$NameProperty
                    $Value2 = $Objects[$i].$NameProperty
                }

                if ($Value1 -is [PSCustomObject]) {
                    [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() }
                    continue
                }
                elseif ($Value1 -is [System.Collections.IDictionary]) {
                    [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() }
                    continue
                }
                elseif ($Value1 -is [Array] -and $Value1.Count -ne 0 -and $Value1[0] -isnot [string]) {
                    [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() }
                    continue
                }
                if (-not $Skip) {
                    $Status = Compare-TwoArrays -FieldName $NameProperty -Object1 $Value1 -Object2 $Value2 -Replace $Replace
                }
                else {
                    $Status['Add'] = $Value1
                }
                if ($FormatDifferences) {
                    $EveryOtherElement["$ValueToUse-Add"] = $Status['Add'] -join $Splitter
                    $EveryOtherElement["$ValueToUse-Remove"] = $Status['Remove'] -join $Splitter
                    $EveryOtherElement["$ValueToUse-Same"] = $Status['Same'] -join $Splitter
                }
                else {
                    $EveryOtherElement["$ValueToUse-Add"] = $Status['Add']
                    $EveryOtherElement["$ValueToUse-Remove"] = $Status['Remove']
                    $EveryOtherElement["$ValueToUse-Same"] = $Status['Same']
                }
                $Status
            }
            if ($null -eq $IsSame.Status) {
                $EveryOtherElement['Status'] = $null
            }
            elseif ($IsSame.Status -notcontains $false) {
                $EveryOtherElement['Status'] = $true
            }
            else {
                $EveryOtherElement['Status'] = $false
            }

            if ($Summary) {
                [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split')
                if ($FormatDifferences) {
                    $EveryOtherElement['Same'] = ($Collection[0] | ForEach-Object {
                            $Count = $_ + 1
                            if ($ObjectsName[$Count]) {
                                $ObjectsName[$Count]
                            }
                            else {
                                $Count
                            }
                        }
                    ) -join $Splitter
                    $EveryOtherElement['Different'] = ($Collection[1] | ForEach-Object {
                            $Count = $_ + 1
                            if ($ObjectsName[$Count]) {
                                $ObjectsName[$Count]
                            }
                            else {
                                $Count
                            }
                        }
                    ) -join $Splitter
                }
                else {
                    $EveryOtherElement['Same'] = $Collection[0] | ForEach-Object {
                        $Count = $_ + 1
                        if ($ObjectsName[$Count]) {
                            $ObjectsName[$Count]
                        }
                        else {
                            $Count
                        }
                    }
                    $EveryOtherElement['Different'] = $Collection[1] | ForEach-Object {
                        $Count = $_ + 1
                        if ($ObjectsName[$Count]) {
                            $ObjectsName[$Count]
                        }
                        else {
                            $Count
                        }
                    }
                }
            }
            [PSCuStomObject] $EveryOtherElement
        }
    )
    if ($ReturnValues.Count -eq 1) {
        return , $ReturnValues
    }
    else {
        return $ReturnValues
    }
}
function Compare-ObjectsAdvanced {
    <#
    .SYNOPSIS
    Compares two sets of objects based on a specified property.
 
    .DESCRIPTION
    This function compares two sets of objects based on a specified property. It can be used to identify differences between the objects and perform actions accordingly.
 
    .PARAMETER Object1
    The first set of objects to compare.
 
    .PARAMETER Object2
    The second set of objects to compare.
 
    .PARAMETER CommonProperty
    Specifies the common property to compare between the objects. Default is 'DistinguishedName'.
 
    .PARAMETER AddObjectArrayName
    An array of names for additional properties to add to Object1.
 
    .PARAMETER AddObjectArray
    An array of values for additional properties to add to Object1.
 
    .PARAMETER Object1Property
    Specifies a property from Object1 to compare.
 
    .PARAMETER Object2Property
    Specifies a property from Object2 to compare.
 
    .PARAMETER ObjectPropertySubstitute
    Specifies a substitute property name for comparison. Default is 'SpecialValueToCompare'.
 
    .PARAMETER RemoveSideIndicator
    Indicates whether to remove side indicators in the comparison results.
 
    .PARAMETER KeepTemporaryProperty
    Indicates whether to keep temporary properties added during comparison.
 
    .PARAMETER Side
    Specifies which side to compare ('Left' or 'Right'). Default is 'Left'.
 
    .EXAMPLE
    Compare-ObjectsAdvanced -Object1 $ObjectSet1 -Object2 $ObjectSet2 -CommonProperty 'Name' -Object1Property 'Size' -Object2Property 'Size'
 
    Description:
    Compares two sets of objects based on the 'Name' property and the 'Size' property from each set.
 
    .EXAMPLE
    Compare-ObjectsAdvanced -Object1 $Users -Object2 $Groups -CommonProperty 'DistinguishedName' -AddObjectArrayName @('Type', 'Status') -AddObjectArray @('User', 'Active') -Side 'Right'
 
    Description:
    Compares two sets of objects based on the 'DistinguishedName' property, adding 'Type' and 'Status' properties to Object1, and compares from the 'Right' side.
 
    #>

    param(
        [object] $Object1,
        [object] $Object2,
        [alias('Property')][string] $CommonProperty = 'DistinguishedName',
        [string[]] $AddObjectArrayName,
        [object[]] $AddObjectArray,
        [string] $Object1Property,
        [string] $Object2Property,
        [string] $ObjectPropertySubstitute = 'SpecialValueToCompare',
        [switch] $RemoveSideIndicator,
        [switch] $KeepTemporaryProperty,
        [ValidateSet('Left', 'Right')][string] $Side = 'Left' # May need Both later on
    )
    $Objects = New-GenericList

    if ($null -eq $Object1 -and $null -eq $Object2) {
    }
    elseif ($null -eq $Object1) {
    }
    elseif ($null -eq $Object2) {
        foreach ($G in $Object1) {

            for ($a = 0; $a -lt $AddObjectArrayName.Count; $a++) {
                $G | Add-Member -MemberType NoteProperty -Name $AddObjectArrayName[$a] -Value $AddObjectArray[$a] -Force
            }
            $Objects.Add($G)
        }
    }
    else {
        $Terminate = New-GenericList -Type [bool]

        if ($Object1Property -and $Object2Property) {                
            if ($Object1[0].PSObject.Properties.Name -notcontains $Object1Property) {
                Write-Warning -Message "Compare-InfrastructureObjects - Object1 property doesn't exists $Object1Property"
                $Terminate.Add($true)
            }
            if ($Object2[0].PSObject.Properties.Name -notcontains $Object2Property) {
                Write-Warning -Message "Compare-InfrastructureObjects - Object2 property doesn't exists $Object2Property"
                $Terminate.Add($true)
            }
            if ($Terminate -contains $true) {
                return
            }
            $Object1 | Add-Member -MemberType AliasProperty -Name $ObjectPropertySubstitute -Value $Object1Property -Force
            $Object2 | Add-Member -MemberType AliasProperty -Name $ObjectPropertySubstitute -Value $Object2Property -Force
            $Compare = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -Property  $ObjectPropertySubstitute -PassThru
        }
        else {
            if ($Object1[0].PSObject.Properties.Name -notcontains $CommonProperty) {
                Write-Warning -Message "Compare-InfrastructureObjects - Object1 property doesn't exists $CommonProperty"
                $Terminate.Add($true)
            }
            if ($Object2[0].PSObject.Properties.Name -notcontains $CommonProperty) {
                Write-Warning -Message "Compare-InfrastructureObjects - Object2 property doesn't exists $CommonProperty"
                $Terminate.Add($true)
            }
            if ($Terminate -contains $true) {
                return
            }
            $Compare = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -Property $CommonProperty -PassThru
        }
        if ($Side -eq 'Left') {
            $Compare = $Compare | Where-Object { $_.SideIndicator -eq '<=' }
        }
        elseif ($Side -eq 'Right') {
            $Compare = $Compare | Where-Object { $_.SideIndicator -eq '=>' }
        }
        else {

            $Compare = $Compare | Where-Object { $_.SideIndicator -eq '==' }
        }
        foreach ($G in $Compare) {
            if ($RemoveSideIndicator) {
                $G.PSObject.Members.Remove('SideIndicator')
            }
            if (-not $KeepTemporaryProperty) {
                $G.PSObject.Members.Remove($ObjectPropertySubstitute)
            }
            for ($a = 0; $a -lt $AddObjectArrayName.Count; $a++) {
                $G | Add-Member -MemberType NoteProperty -Name $AddObjectArrayName[$a] -Value $AddObjectArray[$a] -Force
            }
            $Objects.Add($G) 
        }
    }  
    return $Objects
}

Function Compare-ObjectProperties {
    <#
    .SYNOPSIS
    Compares the properties of two objects and returns the differences.
 
    .DESCRIPTION
    This function compares the properties of two objects and returns the differences found between them. It compares each property of the reference object with the corresponding property of the difference object.
 
    .PARAMETER ReferenceObject
    The reference object to compare properties against.
 
    .PARAMETER DifferenceObject
    The object whose properties are compared against the reference object.
 
    .PARAMETER CaseSensitive
    Indicates whether the comparison should be case-sensitive. Default is false.
 
    .EXAMPLE
    $ad1 = Get-ADUser amelia.mitchell -Properties *
    $ad2 = Get-ADUser carolyn.quinn -Properties *
    Compare-ObjectProperties $ad1 $ad2
    #>

    Param(
        [PSObject]$ReferenceObject,
        [PSObject]$DifferenceObject,
        [switch]$CaseSensitive = $false
    )
    $objprops = @(
        $($ReferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name),
        $($DifferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name)
    )
    $objprops = $objprops | Sort-Object -Unique
    $diffs = foreach ($objprop in $objprops) {
        $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop -CaseSensitive:$CaseSensitive
        if ($diff) {
            $diffprops = [PsCustomObject] @{
                PropertyName = $objprop
                RefValue     = ($diff | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objprop))
                DiffValue    = ($diff | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objprop))
            }
            $diffprops
        }
    }
    if ($diffs) {
        return ($diffs | Select-Object PropertyName, RefValue, DiffValue)
    }
}

function Copy-Dictionary {
    <#
    .SYNOPSIS
    Copies dictionary/hashtable
 
    .DESCRIPTION
    Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4
 
    .PARAMETER Dictionary
    Dictionary to copy
 
    .EXAMPLE
    $Test = [ordered] @{
        Test = 'Test'
        Test1 = @{
            Test2 = 'Test2'
            Test3 = @{
                Test4 = 'Test4'
            }
        }
        Test2 = @(
            "1", "2", "3"
        )
        Test3 = [PSCustomObject] @{
            Test4 = 'Test4'
            Test5 = 'Test5'
        }
    }
 
    $New1 = Copy-Dictionary -Dictionary $Test
    $New1
 
    .NOTES
 
    #>

    [alias('Copy-Hashtable', 'Copy-OrderedHashtable')]
    [cmdletbinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )
    $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue)
    return [System.Management.Automation.PSSerializer]::Deserialize($clone)
}
function Copy-DictionaryManual {
    <#
    .SYNOPSIS
    Copies a dictionary recursively, handling nested dictionaries and lists.
 
    .DESCRIPTION
    This function copies a dictionary recursively, handling nested dictionaries and lists. It creates a deep copy of the input dictionary, ensuring that modifications to the copied dictionary do not affect the original dictionary.
 
    .PARAMETER Dictionary
    The dictionary to be copied.
 
    .EXAMPLE
    $originalDictionary = @{
        'Key1' = 'Value1'
        'Key2' = @{
            'NestedKey1' = 'NestedValue1'
        }
    }
    $copiedDictionary = Copy-DictionaryManual -Dictionary $originalDictionary
 
    This example demonstrates how to copy a dictionary with nested values.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IDictionary] $Dictionary
    )

    $clone = [ordered] @{}
    foreach ($Key in $Dictionary.Keys) {
        $value = $Dictionary.$Key

        $clonedValue = switch ($Dictionary.$Key) {
            { $null -eq $_ } {
                $null
                continue
            }
            { $_ -is [System.Collections.IDictionary] } {
                Copy-DictionaryManual -Dictionary $_
                continue
            }
            {
                $type = $_.GetType()
                $type.IsPrimitive -or $type.IsValueType -or $_ -is [string]
            } {
                $_
                continue
            }
            default {
                $_ | Select-Object -Property *
            }
        }

        if ($value -is [System.Collections.IList]) {
            $clone[$Key] = @($clonedValue)
        }
        else {
            $clone[$Key] = $clonedValue
        }
    }

    $clone
}
function Format-Dictionary {
    <#
    .SYNOPSIS
    Sorts dictionary/hashtable keys including nested hashtables and returns ordered dictionary
 
    .DESCRIPTION
    Sorts dictionary/hashtable keys including nested hashtables and returns ordered dictionary
 
    .PARAMETER Hashtable
    Hashtable to sort
 
    .EXAMPLE
    $Hashtable = [ordered] @{
        ModuleVersion = '2.0.X'
        #PreReleaseTag = 'Preview5'
        CompatiblePSEditions = @('Desktop', 'Core')
 
        RunMe = @{
            Name = 'RunMe'
            Type = 'Script'
            Path = 'RunMe.ps1'
        }
        GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d'
        Author = 'Przemyslaw Klys'
        CompanyName = 'Evotec'
        Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved."
        Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery'
        PowerShellVersion = '5.1'
        Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module')
        IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png'
        ProjectUri = 'https://github.com/EvotecIT/PSPublishModule'
        DotNetFrameworkVersion = '4.5.2'
    }
    $Hashtable = Format-Dictionary -Hashtable $Hashtable
    $Hashtable
 
    .EXAMPLE
    $Hashtable = Format-Dictionary -Hashtable $Hashtable -ByValue
    $Hashtable
 
    .EXAMPLE
    $Hashtable = Format-Dictionary -Hashtable $Hashtable -ByValue -Descending
    $Hashtable
 
    .NOTES
    General notes
    #>

    [alias('Sort-Dictionary')]
    [CmdletBinding()]
    param(
        [alias('Dictionary', 'OrderedDictionary')]
        [Parameter(Mandatory, ValueFromPipeline)]
        [System.Collections.IDictionary] $Hashtable,
        [switch] $Descending,
        [switch] $ByValue
    )
    $Ordered = [ordered] @{}
    if ($ByValue) {

        $Hashtable.GetEnumerator() | Sort-Object -Property Value -Descending:$Descending.IsPresent | ForEach-Object {
            if ($_.Value -is [System.Collections.IDictionary]) {
                $Ordered[$_.Key] = Format-Dictionary -Hashtable $_.Value -Descending:$Descending.IsPresent -ByValue:$ByValue.IsPresent
            }
            else {
                $Ordered[$_.Key] = $_.Value
            }
        }
    }
    else {
        foreach ($K in [string[]] $Hashtable.Keys | Sort-Object -Descending:$Descending.IsPresent) {
            if ($Hashtable[$K] -is [System.Collections.IDictionary]) {
                $Ordered[$K] = Format-Dictionary -Hashtable $Hashtable[$K] -Descending:$Descending.IsPresent -ByValue:$ByValue.IsPresent
            }
            else {
                $Ordered[$K] = $Hashtable[$K]
            }
        }
    }
    $Ordered
}
function Format-FirstXChars {
    <#
    .SYNOPSIS
    This function returns the first X characters of a given text string.
 
    .DESCRIPTION
    The Format-FirstXChars function takes a text string and a number of characters as input and returns the first X characters of the text string.
 
    .PARAMETER Text
    The input text string from which the first X characters will be extracted.
 
    .PARAMETER NumberChars
    The number of characters to extract from the beginning of the input text string.
 
    .EXAMPLE
    Format-FirstXChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15
    # Returns: VERBOSE: Loading
 
    .NOTES
    This function is useful for truncating long text strings to a specific length.
    #>

    param(
        [string] $Text,
        [int] $NumberChars
    )
    return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join ''
}
function Format-PSTable {
    <#
    .SYNOPSIS
    Formats a collection of objects into a table for display.
 
    .DESCRIPTION
    The Format-PSTable function takes a collection of objects and formats them into a table for easy display. It provides options to customize the output by selecting specific properties, excluding certain properties, and more.
 
    .PARAMETER Object
    Specifies the collection of objects to format.
 
    .PARAMETER SkipTitle
    Indicates whether to skip displaying the title row in the table.
 
    .PARAMETER Property
    Specifies an array of property names to include in the table.
 
    .PARAMETER ExcludeProperty
    Specifies an array of property names to exclude from the table.
 
    .PARAMETER OverwriteHeaders
    Specifies an object to use as headers for the table.
 
    .PARAMETER PreScanHeaders
    Indicates whether to pre-scan the object properties to determine headers.
 
    .PARAMETER Splitter
    Specifies the delimiter to use when joining array values.
 
    .EXAMPLE
    $data | Format-PSTable
 
    Description:
    Formats the $data collection into a table with default settings.
 
    .EXAMPLE
    $data | Format-PSTable -Property Name, Age
 
    Description:
    Formats the $data collection into a table displaying only the 'Name' and 'Age' properties.
 
    .EXAMPLE
    $data | Format-PSTable -ExcludeProperty ID
 
    Description:
    Formats the $data collection into a table excluding the 'ID' property.
 
    #>

    [CmdletBinding()]
    param (
        [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][System.Collections.ICollection] $Object,
        [switch] $SkipTitle,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [Object] $OverwriteHeaders,
        [switch] $PreScanHeaders,
        [string] $Splitter = ';'
    )
    if ($Object[0] -is [System.Collections.IDictionary]) {
        $Array = @(

            if (-not $SkipTitle) {
                , @('Name', 'Value')
            }

            foreach ($O in $Object) {
                foreach ($Name in $O.Keys) {
                    $Value = $O[$Name]

                    if ($O[$Name].Count -gt 1) {
                        $Value = $O[$Name] -join $Splitter
                    }
                    else {
                        $Value = $O[$Name]
                    }
                    , @($Name, $Value)
                }
            }
        )
        if ($Array.Count -eq 1) {
            , $Array 
        }
        else {
            $Array 
        }
    }
    elseif ($Object[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') {
        return $Object
    }
    else {
        if ($Property) {
            $Object = $Object | Select-Object -Property $Property
        }
        $Array = @(

            if ($PreScanHeaders) {
                $Titles = Get-ObjectProperties -Object $Object
            }
            elseif ($OverwriteHeaders) {
                $Titles = $OverwriteHeaders
            }
            else {

                $Titles = $Object[0].PSObject.Properties.Name
            }
            if (-not $SkipTitle) {

                , $Titles
            }

            foreach ($O in $Object) {
                $ArrayValues = foreach ($Name in $Titles) {
                    $Value = $O."$Name"

                    if ($Value.Count -gt 1) {
                        $Value -join $Splitter
                    }
                    elseif ($Value.Count -eq 1) {
                        if ($Value.Value) {
                            $Value.Value
                        }
                        else {
                            $Value
                        }
                    }
                    else {
                        ''
                    }
                }
                , $ArrayValues
            }

        )
        if ($Array.Count -eq 1) {
            , $Array 
        }
        else {
            $Array 
        }
    }
}
function Format-Stream {
    <#
    .SYNOPSIS
    Formats input objects for display in a stream-oriented manner.
 
    .DESCRIPTION
    The Format-Stream function formats input objects for display in a stream-oriented manner. It provides flexibility in displaying data in various streams such as Output, Host, Warning, Verbose, Debug, and Information.
 
    .PARAMETER InputObject
    Specifies the input objects to be formatted.
 
    .PARAMETER Property
    Specifies the properties of the input objects to include in the output.
 
    .PARAMETER ExcludeProperty
    Specifies the properties of the input objects to exclude from the output.
 
    .PARAMETER HideTableHeaders
    Indicates whether to hide the table headers in the output.
 
    .PARAMETER ColumnHeaderSize
    Specifies the size of the column headers in the output.
 
    .PARAMETER AlignRight
    Indicates whether to align the output data to the right.
 
    .PARAMETER Stream
    Specifies the stream to display the formatted data. Valid values are 'Output', 'Host', 'Warning', 'Verbose', 'Debug', and 'Information'.
 
    .PARAMETER List
    Indicates whether to display the output as a list.
 
    .PARAMETER Transpose
    Indicates whether to transpose the columns and rows of the output.
 
    .PARAMETER TransposeSort
    Specifies the sorting order when transposing the data. Valid values are 'ASC', 'DESC', and 'NONE'.
 
    .PARAMETER ForegroundColor
    Specifies the foreground color of the output.
 
    .PARAMETER ForegroundColorRow
    Specifies the foreground color of specific rows in the output.
 
    .EXAMPLE
    Get-Process | Format-Stream -Property Name, Id -Stream Host
    Displays the Name and Id properties of the processes in the Host stream.
 
    .EXAMPLE
    Get-Service | Format-Stream -ExcludeProperty Status -List
    Displays all service properties except Status as a list.
 
    #>

    [alias('FS', 'Format-TableStream', 'Format-ListStream')]
    ##[alias('ftv','ftd','fto','fth','fti','flv','fld','flo','flh','fli','Format-TableVerbose', 'Format-TableDebug', 'Format-TableInformation', 'Format-TableWarning')]
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1)]
        [Array] $InputObject,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 0, ParameterSetName = 'Property')]
        [string[]] $Property,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 2, ParameterSetName = 'ExcludeProperty')]
        [string[]] $ExcludeProperty,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)]
        [switch] $HideTableHeaders,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 4)]
        [int] $ColumnHeaderSize,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 5)]
        [switch] $AlignRight,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6)]
        [validateset('Output', 'Host', 'Warning', 'Verbose', 'Debug', 'Information')]
        [string] $Stream = 'Verbose',

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 7)]
        [alias('AsList')][switch] $List,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 8)]
        [alias('Rotate', 'RotateData', 'TransposeColumnsRows', 'TransposeData')]
        [switch] $Transpose,

        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 9)]
        [ValidateSet('ASC', 'DESC', 'NONE')]
        [string] $TransposeSort = 'NONE',

        [alias('Color')]
        [System.ConsoleColor[]] $ForegroundColor,

        [alias('ColorRow')]
        [int[]] $ForegroundColorRow
    )
    Begin {
        $IsVerbosePresent = $PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent

        if ($Stream -eq 'Output') {
        }
        elseif ($Stream -eq 'Host') {
        }
        elseif ($Stream -eq 'Warning') {
            [System.Management.Automation.ActionPreference] $WarningCurrent = $WarningPreference
            $WarningPreference = 'continue'
        }
        elseif ($Stream -eq 'Verbose') {
            [System.Management.Automation.ActionPreference] $VerboseCurrent = $VerbosePreference
            $VerbosePreference = 'continue'
        }
        elseif ($Stream -eq 'Debug') {
            [System.Management.Automation.ActionPreference] $DebugCurrent = $DebugPreference
            $DebugPreference = 'continue'
        }
        elseif ($Stream -eq 'Information') {
            [System.Management.Automation.ActionPreference] $InformationCurrent = $InformationPreference
            $InformationPreference = 'continue'
        }

        [bool] $FirstRun = $True 
        [bool] $FirstLoop = $True 
        [bool] $FirstList = $True 
        [int] $ScreenWidth = $Host.UI.RawUI.WindowSize.Width - 12 
        $ArrayList = @()
    }
    Process {
        if ($InputObject.Count -eq 0) {
            break 
        }
        if ($FirstRun) {
            $FirstRun = $false
            if ($Transpose) {
                $InputObject = Format-TransposeTable -Object $InputObject -Sort $TransposeSort 
            }
            $Data = Format-PSTable -Object $InputObject -Property $Property -ExcludeProperty $ExcludeProperty -NoAliasOrScriptProperties:$NoAliasOrScriptProperties -DisplayPropertySet:$DisplayPropertySet -PreScanHeaders:$PreScanHeaders
            $Headers = $Data[0]
            if ($HideTableHeaders) {
                $Data.RemoveAt(0);
            }
            $ArrayList += $Data
        }
        else {
            if ($Transpose) {
                $InputObject = Format-TransposeTable -Object $InputObject -Sort $TransposeSort 
            }
            $Data = Format-PSTable -Object $InputObject -Property $Property -ExcludeProperty $ExcludeProperty -NoAliasOrScriptProperties:$NoAliasOrScriptProperties -DisplayPropertySet:$DisplayPropertySet -PreScanHeaders:$PreScanHeaders -OverwriteHeaders $Headers -SkipTitle
            $ArrayList += $Data
        }
    }
    End {
        if (-not $ColumnHeaderSize) {
            $ColumnLength = [int[]]::new($Headers.Count);
            foreach ($Row in $ArrayList) {
                $i = 0
                foreach ($Column in $Row) {
                    $Length = "$Column".Length
                    if ($Length -gt $ColumnLength[$i]) {
                        $ColumnLength[$i] = $Length
                    }
                    $i++
                }
            }
            if ($IsVerbosePresent) {
                Write-Verbose "Format-TableVerbose - ScreenWidth $ScreenWidth"
                Write-Verbose "Format-TableVerbose - Column Lengths $($ColumnLength -join ',')"
            }
        }

        if ($Stream -eq 'Output') {
            Write-Output -InputObject ''
        }
        elseif ($Stream -eq 'Host') {
            Write-Host -Object ''
        }
        elseif ($Stream -eq 'Warning') {
            Write-Warning -Message ''
        }
        elseif ($Stream -eq 'Verbose') {
            Write-Verbose -Message ''
        }
        elseif ($Stream -eq 'Debug') {
            Write-Debug -Message ''
        }
        elseif ($Stream -eq 'Information') {
            Write-Information -MessageData ''
        }
        if ($List) {
            [int] $RowCount = 1
            foreach ($Row in $ArrayList ) {
                [string] $Output = ''
                [int] $ColumnNumber = 0
                [int] $CurrentColumnLength = 0

                if ($ColumnHeaderSize) {
                    $PadLength = $ColumnHeaderSize 
                }
                else {
                    $PadLength = (($Headers.Length | Measure-Object -Maximum).Maximum) + 1 
                }

                if (-not $FirstList) {
                    $i = 0
                    foreach ($ColumnValue in $Row) {
                        if (-not $HideTableHeaders) {

                            if ($AlignRight) {
                                $Head = $($Headers[$i]).PadLeft($PadLength)
                            }
                            else {
                                $Head = $($Headers[$i]).PadRight($PadLength)
                            }
                            $Output = "$Head`: $ColumnValue"
                        }
                        else {

                            $Output = "$ColumnValue"
                        }

                        if ($Stream -eq 'Output') {
                            Write-Output -InputObject $Output
                        }
                        elseif ($Stream -eq 'Host') {
                            Write-Host -Object $Output
                        }
                        elseif ($Stream -eq 'Warning') {
                            Write-Warning -Message $Output
                        }
                        elseif ($Stream -eq 'Verbose') {
                            Write-Verbose -Message $Output
                        }
                        elseif ($Stream -eq 'Debug') {
                            Write-Debug -Message $Output
                        }
                        elseif ($Stream -eq 'Information') {
                            Write-Information -MessageData $Output
                        }
                        $i++
                    }
                    $RowCount++
                    if ($RowCount -ne $ArrayList.Count) {

                        if ($Stream -eq 'Output') {
                            Write-Output -InputObject ''
                        }
                        elseif ($Stream -eq 'Host') {
                            Write-Host -Object ''
                        }
                        elseif ($Stream -eq 'Warning') {
                            Write-Warning -Message ''
                        }
                        elseif ($Stream -eq 'Verbose') {
                            Write-Verbose -Message ''
                        }
                        elseif ($Stream -eq 'Debug') {
                            Write-Debug -Message ''
                        }
                        elseif ($Stream -eq 'Information') {
                            Write-Information -MessageData ''
                        }
                    }
                }
                $FirstList = $false
            }
        }
        else {

            [int] $RowCountColors = 1
            foreach ($Row in $ArrayList ) {
                [string] $Output = ''
                [int] $ColumnNumber = 0
                [int] $CurrentColumnLength = 0

                foreach ($ColumnValue in $Row) {

                    if ($ColumnHeaderSize) {
                        $PadLength = $ColumnHeaderSize 
                    }
                    else {
                        $PadLength = $ColumnLength[$ColumnNumber] + 1 
                    }

                    $CurrentColumnLength += $PadLength
                    if ($CurrentColumnLength -ge $ScreenWidth) {
                        break
                    }

                    if ($ColumnHeaderSize) {

                        $ColumnValue = ("$ColumnValue".ToCharArray() | Select-Object -First ($PadLength - 1)) -join ''
                    }
                    else {
                        $ColumnValue = ("$ColumnValue".ToCharArray() | Select-Object -First ($PadLength)) -join ''
                    }
                    if ($Output -eq '') {
                        if ($AlignRight) {
                            $Output = "$ColumnValue".PadLeft($PadLength)
                        }
                        else {
                            $Output = "$ColumnValue".PadRight($PadLength)
                        }
                    }
                    else {
                        if ($AlignRight) {
                            $Output = $Output + "$ColumnValue".PadLeft($PadLength)
                        }
                        else {
                            $Output = $Output + "$ColumnValue".PadRight($PadLength)
                        }
                    }
                    $ColumnNumber++
                }
                if ($Stream -eq 'Output') {
                    Write-Output -InputObject $Output
                }
                elseif ($Stream -eq 'Host') {
                    if ($ForegroundColorRow -contains $RowCountColors) {
                        [int] $Index = $ForegroundColorRow.IndexOf($RowCountColors)
                        Write-Host -Object $Output -ForegroundColor $ForegroundColor[$Index]
                    }
                    else {
                        Write-Host -Object $Output
                    }
                }
                elseif ($Stream -eq 'Warning') {
                    Write-Warning -Message $Output
                }
                elseif ($Stream -eq 'Verbose') {
                    Write-Verbose -Message $Output
                }
                elseif ($Stream -eq 'Debug') {
                    Write-Debug -Message $Output
                }
                elseif ($Stream -eq 'Information') {
                    Write-Information -MessageData $Output
                }

                if (-not $HideTableHeaders) {

                    if ($FirstLoop) {
                        $HeaderUnderline = $Output -Replace '\w', '-'

                        if ($Stream -eq 'Output') {
                            Write-Output -InputObject $HeaderUnderline
                        }
                        elseif ($Stream -eq 'Host') {
                            if ($ForegroundColorRow -contains $RowCountColors) {
                                [int] $Index = $ForegroundColorRow.IndexOf($RowCountColors)
                                Write-Host -Object $HeaderUnderline -ForegroundColor $ForegroundColor[$Index]
                            }
                            else {
                                Write-Host -Object $HeaderUnderline
                            }
                        }
                        elseif ($Stream -eq 'Warning') {
                            Write-Warning -Message $HeaderUnderline
                        }
                        elseif ($Stream -eq 'Verbose') {
                            Write-Verbose -Message $HeaderUnderline
                        }
                        elseif ($Stream -eq 'Debug') {
                            Write-Debug -Message $HeaderUnderline
                        }
                        elseif ($Stream -eq 'Information') {
                            Write-Information -MessageData $HeaderUnderline
                        }
                    }
                }

                $FirstLoop = $false
                $RowCountColors++
            }
        }

        if ($Stream -eq 'Output') {
            Write-Output -InputObject ''
        }
        elseif ($Stream -eq 'Host') {
            Write-Host -Object ''
        }
        elseif ($Stream -eq 'Warning') {
            Write-Warning -Message ''
        }
        elseif ($Stream -eq 'Verbose') {
            Write-Verbose -Message ''
        }
        elseif ($Stream -eq 'Debug') {
            Write-Debug -Message ''
        }
        elseif ($Stream -eq 'Information') {
            Write-Information -MessageData ''
        }

        if ($Stream -eq 'Output') {
        }
        elseif ($Stream -eq 'Host') {
        }
        elseif ($Stream -eq 'Warning') {
            $WarningPreference = $WarningCurrent
        }
        elseif ($Stream -eq 'Verbose') {
            $VerbosePreference = $VerboseCurrent
        }
        elseif ($Stream -eq 'Debug') {
            $DebugPreference = $DebugCurrent
        }
        elseif ($Stream -eq 'Information') {
            $InformationPreference = $InformationCurrent
        }
    }
}
function Format-StringToSentence {
    <#
    .SYNOPSIS
    Formats a given string by adding spaces before uppercase letters, digits, and non-word characters.
 
    .DESCRIPTION
    The Format-AddSpaceToSentence function takes a string or an array of strings
    and adds a space before each uppercase letter, digit, and non-word character (excluding dots, spaces, and underscores).
    It also provides options to convert the string to lowercase, remove certain characters before or after the formatting, and remove double spaces.
 
    .PARAMETER Text
    The string or array of strings to be formatted.
 
    .PARAMETER RemoveCharsBefore
    An array of characters to be removed from the string before the formatting is applied.
 
    .PARAMETER RemoveCharsAfter
    An array of characters to be removed from the string after the formatting is applied.
 
    .PARAMETER ToLowerCase
    If this switch is present, the function will convert the string to lowercase.
 
    .PARAMETER RemoveDoubleSpaces
    If this switch is present, the function will remove any double spaces from the string.
 
    .PARAMETER MakeWordsUpperCase
    An array of words that should be converted to uppercase after the formatting is applied.
 
    .PARAMETER DisableAddingSpace
    If this switch is present, the function will not add spaces before uppercase letters, digits, and non-word characters.
 
    .EXAMPLE
    $test = @(
        'OnceUponATime',
        'OnceUponATime1',
        'Money@Risk',
        'OnceUponATime123',
        'AHappyMan2014'
        'OnceUponATime_123'
        'Domain test.com'
    )
 
    Format-StringToSentence -Text $Test -RemoveCharsAfter '_' -RemoveDoubleSpaces
 
    This example formats each string in the $test array, removes any underscores after the formatting, and removes any double spaces.
 
    .EXAMPLE
    $test = @(
        'OnceUponATime',
        'OnceUponATime1',
        'Money@Risk',
        'OnceUponATime123',
        'AHappyMan2014'
        'OnceUponATime_123'
        'Domain test.com'
    )
 
    $Test | Format-StringToSentence -ToLowerCase -RemoveCharsAfter '_' -RemoveDoubleSpaces
 
    This example does the same as the previous one, but also converts each string to lowercase.
 
    .EXAMPLE
    $test = @(
        'OnceUponATime',
        'OnceUponATime1',
        'Money@Risk',
        'OnceUponATime123',
        'AHappyMan2014'
        'OnceUponATime_123'
        'Domain test.com'
    )
 
    Format-StringToSentence -Text $Test -RemoveCharsAfter '_' -RemoveDoubleSpaces -MakeWordsUpperCase 'test.com', 'money'
 
    .NOTES
    The function uses the -creplace operator to add spaces, which is case-insensitive. Therefore, it will add spaces before both uppercase and lowercase letters if they are specified in the RemoveCharsBefore or RemoveCharsAfter parameters.
    #>

    [alias('Format-AddSpaceToSentence')]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)][string[]] $Text,
        [string[]] $RemoveCharsBefore,
        [string[]] $RemoveCharsAfter,
        [switch] $ToLowerCase,
        [switch] $RemoveDoubleSpaces,
        [string[]] $MakeWordsUpperCase,
        [switch] $DisableAddingSpace
    )
    Process {
        foreach ($T in $Text) {
            if ($RemoveCharsBefore) {
                foreach ($R in $RemoveCharsBefore) {
                    $T = $T -ireplace [regex]::Escape($R), ""
                }
            }
            if (-not $DisableAddingSpace) {
                $T = $T -creplace '([A-Z]|[^a-zA-Z0-9_.\s]|_|\d+)(?<![a-z])', ' $&'
            }
            if ($ToLowerCase) {
                $T = $T.ToLower()
            }
            if ($RemoveCharsAfter) {
                foreach ($R in $RemoveCharsAfter) {
                    $T = $T -ireplace [regex]::Escape($R), ""
                }
            }
            if ($RemoveDoubleSpaces) {
                $T = $T -creplace '\s+', ' '
            }
            if ($MakeWordsUpperCase) {
                foreach ($W in $MakeWordsUpperCase) {
                    $T = $T -ireplace [regex]::Escape($W), $W.ToUpper()
                }
            }
            $T.Trim()
        }
    }
}
function Format-ToTitleCase {
    <#
    .SYNOPSIS
    Formats string or number of strings to Title Case
 
    .DESCRIPTION
    Formats string or number of strings to Title Case allowing for prettty display
 
    .PARAMETER Text
    Sentence or multiple sentences to format
 
    .PARAMETER RemoveWhiteSpace
    Removes spaces after formatting string to Title Case.
 
    .PARAMETER RemoveChar
    Array of characters to remove
 
    .EXAMPLE
    Format-ToTitleCase 'me'
 
    Output:
    Me
 
    .EXAMPLE
    'me i feel good' | Format-ToTitleCase
 
    Output:
    Me I Feel Good
    Not Feel
 
    .EXAMPLE
    'me i feel', 'not feel' | Format-ToTitleCase
 
    Output:
    Me I Feel Good
    Not Feel
 
    .EXAMPLE
    Format-ToTitleCase -Text 'This is my thing' -RemoveWhiteSpace
 
    Output:
    ThisIsMyThing
 
    .EXAMPLE
    Format-ToTitleCase -Text "This is my thing: That - No I don't want all chars" -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':'
 
    .NOTES
    General notes
 
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text,
        [switch] $RemoveWhiteSpace,
        [string[]] $RemoveChar
    )
    Begin {
    }
    Process {
        $Conversion = foreach ($T in $Text) {
            $Output = (Get-Culture).TextInfo.ToTitleCase($T)
            foreach ($Char in $RemoveChar) {
                $Output = $Output -replace $Char
            }
            if ($RemoveWhiteSpace) {
                $Output = $Output -replace ' ', ''
            }
            $Output
        }
        $Conversion
    }
    End {
    }
}
function Format-TransposeTable {
    <#
    .SYNOPSIS
    Transposes (pivot) a table of objects
 
    .DESCRIPTION
    Transposes (pivot) a table of objects
 
    .PARAMETER AllObjects
    List of objects to transpose
 
    .PARAMETER Sort
    Legacy parameter to sort the output
 
    .PARAMETER Legacy
    Allows to transpose the table in a legacy way
 
    .PARAMETER Property
    Provides a property to name the column based on the property value
 
    .EXAMPLE
    $T = [PSCustomObject] @{
        Name = "Server 1"
        Test = 1
        Test2 = 7
        Ole = 'bole'
        Trolle = 'A'
        Alle = 'sd'
    }
    $T1 = [PSCustomObject] @{
        Name = "Server 2"
        Test = 2
        Test2 = 3
        Ole = '1bole'
        Trolle = 'A'
        Alle = 'sd'
    }
 
    Format-TransposeTable -Object @($T, $T1) -Property "Name" | Format-Table
 
    .EXAMPLE
    $T2 = [ordered] @{
        Name = "Server 1"
        Test = 1
        Test2 = 7
        Ole = 'bole'
        Trolle = 'A'
        Alle = 'sd'
    }
    $T3 = [ordered] @{
        Name = "Server 2"
        Test = 2
        Test2 = 3
        Ole = '1bole'
        Trolle = 'A'
        Alle = 'sd'
    }
 
    $Test = Format-TransposeTable -Object @($T2, $T3)
    $Test | Format-Table
 
    .NOTES
    General notes
    #>

    [CmdletBinding(DefaultParameterSetName = 'Pivot')]
    param (
        [Parameter(ParameterSetName = 'Legacy')]
        [Parameter(ParameterSetName = 'Pivot')]
        [Alias("Object")]
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)][System.Collections.ICollection] $AllObjects,

        [Parameter(ParameterSetName = 'Legacy')]
        [ValidateSet("ASC", "DESC", "NONE")][String] $Sort = 'NONE',

        [Parameter(ParameterSetName = 'Legacy')]
        [switch] $Legacy,

        [Parameter(ParameterSetName = 'Pivot')]
        [string] $Property,

        [Parameter(ParameterSetName = 'Pivot')]
        [string] $Name = "Object "
    )
    begin {
        $Object = [System.Collections.Generic.List[object]]::new()
    }
    process {
        foreach ($O in $AllObjects) {
            $Object.Add($O)
        }
    }
    end {
        if (-not $Legacy) {
            if ($Object[0] -is [System.Collections.IDictionary]) {

                $ListHeader = [System.Collections.Generic.List[string]]::new()
                $ListHeader.Add('Name')
                if ($Property) {
                    foreach ($myObject in $Object) {
                        $ListHeader.Add($myObject.$Property)
                    }
                }
                else {
                    for ($i = 0; $i -lt $Object.Count; $i++) {
                        $ListHeader.Add("$($Name)$i")
                    }
                }
                $CountOfProperties = $Object[0].GetEnumerator().Name.Count
                [Array] $ObjectsList = for ($i = 0; $i -lt $CountOfProperties; $i++) {
                    $TranslatedObject = [ordered] @{
                        'Name' = $Object[0].GetEnumerator().Name[$i]
                    }
                    foreach ($Header in $ListHeader) {
                        if ($Header -ne 'Name') {
                            $TranslatedObject[$Header] = ''
                        }
                    }
                    $TranslatedObject
                }
                for ($i = 0; $i -lt $ObjectsList.Count; $i++) {
                    for ($j = 0; $j -lt $Object.Count; $j++) {
                        $NameOfProperty = $ObjectsList[$i].Name
                        $ObjectsList[$i][$j + 1] = $Object[$j].$NameOfProperty
                    }
                    [PSCustomObject] $ObjectsList[$i]
                }
            }
            else {

                $ListHeader = [System.Collections.Generic.List[string]]::new()
                $ListHeader.Add('Name')
                if ($Property) {
                    foreach ($myObject in $Object) {
                        $ListHeader.Add($myObject.$Property)
                    }
                }
                else {
                    for ($i = 0; $i -lt $Object.Count; $i++) {
                        $ListHeader.Add("$($Name)$i")
                    }
                }
                $CountOfProperties = $Object[0].PSObject.Properties.Name.Count
                [Array] $ObjectsList = for ($i = 0; $i -lt $CountOfProperties; $i++) {
                    $TranslatedObject = [ordered] @{
                        'Name' = $Object[0].PSObject.Properties.Name[$i]
                    }
                    foreach ($Header in $ListHeader) {
                        if ($Header -ne 'Name') {
                            $TranslatedObject[$Header] = ''
                        }
                    }
                    $TranslatedObject
                }
                for ($i = 0; $i -lt $ObjectsList.Count; $i++) {
                    for ($j = 0; $j -lt $Object.Count; $j++) {
                        $NameOfProperty = $ObjectsList[$i].Name
                        $ObjectsList[$i][$j + 1] = $Object[$j].$NameOfProperty
                    }
                    [PSCustomObject] $ObjectsList[$i]
                }
            }
        }
        else {
            foreach ($myObject in $Object) {
                if ($myObject -is [System.Collections.IDictionary]) {
                    if ($Sort -eq 'ASC') {
                        [PSCustomObject] $myObject.GetEnumerator() | Sort-Object -Property Name -Descending:$false
                    }
                    elseif ($Sort -eq 'DESC') {
                        [PSCustomObject] $myObject.GetEnumerator() | Sort-Object -Property Name -Descending:$true
                    }
                    else {
                        [PSCustomObject] $myObject
                    }
                }
                else {
                    $Output = [ordered] @{ }
                    if ($Sort -eq 'ASC') {
                        $myObject.PSObject.Properties | Sort-Object -Property Name -Descending:$false | ForEach-Object {
                            $Output["$($_.Name)"] = $_.Value
                        }
                    }
                    elseif ($Sort -eq 'DESC') {
                        $myObject.PSObject.Properties | Sort-Object -Property Name -Descending:$true | ForEach-Object {
                            $Output["$($_.Name)"] = $_.Value
                        }
                    }
                    else {
                        $myObject.PSObject.Properties | ForEach-Object {
                            $Output["$($_.Name)"] = $_.Value
                        }
                    }
                    $Output
                }
            }
        }
    }
}
function Format-View {
    <#
    .SYNOPSIS
    Formats and displays objects in a customizable view.
 
    .DESCRIPTION
    The Format-View function formats and displays objects in a customizable view. It allows you to specify properties to include or exclude, control output streams, and customize the display format.
 
    .PARAMETER InputObject
    Specifies the object to format.
 
    .PARAMETER Property
    Specifies the properties of the object to include in the output.
 
    .PARAMETER ExcludeProperty
    Specifies the properties of the object to exclude from the output.
 
    .PARAMETER HideTableHeaders
    Indicates whether to hide table headers in the output.
 
    .PARAMETER Stream
    Specifies the output stream for the formatted object. Valid values are 'Output', 'Host', 'Warning', 'Verbose', 'Debug', and 'Information'.
 
    .PARAMETER List
    Indicates whether to display the object as a list.
 
    .PARAMETER Autosize
    Indicates whether to automatically adjust the column width based on the content.
 
    .EXAMPLE
    Format-View -InputObject $object -Property Name, Age -Stream Verbose
    Formats the object with only the 'Name' and 'Age' properties and outputs to the verbose stream.
 
    .EXAMPLE
    Get-Process | Format-View -Property Name, CPU -Stream Host
    Formats the process objects with only the 'Name' and 'CPU' properties and outputs to the host.
 
    #>

    [alias('FV', 'Format-Verbose', 'Format-Debug', 'Format-Warning')]
    [CmdletBinding(DefaultParameterSetName = 'All')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
        [object] $InputObject,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1, ParameterSetName = 'Property')]
        [Object[]] $Property,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 2, ParameterSetName = 'ExcludeProperty')]
        [Object[]] $ExcludeProperty,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)]
        [switch] $HideTableHeaders,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6)]
        [validateset('Output', 'Host', 'Warning', 'Verbose', 'Debug', 'Information')]
        [string] $Stream = 'Verbose',
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 7)]
        [alias('AsList')][switch] $List,
        [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 8)]
        [switch] $Autosize
    )
    Begin {
        $IsVerbosePresent = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent

        if ($Stream -eq 'Output') {
        }
        elseif ($Stream -eq 'Host') {
        }
        elseif ($Stream -eq 'Warning') {
            [System.Management.Automation.ActionPreference] $WarningCurrent = $WarningPreference
            $WarningPreference = 'continue'
        }
        elseif ($Stream -eq 'Verbose') {
            [System.Management.Automation.ActionPreference] $VerboseCurrent = $VerbosePreference
            $VerbosePreference = 'continue'
        }
        elseif ($Stream -eq 'Debug') {
            [System.Management.Automation.ActionPreference] $DebugCurrent = $DebugPreference
            $DebugPreference = 'continue'
        }
        elseif ($Stream -eq 'Information') {
            [System.Management.Automation.ActionPreference] $InformationCurrent = $InformationPreference
            $InformationPreference = 'continue'
        }

        [bool] $FirstRun = $True 
        [bool] $FirstLoop = $True 
        [bool] $FirstList = $True 
        [int] $ScreenWidth = $Host.UI.RawUI.WindowSize.Width - 12 
        $MyList = [System.Collections.Generic.List[Object]]::new()
    }
    Process {
        $MyList.Add($InputObject)
    }
    End {
        $Parameters = @{}
        if ($Property) {
            $Parameters.Property = $Property
        }
        if ($ExcludeProperty) {
            $Parameters.ExcludeProperty = $ExcludeProperty
        }
        if ($HideTableHeaders) {
            $Parameters.HideTableHeaders = $HideTableHeaders
        }

        if ($List) {

            if ($Stream -eq 'Output') {
                $MyList | Format-List @Parameters | Out-String | Write-Output
            }
            elseif ($Stream -eq 'Host') {
                $MyList | Format-List @Parameters | Out-String | Write-Host
            }
            elseif ($Stream -eq 'Warning') {
                $MyList | Format-List @Parameters | Out-String | Write-Warning
                $WarningPreference = $WarningCurrent
            }
            elseif ($Stream -eq 'Verbose') {
                $MyList | Format-List @Parameters | Out-String | Write-Verbose
                $VerbosePreference = $VerboseCurrent
            }
            elseif ($Stream -eq 'Debug') {
                $MyList | Format-List @Parameters | Out-String | Write-Debug
                $DebugPreference = $DebugCurrent
            }
            elseif ($Stream -eq 'Information') {
                $MyList | Format-List @Parameters | Out-String | Write-Information
                $InformationPreference = $InformationCurrent
            }
        }
        else {

            if ($Stream -eq 'Output') {
                $MyList | Format-Table @Parameters | Out-String | Write-Output
            }
            elseif ($Stream -eq 'Host') {
                $MyList | Format-Table @Parameters | Out-String | Write-Host
            }
            elseif ($Stream -eq 'Warning') {
                $MyList | Format-Table @Parameters | Out-String | Write-Warning
                $WarningPreference = $WarningCurrent
            }
            elseif ($Stream -eq 'Verbose') {
                $MyList | Format-Table @Parameters | Out-String | Write-Verbose
                $VerbosePreference = $VerboseCurrent
            }
            elseif ($Stream -eq 'Debug') {
                $MyList | Format-Table @Parameters | Out-String | Write-Debug
                $DebugPreference = $DebugCurrent
            }
            elseif ($Stream -eq 'Information') {
                $MyList | Format-Table @Parameters | Out-String | Write-Information
                $InformationPreference = $InformationCurrent
            }
        }
    }
}
function Get-Colors {
    <#
    .SYNOPSIS
    Retrieves RGB color values based on the provided color names.
 
    .DESCRIPTION
    The Get-Colors function retrieves RGB color values from a predefined list based on the color names provided as input. If no color names are specified, it returns all available RGB color values.
 
    .PARAMETER Color
    Specifies an array of color names for which RGB values are to be retrieved.
 
    .EXAMPLE
    Get-Colors -Color "Red", "Green"
    Retrieves the RGB values for the colors Red and Green.
 
    .EXAMPLE
    Get-Colors
    Retrieves all available RGB color values.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $Color
    )
    if ($Color) {
        foreach ($_ in $Color) {
            $Script:RGBColors.$_
        }
    }
    else {
        return $Script:RGBColors
    }
}
$ScriptBlockColors = {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    $Script:RGBColors.Keys | Where-Object { $_ -like "*$wordToComplete*" }
}

Register-ArgumentCompleter -CommandName Get-Colors -ParameterName Color -ScriptBlock $ScriptBlockColors
function Get-DataInformation {
    <#
    .SYNOPSIS
    Retrieves data information based on specified criteria.
 
    .DESCRIPTION
    This function retrieves data information based on the specified criteria. It checks for required types, availability of commands, and executes content if provided.
 
    .PARAMETER Content
    The script block to execute for gathering data.
 
    .PARAMETER Text
    The text message to display when gathering data.
 
    .PARAMETER TypesRequired
    An array of types required for data gathering.
 
    .PARAMETER TypesNeeded
    An array of types needed for data gathering.
 
    .PARAMETER Commands
    An array of commands to check for availability.
 
    .PARAMETER SkipAvailability
    Switch to skip availability check for commands.
 
    .EXAMPLE
    Get-DataInformation -Content { Get-Process } -Text "Gathering process information" -TypesRequired @("System.Diagnostics.Process") -TypesNeeded @("System.Diagnostics.Process") -Commands @("Get-Process")
 
    Description:
    Retrieves process information using the Get-Process command.
 
    .EXAMPLE
    Get-DataInformation -Content { Get-Service } -Text "Gathering service information" -TypesRequired @("System.ServiceProcess.ServiceController") -TypesNeeded @("System.ServiceProcess.ServiceController") -Commands @("Get-Service")
 
    Description:
    Retrieves service information using the Get-Service command.
    #>

    [CmdletBinding()]
    param(
        [ScriptBlock] $Content,
        [string] $Text,
        [Array] $TypesRequired,
        [Array] $TypesNeeded,
        [Array] $Commands,
        [switch] $SkipAvailability
    )
    if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded $TypesNeeded) {
        Write-Verbose -Message $Text
        $Time = Start-TimeLog

        if ($Commands.Count -gt 0 -and -not $SkipAvailability) {
            $Available = Test-AvailabilityCommands -Commands $Commands
            if ($Available -contains $false) {
                $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
                Write-Warning "Get-DataInformation - Commands $($Commands -join ", ") is/are not available. Data gathering skipped."
                Write-Verbose "$Text - Time: $EndTime"
                return
            }
        }
        if ($null -ne $Content) {
            & $Content
        }
        $EndTime = Stop-TimeLog -Time $Time -Option OneLiner
        Write-Verbose "$Text - Time: $EndTime"
    }
}
function Get-HashMaxValue {
    <#
    .SYNOPSIS
    Gets the maximum value from a hashtable.
 
    .DESCRIPTION
    This function retrieves the maximum value from a given hashtable. It can also return the minimum value if the -Lowest switch is used.
 
    .PARAMETER hashTable
    The hashtable from which to find the maximum value.
 
    .PARAMETER Lowest
    If specified, the function will return the minimum value instead of the maximum.
 
    .EXAMPLE
    $myHashTable = @{ 'A' = 10; 'B' = 20; 'C' = 5 }
    Get-HashMaxValue -hashTable $myHashTable
    # Output: 20
 
    .EXAMPLE
    $myHashTable = @{ 'A' = 10; 'B' = 20; 'C' = 5 }
    Get-HashMaxValue -hashTable $myHashTable -Lowest
    # Output: 5
    #>

    [CmdletBinding()]
    param (
        [Object] $hashTable,
        [switch] $Lowest
    )
    if ($Lowest) {
        return ($hashTable.GetEnumerator() | Sort-Object value -Descending | Select-Object -Last 1).Value
    }
    else {
        return ($hashTable.GetEnumerator() | Sort-Object value -Descending | Select-Object -First 1).Value
    }
}
function Get-MimeType {
    <#
    .SYNOPSIS
    Get-MimeType function returns the MIME type of a file based on its extension.
 
    .DESCRIPTION
    This function takes a file name as input and returns the corresponding MIME type based on the file extension.
 
    .PARAMETER FileName
    Specifies the name of the file for which the MIME type needs to be determined.
 
    .EXAMPLE
    Get-MimeType -FileName "example.jpg"
    Returns "image/jpeg" as the MIME type for the file "example.jpg".
 
    .EXAMPLE
    Get-MimeType -FileName "example.png"
    Returns "image/png" as the MIME type for the file "example.png".
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $FileName
    )

    $MimeMappings = @{
        '.jpeg' = 'image/jpeg'
        '.jpg'  = 'image/jpeg'
        '.png'  = 'image/png'
    }

    $Extension = [System.IO.Path]::GetExtension( $FileName )
    $ContentType = $MimeMappings[ $Extension ]

    if ([string]::IsNullOrEmpty($ContentType)) {
        return New-Object System.Net.Mime.ContentType
    }
    else {
        return New-Object System.Net.Mime.ContentType($ContentType)
    }
}
function Get-ObjectCount {
    <#
    .SYNOPSIS
    Counts the number of objects passed as input.
 
    .DESCRIPTION
    This function calculates and returns the total count of objects passed as input. It is designed to be used in scenarios where counting the number of objects is required.
 
    .PARAMETER Object
    Specifies the object or objects for which the count needs to be calculated.
 
    .EXAMPLE
    Get-Process | Get-ObjectCount
    Returns the total count of processes currently running.
 
    .EXAMPLE
    $Files = Get-ChildItem -Path "C:\Files"
    $FileCount = $Files | Get-ObjectCount
    Returns the total count of files in the specified directory.
 
    #>

    [CmdletBinding()]
    param(
        [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][Object]$Object
    )
    return $($Object | Measure-Object).Count
}
function Get-ObjectData {
    <#
    .SYNOPSIS
    Retrieves data from an object based on the specified title.
 
    .DESCRIPTION
    This function retrieves data from the specified object based on the provided title. It returns an array of values associated with the title.
 
    .PARAMETER Object
    The object from which data will be retrieved.
 
    .PARAMETER Title
    The title of the data to retrieve from the object.
 
    .PARAMETER DoNotAddTitles
    Switch parameter to indicate whether titles should be included in the output.
 
    .EXAMPLE
    Get-ObjectData -Object $myObject -Title "Name"
    Retrieves the names associated with the object $myObject.
 
    .EXAMPLE
    Get-ObjectData -Object $myObject -Title "Age" -DoNotAddTitles
    Retrieves the ages associated with the object $myObject without including the title.
 
    #>

    [CmdletBinding()]
    param(
        $Object,
        $Title,
        [switch] $DoNotAddTitles
    )
    [Array] $Values = $Object.$Title
    [Array] $ArrayList = @(
        if ($Values.Count -eq 1 -and $DoNotAddTitles -eq $false) {
            "$Title - $($Values[0])"
        }
        else {
            if ($DoNotAddTitles -eq $false) {
                $Title
            }
            foreach ($Value in $Values) {
                "$Value"
            }
        }
    )
    return $ArrayList
}
Function Get-ObjectEnumValues {
    <#
    .SYNOPSIS
    Retrieves the values of an enumeration type and returns them as a hashtable.
 
    .DESCRIPTION
    This function takes an enumeration type as input and retrieves all its values, storing them in a hashtable where the key is the name of the enum value and the value is the corresponding numeric value.
 
    .PARAMETER enum
    Specifies the enumeration type for which values need to be retrieved.
 
    .EXAMPLE
    Get-ObjectEnumValues -enum [System.DayOfWeek]
    Retrieves all values of the System.DayOfWeek enumeration and returns them as a hashtable.
 
    .EXAMPLE
    Get-ObjectEnumValues -enum [System.ConsoleColor]
    Retrieves all values of the System.ConsoleColor enumeration and returns them as a hashtable.
 
    #>

    param(
        [string]$enum
    )
    $enumValues = @{}
    [enum]::getvalues([type]$enum) |
        ForEach-Object {
            $enumValues.add($_, $_.value__)
        }
    $enumValues
}
function Get-ObjectKeys {
    <#
    .SYNOPSIS
    Retrieves the keys of an object excluding a specified key.
 
    .DESCRIPTION
    This function retrieves the keys of an object while excluding a specified key. It returns an array of keys from the object.
 
    .PARAMETER Object
    The object from which keys need to be retrieved.
 
    .PARAMETER Ignore
    The key to be excluded from the result.
 
    .EXAMPLE
    $object = @{ 'key1' = 'value1'; 'key2' = 'value2'; 'key3' = 'value3' }
    Get-ObjectKeys -Object $object -Ignore 'key2'
    # Returns 'key1', 'key3'
 
    #>

    param(
        [object] $Object,
        [string] $Ignore
    )
    $Data = $Object.Keys | Where-Object { $_ -notcontains $Ignore }
    return $Data
}

function Get-ObjectProperties {
    <#
    .SYNOPSIS
    Retrieves all properties of an object and allows for adding custom properties.
 
    .DESCRIPTION
    This function retrieves all properties of an object provided as input. It also allows for adding custom properties to the list. The function can be useful for ensuring that all properties are known at runtime when exporting to SQL, Excel, or Word.
 
    .PARAMETER Object
    Specifies the object for which properties need to be retrieved.
 
    .PARAMETER AddProperties
    Specifies an array of custom properties to be added to the list.
 
    .PARAMETER Sort
    Indicates whether the properties should be sorted.
 
    .PARAMETER RequireUnique
    Specifies whether the list of properties should be unique.
 
    .EXAMPLE
    $Test = Get-Process
    Get-ObjectProperties -Object $Test
 
    Description
    -----------
    Retrieves all properties of the Get-Process object.
 
    .EXAMPLE
    $Test = Get-Process
    Get-ObjectProperties -Object $Test -AddProperties 'CustomProperty1', 'CustomProperty2' -Sort -RequireUnique $false
 
    Description
    -----------
    Retrieves all properties of the Get-Process object and adds custom properties 'CustomProperty1' and 'CustomProperty2' to the list. The properties are sorted and duplicates are allowed.
 
    #>

    [CmdletBinding()]
    param (
        [System.Collections.ICollection] $Object,
        [string[]] $AddProperties, # provides ability to add some custom properties
        [switch] $Sort,
        [bool] $RequireUnique = $true
    )
    $Properties = @(
        foreach ($O in $Object) {
            $ObjectProperties = $O.PSObject.Properties.Name
            $ObjectProperties
        }
        foreach ($Property in $AddProperties) {

            $Property
        }
    )
    if ($Sort) {
        return $Properties | Sort-Object -Unique:$RequireUnique
    }
    else {
        return $Properties | Select-Object -Unique:$RequireUnique
    }
}
function Get-ObjectPropertiesAdvanced {
    <#
    .SYNOPSIS
    Retrieves properties of objects and provides the ability to add custom properties.
 
    .DESCRIPTION
    This function retrieves properties of objects and allows users to add custom properties to the output. It calculates the highest count of properties among the objects and returns the properties in an array.
 
    .PARAMETER Object
    Specifies the object or objects whose properties need to be retrieved.
 
    .PARAMETER AddProperties
    Specifies an array of custom properties to be added to the output.
 
    .PARAMETER Sort
    Indicates whether the properties should be sorted alphabetically.
 
    .EXAMPLE
    $objects = Get-ObjectPropertiesAdvanced -Object $myObject -AddProperties @("CustomProperty1", "CustomProperty2") -Sort
    This example retrieves properties of $myObject and adds custom properties "CustomProperty1" and "CustomProperty2" to the output, sorted alphabetically.
 
    #>

    [CmdletBinding()]
    param (
        [object] $Object,
        [string[]] $AddProperties, # provides ability to add some custom properties
        [switch] $Sort
    )
    $Data = @{ }
    $Properties = New-ArrayList
    $HighestCount = 0

    foreach ($O in $Object) {
        $ObjectProperties = $O.PSObject.Properties.Name

        $Count = $ObjectProperties.Count
        if ($Count -gt $HighestCount) {
            $Data.HighestCount = $Count
            $Data.HighestObject = $O
            $HighestCount = $Count
        }
        foreach ($Property in $ObjectProperties) {
            Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique
        }
    }
    foreach ($Property in $AddProperties) {
        Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique
    }
    $Data.Properties = if ($Sort) {
        $Properties | Sort-Object 
    }
    else {
        $Properties 
    }

    return $Data
}
function Get-ObjectTitles {
    <#
    .SYNOPSIS
    Retrieves the titles of properties from an object.
 
    .DESCRIPTION
    This function retrieves the titles of properties from an object and returns them in an ArrayList.
 
    .PARAMETER Object
    Specifies the object from which to retrieve property titles.
 
    .EXAMPLE
    $object = [PSCustomObject]@{
        Name = "John Doe"
        Age = 30
        City = "New York"
    }
    Get-ObjectTitles -Object $object
 
    Description
    -----------
    Retrieves the property titles from the $object and returns them in an ArrayList.
 
    #>

    [CmdletBinding()]
    param(
        $Object
    )
    $ArrayList = New-Object System.Collections.ArrayList
    Write-Verbose "Get-ObjectTitles - ObjectType $($Object.GetType())"
    foreach ($Title in $Object.PSObject.Properties) {
        Write-Verbose "Get-ObjectTitles - Value added to array: $($Title.Name)"
        $ArrayList.Add($Title.Name) | Out-Null
    }
    Write-Verbose "Get-ObjectTitles - Array size: $($ArrayList.Count)"
    return $ArrayList
}
function Get-ObjectType {
    <#
    .SYNOPSIS
    Retrieves information about the type of the given object.
 
    .DESCRIPTION
    This function retrieves information about the type of the specified object, including its name, base type, and system type.
 
    .PARAMETER Object
    The object for which type information is to be retrieved.
 
    .PARAMETER ObjectName
    The name of the object. Default is 'Random Object Name'.
 
    .PARAMETER VerboseOnly
    Indicates whether to output verbose information only.
 
    .EXAMPLE
    Get-ObjectType -Object $myObject
    Retrieves type information for the object stored in $myObject.
 
    .EXAMPLE
    Get-ObjectType -Object $myObject -ObjectName "My Custom Object"
    Retrieves type information for the object stored in $myObject with a custom name.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $Object,
        [string] $ObjectName = 'Random Object Name',
        [switch] $VerboseOnly
    )
    $ReturnData = [ordered] @{}
    $ReturnData.ObjectName = $ObjectName

    if ($null -ne $Object) {
        try {
            $TypeInformation = $Object.GetType()
            $ReturnData.ObjectTypeName = $TypeInformation.Name
            $ReturnData.ObjectTypeBaseName = $TypeInformation.BaseType
            $ReturnData.SystemType = $TypeInformation.UnderlyingSystemType
        }
        catch {
            $ReturnData.ObjectTypeName = ''
            $ReturnData.ObjectTypeBaseName = ''
            $ReturnData.SystemType = ''
        }
        try {
            $TypeInformationInsider = $Object[0].GetType()
            $ReturnData.ObjectTypeInsiderName = $TypeInformationInsider.Name
            $ReturnData.ObjectTypeInsiderBaseName = $TypeInformationInsider.BaseType
            $ReturnData.SystemTypeInsider = $TypeInformationInsider.UnderlyingSystemType
        }
        catch {

            $ReturnData.ObjectTypeInsiderName = ''
            $ReturnData.ObjectTypeInsiderBaseName = ''
            $ReturnData.SystemTypeInsider = ''
        }
    }
    else {
        $ReturnData.ObjectTypeName = ''
        $ReturnData.ObjectTypeBaseName = ''
        $ReturnData.SystemType = ''
        $ReturnData.ObjectTypeInsiderName = ''
        $ReturnData.ObjectTypeInsiderBaseName = ''
        $ReturnData.SystemTypeInsider = ''
    }
    Write-Verbose "Get-ObjectType - ObjectTypeName: $($ReturnData.ObjectTypeName)"
    Write-Verbose "Get-ObjectType - ObjectTypeBaseName: $($ReturnData.ObjectTypeBaseName)"
    Write-Verbose "Get-ObjectType - SystemType: $($ReturnData.SystemType)"
    Write-Verbose "Get-ObjectType - ObjectTypeInsiderName: $($ReturnData.ObjectTypeInsiderName)"
    Write-Verbose "Get-ObjectType - ObjectTypeInsiderBaseName: $($ReturnData.ObjectTypeInsiderBaseName)"
    Write-Verbose "Get-ObjectType - SystemTypeInsider: $($ReturnData.SystemTypeInsider)"
    if ($VerboseOnly) {
        return 
    }
    else {
        return Format-TransposeTable -Object $ReturnData 
    }
}
Function Get-Types {
    <#
    .SYNOPSIS
    Retrieves the enum values of the specified types.
 
    .DESCRIPTION
    This function takes an array of types and returns the enum values of each type.
 
    .PARAMETER Types
    Specifies the types for which enum values need to be retrieved.
 
    .EXAMPLE
    Get-Types -Types [System.DayOfWeek]
    Retrieves the enum values of the System.DayOfWeek type.
 
    .EXAMPLE
    Get-Types -Types [System.ConsoleColor, System.EnvironmentVariableTarget]
    Retrieves the enum values of the System.ConsoleColor and System.EnvironmentVariableTarget types.
    #>

    [CmdletBinding()]
    param (
        [Object] $Types
    )
    $TypesRequired = foreach ($Type in $Types) {
        $Type.GetEnumValues()
    }
    return $TypesRequired
}
function Join-Uri {
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together
 
    .DESCRIPTION
    Provides ability to join two Url paths together
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .EXAMPLE
    Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-Url')]
    [cmdletBinding()]
    param(
        [parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri
    )

    return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/'))
}
function Join-UriQuery {
    <#
    .SYNOPSIS
    Provides ability to join two Url paths together including advanced querying
 
    .DESCRIPTION
    Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls
 
    .PARAMETER BaseUri
    Primary Url to merge
 
    .PARAMETER RelativeOrAbsoluteUri
    Additional path to merge with primary url (optional)
 
    .PARAMETER QueryParameter
    Parameters and their values in form of hashtable
 
    .PARAMETER EscapeUriString
    If set, will escape the url string
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{
        page = 1
        per_page = 20
        search = 'SearchString'
    }
 
    .EXAMPLE
    Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts'
 
    .NOTES
    General notes
    #>

    [alias('Join-UrlQuery')]
    [CmdletBinding()]
    param (
        [parameter(Mandatory)][uri] $BaseUri,
        [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri,
        [Parameter()][System.Collections.IDictionary] $QueryParameter,
        [alias('EscapeUrlString')][switch] $EscapeUriString
    )
    Begin {
        Add-Type -AssemblyName System.Web
    }
    Process {

        if ($BaseUri -and $RelativeOrAbsoluteUri) {
            $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri
        }
        else {
            $Url = $BaseUri
        }

        if ($QueryParameter) {
            $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
            foreach ($key in $QueryParameter.Keys) {
                $Collection.Add($key, $QueryParameter.$key)
            }
        }

        $uriRequest = [System.UriBuilder] $Url
        if ($Collection) {
            $uriRequest.Query = $Collection.ToString()
        }
        if (-not $EscapeUriString) {
            $uriRequest.Uri.AbsoluteUri
        }
        else {
            [System.Uri]::EscapeUriString($uriRequest.Uri.AbsoluteUri)
        }
    }
}
function Merge-Array {
    <#
    .SYNOPSIS
    Merge-Array allows to merge two or more arrays together.
 
    .DESCRIPTION
    Merge-Array allows to merge two or more arrays together. It copies headers from each Array and merges them together allowing for fulll output.
    When used with Prescan parameter it actually is able to show headers from all arrays
 
    .PARAMETER Array
    List of Arrays to process
 
    .PARAMETER Prescan
    Scans each element of each array for headers.
 
    .EXAMPLE
    $Array1 = @(
        [PSCustomObject] @{ Name = 'Company1'; Count = 259 }
        [PSCustomObject] @{ Name = 'Company2'; Count = 300 }
    )
    $Array2 = @(
        [PSCustomObject] @{ Name = 'Company1'; Count = 25 }
        [PSCustomObject] @{ Name = 'Company2'; Count = 100 }
    )
    $Array3 = @(
        [PSCustomObject] @{ Name1 = 'Company1'; Count3 = 25 }
        [PSCustomObject] @{ Name1 = 'Company2'; Count3 = 100 }
        [PSCustomObject] @{ Name2 = 'Company2'; Count3 = 100 }
    )
 
    $Array1 | Format-Table -AutoSize
    $Array2 | Format-Table -AutoSize
    $Array3 | Format-Table -AutoSize
 
    Merge-Array -Array $Array1, $Array2, $Array3 | Format-Table -AutoSize
    Merge-Array -Array $Array1, $Array2, $Array3 -Prescan | Format-Table -AutoSize
 
    .NOTES
    General notes
    #>


    param(
        [Array[]] $Array,
        [switch] $Prescan
    )
    $PropertyNames = foreach ($A in $Array) {
        if ($Prescan) {
            foreach ($O in $A) {
                $O.PSObject.Properties.Name
            }
        }
        else {
            $A[0].PSObject.Properties.Name
        }
    } 
    $PropertyNames = $PropertyNames | Sort-Object -Unique
    foreach ($A in $Array) {
        $A | Select-Object -Property $PropertyNames
    }
}

function Merge-Objects {
    <#
    .SYNOPSIS
    Merges two objects into a single object.
 
    .DESCRIPTION
    This function merges two objects into a single object by combining their properties. If there are duplicate properties, the values from Object2 will overwrite the values from Object1.
 
    .PARAMETER Object1
    The first object to be merged.
 
    .PARAMETER Object2
    The second object to be merged.
 
    .EXAMPLE
    $Object1 = [pscustomobject] @{
        'Name' = 'John Doe'
        'Age' = 30
    }
 
    $Object2 = [pscustomobject] @{
        'Age' = 31
        'City' = 'New York'
    }
 
    Merge-Objects -Object1 $Object1 -Object2 $Object2
 
    Description
    -----------
    Merges $Object1 and $Object2 into a single object. The resulting object will have properties 'Name', 'Age', and 'City' with values 'John Doe', 31, and 'New York' respectively.
 
    #>

    [CmdletBinding()]
    param (
        [Object] $Object1,
        [Object] $Object2
    )
    $Object = [ordered] @{}
    foreach ($Property in $Object1.PSObject.Properties) {
        $Object.($Property.Name) = $Property.Value
    }
    foreach ($Property in $Object2.PSObject.Properties) {
        $Object.($Property.Name) = $Property.Value
    }
    return [pscustomobject] $Object
}
function New-ArrayList {
    <#
    .SYNOPSIS
    Creates a new ArrayList object.
 
    .DESCRIPTION
    This function creates a new instance of the ArrayList class from the System.Collections namespace.
 
    .EXAMPLE
    $myList = New-ArrayList
    $myList.Add("Apple")
    $myList.Add("Banana")
    $myList.Add("Orange")
    $myList
    #>

    [CmdletBinding()]
    param()
    $List = [System.Collections.ArrayList]::new()

    return , $List
}
function New-GenericList {
    <#
    .SYNOPSIS
    Creates a new instance of a generic list.
 
    .DESCRIPTION
    This function creates a new instance of a generic list based on the specified type.
 
    .PARAMETER Type
    Specifies the type of objects that the generic list will hold. Defaults to [System.Object].
 
    .EXAMPLE
    PS C:\> $list = New-GenericList -Type [int]
    Creates a new generic list that holds integers.
 
    .EXAMPLE
    PS C:\> $list = New-GenericList
    Creates a new generic list that holds objects.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $Type = [System.Object]
    )
    return New-Object "System.Collections.Generic.List[$Type]"
}
function Remove-DuplicateObjects {
    <#
    .SYNOPSIS
    Removes duplicate objects from a list based on specified properties.
 
    .DESCRIPTION
    This function removes duplicate objects from the input list based on the specified properties. It retains only unique objects in the list.
 
    .PARAMETER Object
    The list of objects to remove duplicates from.
 
    .PARAMETER Property
    The properties to consider when identifying duplicates.
 
    .EXAMPLE
    $Array = @()
    $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
    $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'}
 
    Write-Color 'Before' -Color Red
    $Array | Format-Table -A
 
    Write-Color 'After' -Color Green
    $Array = $Array | Sort-Object -Unique -Property 'Name', 'Val1','Val2'
    $Array | Format-Table -AutoSize
 
    .NOTES
    This function removes duplicate objects from a list based on specified properties.
    #>

    [CmdletBinding()]
    param(
        [System.Collections.IList] $Object,
        [string[]] $Property
    )
    if ($Object.Count -eq 0) {
        return $Object
    }
    else {
        return $Object | Sort-Object -Property $Property -Unique
    }
}
function Remove-EmptyValue {
    <#
    .SYNOPSIS
    Removes empty values from a hashtable recursively.
 
    .DESCRIPTION
    This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values.
 
    .PARAMETER Hashtable
    The hashtable from which empty values will be removed.
 
    .PARAMETER ExcludeParameter
    An array of keys to exclude from the removal process.
 
    .PARAMETER Recursive
    Indicates whether to recursively remove empty values from nested hashtables.
 
    .PARAMETER Rerun
    Specifies the number of times to rerun the removal process recursively.
 
    .PARAMETER DoNotRemoveNull
    If specified, null values will not be removed.
 
    .PARAMETER DoNotRemoveEmpty
    If specified, empty string values will not be removed.
 
    .PARAMETER DoNotRemoveEmptyArray
    If specified, empty array values will not be removed.
 
    .PARAMETER DoNotRemoveEmptyDictionary
    If specified, empty dictionary values will not be removed.
 
    .EXAMPLE
    $hashtable = @{
        'Key1' = '';
        'Key2' = $null;
        'Key3' = @();
        'Key4' = @{}
    }
    Remove-EmptyValue -Hashtable $hashtable -Recursive
 
    Description
    -----------
    This example removes empty values from the $hashtable recursively.
 
    #>

    [alias('Remove-EmptyValues')]
    [CmdletBinding()]
    param(
        [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable,
        [string[]] $ExcludeParameter,
        [switch] $Recursive,
        [int] $Rerun,
        [switch] $DoNotRemoveNull,
        [switch] $DoNotRemoveEmpty,
        [switch] $DoNotRemoveEmptyArray,
        [switch] $DoNotRemoveEmptyDictionary
    )
    foreach ($Key in [string[]] $Hashtable.Keys) {
        if ($Key -notin $ExcludeParameter) {
            if ($Recursive) {
                if ($Hashtable[$Key] -is [System.Collections.IDictionary]) {
                    if ($Hashtable[$Key].Count -eq 0) {
                        if (-not $DoNotRemoveEmptyDictionary) {
                            $Hashtable.Remove($Key)
                        }
                    }
                    else {
                        Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive
                    }
                }
                else {
                    if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                        $Hashtable.Remove($Key)
                    }
                    elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                        $Hashtable.Remove($Key)
                    }
                }
            }
            else {
                if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') {
                    $Hashtable.Remove($Key)
                }
                elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) {
                    $Hashtable.Remove($Key)
                }
            }
        }
    }
    if ($Rerun) {
        for ($i = 0; $i -lt $Rerun; $i++) {
            Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive
        }
    }
}

function Remove-FromArray {
    <#
    .SYNOPSIS
    Removes an element from an ArrayList.
 
    .DESCRIPTION
    This function removes a specified element from an ArrayList. It can remove either a specific element or the last element in the list.
 
    .PARAMETER List
    The ArrayList from which the element will be removed.
 
    .PARAMETER Element
    The element to be removed from the ArrayList.
 
    .PARAMETER LastElement
    If this switch is used, the last element in the ArrayList will be removed.
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    $myList.Add("Apple")
    $myList.Add("Banana")
    Remove-FromArray -List $myList -Element "Banana"
    # This will remove the element "Banana" from the ArrayList.
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    $myList.Add("Apple")
    $myList.Add("Banana")
    Remove-FromArray -List $myList -LastElement
    # This will remove the last element in the ArrayList.
 
    #>

    [CmdletBinding()]
    param(
        [System.Collections.ArrayList] $List,
        [Object] $Element,
        [switch] $LastElement
    )
    if ($LastElement) {
        $LastID = $List.Count - 1
        $List.RemoveAt($LastID) > $null
    }
    else {
        $List.Remove($Element) > $null
    }
}
function Remove-ObjectsExistingInTarget {
    <#
    .SYNOPSIS
    Removes objects from the source list that exist in the target list based on specified properties.
 
    .DESCRIPTION
    This function compares two lists of objects and removes objects from the source list that have matching properties in the target list. It returns either the objects that do not exist in the target list or only the objects that exist in the target list based on the specified properties.
 
    .PARAMETER ObjectSource
    The list of objects to compare against the target list.
 
    .PARAMETER ObjectTarget
    The list of objects to compare with.
 
    .PARAMETER ComparePropertySource
    The property in the source objects to compare.
 
    .PARAMETER ComparePropertyTarget
    The property in the target objects to compare.
 
    .PARAMETER Reverse
    Switch to return only the objects that exist in the target list.
 
    .EXAMPLE
    $sourceList = @(
        [PSCustomObject]@{Id = 1; Name = "A"},
        [PSCustomObject]@{Id = 2; Name = "B"},
        [PSCustomObject]@{Id = 3; Name = "C"}
    )
 
    $targetList = @(
        [PSCustomObject]@{Id = 2; Name = "B"},
        [PSCustomObject]@{Id = 3; Name = "C"}
    )
 
    Remove-ObjectsExistingInTarget -ObjectSource $sourceList -ObjectTarget $targetList -ComparePropertySource "Id" -ComparePropertyTarget "Id"
    # Output: Id Name
    # -- ----
    # 1 A
 
    .EXAMPLE
    $sourceList = @(
        [PSCustomObject]@{Id = 1; Name = "A"},
        [PSCustomObject]@{Id = 2; Name = "B"},
        [PSCustomObject]@{Id = 3; Name = "C"}
    )
 
    $targetList = @(
        [PSCustomObject]@{Id = 2; Name = "B"},
        [PSCustomObject]@{Id = 3; Name = "C"}
    )
 
    Remove-ObjectsExistingInTarget -ObjectSource $sourceList -ObjectTarget $targetList -ComparePropertySource "Id" -ComparePropertyTarget "Id" -Reverse
    # Output: Id Name
    # -- ----
    # 2 B
    # 3 C
    #>

    param(
        $ObjectSource,
        $ObjectTarget,
        [string] $ComparePropertySource,
        [string] $ComparePropertyTarget,
        [switch] $Reverse # returns only existing objects
    )
    $ObjectsExistingInTarget = @()
    $ObjectsNotExistingInTarget = @()
    foreach ($Object in $ObjectSource) {
        if ($ObjectTarget.$ComparePropertySource -contains $Object.$ComparePropertyTarget) {
            $ObjectsExistingInTarget += $Object
        }
        else {
            $ObjectsNotExistingInTarget += $Object
        }
    }
    if ($Reverse) {
        return $ObjectsExistingInTarget
    }
    else {
        return $ObjectsNotExistingInTarget
    }
}
function Remove-WhiteSpace {
    <#
    .SYNOPSIS
    Removes leading, trailing, and extra white spaces from a given text string.
 
    .DESCRIPTION
    The Remove-WhiteSpace function removes any leading, trailing, and extra white spaces from the input text string. It ensures that only single spaces separate words within the text.
 
    .PARAMETER Text
    The input text string from which white spaces are to be removed.
 
    .EXAMPLE
    $MyValue = Remove-WhiteSpace -Text ' My Field '
    # $MyValue now contains 'My Field'
 
    #>

    param(
        [string] $Text
    )
    $Text = $Text -replace '(^\s+|\s+$)', '' -replace '\s+', ' '
    return $Text
}

Function Rename-LatinCharacters {
    <#
    .SYNOPSIS
    Renames a name to a name without special chars.
 
    .DESCRIPTION
    Renames a name to a name without special chars.
 
    .PARAMETER String
    Provide a string to rename
 
    .EXAMPLE
    Rename-LatinCharacters -String 'Przemysław Kłys'
 
    .EXAMPLE
    Rename-LatinCharacters -String 'Przemysław'
 
    .NOTES
    General notes
    #>

    [alias('Remove-StringLatinCharacters')]
    [cmdletBinding()]
    param(
        [string] $String
    )
    [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}
function Rename-UserValuesFromHash {
    <#
    .SYNOPSIS
    This function renames user values based on a hash table of match data.
 
    .DESCRIPTION
    The Rename-UserValuesFromHash function takes a list of users, a hash table of match data, and an array of field types. It then renames specific values in the user objects based on the match data provided.
 
    .PARAMETER Users
    The list of user objects to be processed.
 
    .PARAMETER MatchData
    A hash table containing the match data used for renaming values.
 
    .PARAMETER FieldTypes
    An array of field types to be considered for renaming.
 
    .EXAMPLE
    $users = @(
        [PSCustomObject]@{ UserPrincipalName = 'user1@test.com'; License = 'test:license'; ProxyAddress = 'proxy@test.com' }
        [PSCustomObject]@{ UserPrincipalName = 'user2@test.com'; License = 'test:license'; ProxyAddress = 'proxy@test.com' }
    )
    $matchData = @{
        'test.com' = 'newdomain.com'
        'test:' = 'newdomain:'
    }
    $fieldTypes = @('UserPrincipalName', 'License')
    Rename-UserValuesFromHash -Users $users -MatchData $matchData -FieldTypes $fieldTypes
 
    #>

    [CmdletBinding()]
    param(
        $Users,
        $MatchData,
        $FieldTypes
    )

    Write-Verbose "FieldTypes: $($FieldTypes -join ',')"
    foreach ($User in $Users) {
        foreach ($Match in $MatchData.Keys) {
            $Key = $Match
            $Value = $MatchData.$Match
            Write-Verbose "User: $($User.UserPrincipalName) Key: $Key Value: $Value"
            foreach ($Field in $FieldTypes) {
                if ($User.$Field) {
                    $User.$Field = $($User.$Field).ToLower().Replace($Key, $Value)
                }
            }
        }
    }
    return $Users
}
function Select-Properties {
    <#
    .SYNOPSIS
    Allows for easy selecting property names from one or multiple objects
 
    .DESCRIPTION
    Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects.
 
    .PARAMETER Objects
    One or more objects
 
    .PARAMETER Property
    Properties to include
 
    .PARAMETER ExcludeProperty
    Properties to exclude
 
    .PARAMETER AllProperties
    All unique properties from all objects
 
    .PARAMETER PropertyNameReplacement
    Default property name when object has no properties
 
    .EXAMPLE
    $Object1 = [PSCustomobject] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object2 = [PSCustomobject] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object1, $Object2 -AllProperties
 
    #OR:
 
    $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3
 
    .EXAMPLE
    $Object3 = [Ordered] @{
        Name1 = '1'
        Name2 = '3'
        Name3 = '5'
    }
    $Object4 = [Ordered] @{
        Name4 = '2'
        Name5 = '6'
        Name6 = '7'
    }
 
    Select-Properties -Objects $Object3, $Object4 -AllProperties
 
    $Object3, $Object4 | Select-Properties -AllProperties
 
    .NOTES
    General notes
    #>

    [CmdLetBinding()]
    param(
        [Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects,
        [string[]] $Property,
        [string[]] $ExcludeProperty,
        [switch] $AllProperties,
        [string] $PropertyNameReplacement = '*'
    )
    Begin {
        function Select-Unique {
            [CmdLetBinding()]
            param(
                [System.Collections.IList] $Object
            )
            [Array] $CleanedList = foreach ($O in $Object) {
                if ($null -ne $O) {
                    $O
                }
            }
            $New = $CleanedList.ToLower() | Select-Object -Unique
            $Selected = foreach ($_ in $New) {
                $Index = $Object.ToLower().IndexOf($_)
                if ($Index -ne -1) {
                    $Object[$Index]
                }
            }
            $Selected
        }
        $ObjectsList = [System.Collections.Generic.List[Object]]::new()
    }
    Process {
        foreach ($Object in $Objects) {
            $ObjectsList.Add($Object)
        }
    }
    End {
        if ($ObjectsList.Count -eq 0) {
            Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.'
            return
        }
        if ($ObjectsList[0] -is [System.Collections.IDictionary]) {
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) {
                    $_.Keys
                }

                $FirstObjectProperties = Select-Unique -Object $All
            }
            else {
                $FirstObjectProperties = $ObjectsList[0].Keys
            }
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
            elseif ($Property.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($Property -contains $_) {
                        $_
                        continue
                    }
                }
            }
            elseif ($ExcludeProperty.Count -gt 0) {

                $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) {
                    if ($ExcludeProperty -notcontains $_) {
                        $_
                        continue
                    }
                }
            }
        }
        elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') {
            $FirstObjectProperties = $PropertyNameReplacement
        }
        else {
            if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty
            }
            elseif ($Property.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property $Property 
            }
            elseif ($ExcludeProperty.Count -gt 0) {
                $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty
            }
            if ($AllProperties) {
                [Array] $All = foreach ($_ in $ObjectsList) {
                    $ListProperties = $_.PSObject.Properties.Name
                    if ($null -ne $ListProperties) {
                        $ListProperties
                    }
                }

                $FirstObjectProperties = Select-Unique -Object $All
            }
            else {
                $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name
            }
        }
        $FirstObjectProperties
    }
}
function Split-Array {
    <#
    .SYNOPSIS
    Split an array into multiple arrays of a specified size or by a specified number of elements
 
    .DESCRIPTION
    Split an array into multiple arrays of a specified size or by a specified number of elements
 
    .PARAMETER Objects
    Lists of objects you would like to split into multiple arrays based on their size or number of parts to split into.
 
    .PARAMETER Parts
    Parameter description
 
    .PARAMETER Size
    Parameter description
 
    .EXAMPLE
    This splits array into multiple arrays of 3
    Example below wil return 1,2,3 + 4,5,6 + 7,8,9
    Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Parts 3
 
    .EXAMPLE
    This splits array into 3 parts regardless of amount of elements
    Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Size 3
 
    .NOTES
 
    #>

    [CmdletBinding()]
    param(
        [alias('InArray', 'List')][Array] $Objects,
        [int]$Parts,
        [int]$Size
    )
    if ($Objects.Count -eq 1) {
        return $Objects 
    }
    if ($Parts) {
        $PartSize = [Math]::Ceiling($inArray.count / $Parts)
    }
    if ($Size) {
        $PartSize = $Size
        $Parts = [Math]::Ceiling($Objects.count / $Size)
    }
    $outArray = [System.Collections.Generic.List[Object]]::new()
    for ($i = 1; $i -le $Parts; $i++) {
        $start = (($i - 1) * $PartSize)
        $end = (($i) * $PartSize) - 1
        if ($end -ge $Objects.count) {
            $end = $Objects.count - 1 
        }
        $outArray.Add(@($Objects[$start..$end]))
    }
    , $outArray
}
function Test-IsDistinguishedName {
    <#
    .SYNOPSIS
    Determines whether a given string is a valid Distinguished Name (DN) format.
 
    .DESCRIPTION
    This function checks if the provided string matches the format of a Distinguished Name (DN) in Active Directory. It validates the structure of a DN which typically consists of Common Name (CN), Organizational Unit (OU), and Domain Component (DC) components.
 
    .PARAMETER Identity
    Specifies the string to be tested as a Distinguished Name (DN).
 
    .EXAMPLE
    Test-IsDistinguishedName -Identity "CN=John Doe,OU=Users,DC=example,DC=com"
    This example checks if the given string is a valid Distinguished Name format.
 
    .NOTES
    Original source: https://github.com/PsCustomObject/IT-ToolBox/blob/master/Public/Test-IsValidDn.ps1
 
    #>

    [alias('Test-IsDN')]
    [cmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('DN', 'DistinguishedName')][string] $Identity
    )
    Process {
        [regex]$distinguishedNameRegex = '^(?:(?<cn>CN=(?<name>(?:[^,]|\,)*)),)?(?:(?<path>(?:(?:CN|OU)=(?:[^,]|\,)+,?)+),)?(?<domain>(?:DC=(?:[^,]|\,)+,?)+)$'
        $Identity -match $distinguishedNameRegex
    }
}
function Find-MyProgramData {
    <#
    .SYNOPSIS
    Finds specific data within a given array of strings.
 
    .DESCRIPTION
    This function searches for a specific text within an array of strings and returns the second element of the string that matches the search criteria.
 
    .PARAMETER Data
    The array of strings to search through.
 
    .PARAMETER FindText
    The text to search for within the array of strings.
 
    .EXAMPLE
    Find-MyProgramData -Data @("Program A 123", "Program B 456", "Program C 789") -FindText "B"
    This example will return "456" as it finds the string containing "B" and returns the second element of that string.
 
    #>

    [CmdletBinding()]
    param (
        $Data,
        $FindText
    )
    foreach ($Sub in $Data) {
        if ($Sub -like $FindText) {
            $Split = $Sub.Split(' ')
            return $Split[1]
        }
    }
    return ''
}
function Initialize-ModulePortable {
    <#
    .SYNOPSIS
    Initializes a portable module by downloading or importing it along with its required modules.
 
    .DESCRIPTION
    This function initializes a portable module by either downloading it from the PowerShell Gallery or importing it from a specified path. It also recursively loads any required modules for the primary module.
 
    .PARAMETER Name
    Specifies the name of the module to initialize.
 
    .PARAMETER Path
    Specifies the path where the module will be downloaded or imported. Defaults to the current script root.
 
    .PARAMETER Download
    Switch to indicate whether to download the module from the PowerShell Gallery.
 
    .PARAMETER Import
    Switch to indicate whether to import the module from the specified path.
 
    .EXAMPLE
    Initialize-ModulePortable -Name "MyModule" -Download
    Downloads the module named "MyModule" from the PowerShell Gallery.
 
    .EXAMPLE
    Initialize-ModulePortable -Name "MyModule" -Path "C:\Modules" -Import
    Imports the module named "MyModule" from the specified path "C:\Modules".
 
    #>

    [CmdletBinding()]
    param(
        [alias('ModuleName')][string] $Name,
        [string] $Path = $PSScriptRoot,
        [switch] $Download,
        [switch] $Import
    )
    function Get-RequiredModule {
        param(
            [string] $Path,
            [string] $Name
        )
        $PrimaryModule = Get-ChildItem -LiteralPath "$Path\$Name" -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1
        if ($PrimaryModule) {
            $Module = Get-Module -ListAvailable $PrimaryModule.FullName -ErrorAction SilentlyContinue -Verbose:$false
            if ($Module) {
                [Array] $RequiredModules = $Module.RequiredModules.Name
                if ($null -ne $RequiredModules) {
                    $null
                }
                $RequiredModules
                foreach ($_ in $RequiredModules) {
                    Get-RequiredModule -Path $Path -Name $_
                }
            }
        }
        else {
            Write-Warning "Initialize-ModulePortable - Modules to load not found in $Path"
        }
    }

    if (-not $Name) {
        Write-Warning "Initialize-ModulePortable - Module name not given. Terminating."
        return
    }
    if (-not $Download -and -not $Import) {
        Write-Warning "Initialize-ModulePortable - Please choose Download/Import switch. Terminating."
        return
    }

    if ($Download) {
        try {
            if (-not $Path -or -not (Test-Path -LiteralPath $Path)) {
                $null = New-Item -ItemType Directory -Path $Path -Force
            }
            Save-Module -Name $Name -LiteralPath $Path -WarningVariable WarningData -WarningAction SilentlyContinue -ErrorAction Stop
        }
        catch {
            $ErrorMessage = $_.Exception.Message

            if ($WarningData) {
                Write-Warning "Initialize-ModulePortable - $WarningData"
            }
            Write-Warning "Initialize-ModulePortable - Error $ErrorMessage"
            return
        }
    }

    if ($Download -or $Import) {
        [Array] $Modules = Get-RequiredModule -Path $Path -Name $Name | Where-Object { $null -ne $_ }
        if ($null -ne $Modules) {
            [array]::Reverse($Modules)
        }
        $CleanedModules = [System.Collections.Generic.List[string]]::new()

        foreach ($_ in $Modules) {
            if ($CleanedModules -notcontains $_) {
                $CleanedModules.Add($_)
            }
        }
        $CleanedModules.Add($Name)

        $Items = foreach ($_ in $CleanedModules) {
            Get-ChildItem -LiteralPath "$Path\$_" -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1
        }
        [Array] $PSD1Files = $Items.FullName
    }
    if ($Download) {
        $ListFiles = foreach ($PSD1 in $PSD1Files) {
            $PSD1.Replace("$Path", '$PSScriptRoot')
        }

        $Content = @(
            '$Modules = @('
            foreach ($_ in $ListFiles) {
                " `"$_`""
            }
            ')'
            "foreach (`$_ in `$Modules) {"
            " Import-Module `$_ -Verbose:`$false -Force"
            "}"
        )
        $Content | Set-Content -Path $Path\$Name.ps1 -Force
    }
    if ($Import) {
        $ListFiles = foreach ($PSD1 in $PSD1Files) {
            $PSD1
        }
        foreach ($_ in $ListFiles) {
            Import-Module $_ -Verbose:$false -Force
        }
    }
}

function Invoke-CommandCustom {
    <#
    .SYNOPSIS
    Invokes a script block with optional parameters and arguments.
 
    .DESCRIPTION
    The Invoke-CommandCustom function executes a script block with the ability to pass parameters and arguments. It provides options to return verbose output, errors, and warnings.
 
    .PARAMETER ScriptBlock
    Specifies the script block to execute.
 
    .PARAMETER Parameter
    Specifies a dictionary of parameters to pass to the script block.
 
    .PARAMETER Argument
    Specifies an array of arguments to pass to the script block.
 
    .PARAMETER ReturnVerbose
    Indicates whether to return verbose output.
 
    .PARAMETER ReturnError
    Indicates whether to return errors.
 
    .PARAMETER ReturnWarning
    Indicates whether to return warnings.
 
    .PARAMETER AddParameter
    Indicates whether to add parameters to the script block.
 
    .EXAMPLE
    Invoke-CommandCustom -ScriptBlock { Get-Process } -ReturnVerbose
 
    Description:
    Invokes the Get-Process cmdlet and returns verbose output.
 
    .EXAMPLE
    Invoke-CommandCustom -ScriptBlock { Get-Service } -Parameter @{Name="Spooler"} -ReturnError
 
    Description:
    Invokes the Get-Service cmdlet with the "Spooler" parameter and returns any errors encountered.
 
    #>

    [cmdletBinding()]
    param(
        [scriptblock] $ScriptBlock,
        [System.Collections.IDictionary] $Parameter,
        [array] $Argument,
        [switch] $ReturnVerbose,
        [switch] $ReturnError,
        [switch] $ReturnWarning,
        [switch] $AddParameter
    )
    $Output = [ordered]@{}
    $ps = [PowerShell]::Create()
    if ($ReturnVerbose) {
        $null = $ps.AddScript('$VerbosePreference = "Continue"').AddStatement()
    }
    if ($ScriptBlock) {
        if ($Parameter -and $AddParameter) {
            $Count = 0
            [string] $ScriptBlockParams = @(
                "param("
                foreach ($Key in $Parameter.Keys) {
                    $Count++
                    if ($Count -eq $Parameter.Keys.Count) {
                        "`$$($Key)"
                    }
                    else {
                        "`$$($Key),"
                    }
                }
                ")"
                $ScriptBlock.ToString()
            )
            $ScriptBlockScript = [scriptblock]::Create($ScriptBlockParams)
            $null = $ps.AddScript($ScriptBlockScript)
        }
        else {
            $null = $ps.AddScript($ScriptBlock)
        }
    }
    if ($Parameter) {
        foreach ($Key in $Parameter.Keys) {
            $null = $ps.AddParameter($Key, $Parameter[$Key])
        }
    }
    if ($Argument) {
        foreach ($Arg in $Argument) {
            $null = $ps.AddArgument($Arg)
        }
    }
    $ErrorCaught = $null
    try {
        $InvokedCommand = $ps.Invoke()
    }
    catch {
        $ErrorCaught = $_
    }
    if ($InvokedCommand) {
        $Output['Output'] = $InvokedCommand
    }
    if ($ReturnVerbose) {
        if ($Ps.Streams.Verbose) {
            $Output['Verbose'] = $ps.Streams.Verbose
        }
    }
    if ($ReturnWarning) {
        if ($ps.Streams.Warning) {
            $Output['Warning'] = $ps.Streams.Warning
        }
    }
    if ($ReturnError) {
        if ($ErrorCaught) {
            $Output['Error'] = $ErrorCaught
        }
        else {
            if ($Ps.Streams.Error) {
                $Output['Error'] = $ps.Streams.Error
            }
        }
    }
    if ($ReturnError -or $ReturnVerbose -or $ReturnWarning) {
        $Output
    }
    else {
        $Output.Output
    }
}
function Start-InternalFunction {
    <#
    .SYNOPSIS
    Starts an internal function within a specified module.
 
    .DESCRIPTION
    This function starts an internal function within a specified module by importing the module and executing the provided script block.
 
    .PARAMETER ScriptBlock
    Specifies the script block to be executed as the internal function.
 
    .PARAMETER Module
    Specifies the name of the module containing the internal function.
 
    .EXAMPLE
    Start-InternalFunction -ScriptBlock { Get-ChildItem } -Module "ExampleModule"
    This example starts the internal function 'Get-ChildItem' within the 'ExampleModule' module.
 
    #>

    [CmdletBinding()]
    param(
        [ScriptBlock] $ScriptBlock,
        [string] $Module
    )

    $InternalModule = Import-Module -Name $Module -PassThru
    & $InternalModule $ScriptBlock
}
function Start-MyProgram {
    <#
    .SYNOPSIS
    Starts a program with specified arguments and logs the output.
 
    .DESCRIPTION
    This function starts a program with the provided arguments and logs the output using a specified logger. If no logger is provided, it returns the output as a string.
 
    .PARAMETER Program
    The path to the program to be executed.
 
    .PARAMETER CmdArgList
    An array of arguments to be passed to the program.
 
    .PARAMETER LoggerParameters
    A dictionary containing parameters for the logger.
 
    .EXAMPLE
    Start-MyProgram -Program "C:\Program.exe" -CmdArgList @("-arg1", "-arg2") -LoggerParameters @{"LogPath"="C:\Logs"; "LogLevel"="Info"}
    Starts the program "C:\Program.exe" with arguments "-arg1" and "-arg2" and logs the output using a logger with log path "C:\Logs" and log level "Info".
 
    .EXAMPLE
    Start-MyProgram -Program "C:\AnotherProgram.exe" -CmdArgList @("-input", "file.txt")
    Starts the program "C:\AnotherProgram.exe" with argument "-input file.txt" and returns the output as a string.
 
    #>

    [CmdletBinding()]
    param (
        [string] $Program,
        [string[]] $CmdArgList,
        [System.Collections.IDictionary] $LoggerParameters
    )

    $Output = (cmd /c $Program $CmdArgList '2>&1')
    if (-not $LoggerParameters) {
        if ($Output) {
            return $Output
        }
    }
    else {
        $Logger = Get-Logger @LoggerParameters
        if ($null -ne $Output) {
            $Logger.AddInfoRecord("Running program $Program with output: $Output")
        }
        else {
            $Logger.AddInfoRecord("Running program $Program $CmdArgList")
        }
    }
}
function Get-RandomCharacters {
    <#
    .SYNOPSIS
    Generates a random string of characters from a specified character set.
 
    .DESCRIPTION
    This function generates a random string of characters from a specified character set with the given length.
 
    .PARAMETER length
    The length of the random string to generate.
 
    .PARAMETER characters
    The set of characters from which to generate the random string.
 
    .EXAMPLE
    Get-RandomCharacters -length 8 -characters 'abcdef123'
    Generates a random string of 8 characters from the character set 'abcdef123'.
 
    .EXAMPLE
    Get-RandomCharacters -length 12 -characters 'ABC123!@#'
    Generates a random string of 12 characters from the character set 'ABC123!@#'.
    #>

    [cmdletbinding()]
    param(
        [int] $length,
        [string] $characters
    )
    if ($length -ne 0 -and $characters -ne '') {
        $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
        $private:ofs = "" 
        return [String]$characters[$random]
    }
    else {
        return
    }
}
function Get-RandomFileName {
    <#
    .SYNOPSIS
    Generates a random file name with a specified length and extension.
 
    .DESCRIPTION
    This function generates a random file name with a specified length and extension. The file name is created using random letters only.
 
    .PARAMETER Length
    The length of the random file name to generate. Default is 16.
 
    .PARAMETER Extension
    The extension to append to the random file name.
 
    .EXAMPLE
    Get-RandomFileName -Length 8 -Extension "txt"
    Generates a random file name with a length of 8 characters and appends the extension ".txt".
 
    .EXAMPLE
    Get-RandomFileName -Extension "docx"
    Generates a random file name with a default length of 16 characters and appends the extension ".docx".
    #>

    [cmdletbinding()]
    param(
        $Length = 16,
        $Extension
    )
    $File = Get-RandomStringName -Size $Length -LettersOnly -ToLower
    return "$File.$Extension"
}
function Get-RandomPassword {
    <#
    .SYNOPSIS
    Generates a random password with a specified number of lowercase letters, uppercase letters, numbers, and special characters.
 
    .DESCRIPTION
    This function generates a random password with a customizable combination of lowercase letters, uppercase letters, numbers, and special characters.
 
    .PARAMETER LettersLowerCase
    Specifies the number of lowercase letters to include in the password.
 
    .PARAMETER LettersHigherCase
    Specifies the number of uppercase letters to include in the password.
 
    .PARAMETER Numbers
    Specifies the number of numbers to include in the password.
 
    .PARAMETER SpecialChars
    Specifies the number of special characters to include in the password.
 
    .PARAMETER SpecialCharsLimited
    Specifies the number of limited special characters to include in the password.
 
    .EXAMPLE
    Get-RandomPassword -LettersLowerCase 4 -LettersHigherCase 2 -Numbers 1 -SpecialChars 0 -SpecialCharsLimited 1
    Generates a random password with 4 lowercase letters, 2 uppercase letters, 1 number, and 1 limited special character.
 
    .EXAMPLE
    Get-RandomPassword -LettersLowerCase 3 -LettersHigherCase 3 -Numbers 2 -SpecialChars 2 -SpecialCharsLimited 1
    Generates a random password with 3 lowercase letters, 3 uppercase letters, 2 numbers, 2 special characters, and 1 limited special character.
    #>

    [cmdletbinding()]
    param(
        [int] $LettersLowerCase = 4,
        [int] $LettersHigherCase = 2,
        [int] $Numbers = 1,
        [int] $SpecialChars = 0,
        [int] $SpecialCharsLimited = 1
    )
    $Password = @(
        Get-RandomCharacters -length $LettersLowerCase -characters 'abcdefghiklmnoprstuvwxyz'
        Get-RandomCharacters -length $LettersHigherCase -characters 'ABCDEFGHKLMNOPRSTUVWXYZ'
        Get-RandomCharacters -length $Numbers -characters '1234567890'
        Get-RandomCharacters -length $SpecialChars -characters '!$%()=?{@#'
        Get-RandomCharacters -length $SpecialCharsLimited -characters '!$#'
    )
    $StringPassword = $Password -join ''
    $StringPassword = ($StringPassword.ToCharArray() | Get-Random -Count $StringPassword.Length) -join ''
    return $StringPassword
}

function Get-RandomStringName {
    <#
    .SYNOPSIS
    Generates a random string of specified length with various options.
 
    .DESCRIPTION
    This function generates a random string of specified length with options to convert the case and include only letters.
 
    .PARAMETER Size
    The length of the random string to generate. Default is 31.
 
    .PARAMETER ToLower
    Convert the generated string to lowercase.
 
    .PARAMETER ToUpper
    Convert the generated string to uppercase.
 
    .PARAMETER LettersOnly
    Generate a random string with only letters.
 
    .EXAMPLE
    Get-RandomStringName -Size 10
    Generates a random string of length 10.
 
    .EXAMPLE
    Get-RandomStringName -Size 8 -ToLower
    Generates a random string of length 8 and converts it to lowercase.
 
    .EXAMPLE
    Get-RandomStringName -Size 12 -ToUpper
    Generates a random string of length 12 and converts it to uppercase.
 
    .EXAMPLE
    Get-RandomStringName -Size 15 -LettersOnly
    Generates a random string of length 15 with only letters.
 
    #>

    [cmdletbinding()]
    param(
        [int] $Size = 31,
        [switch] $ToLower,
        [switch] $ToUpper,
        [switch] $LettersOnly
    )
    [string] $MyValue = @(
        if ($LettersOnly) {
            ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ }))
        }
        else {
            ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ }))
        }
    )
    if ($ToLower) {
        return $MyValue.ToLower()
    }
    if ($ToUpper) {
        return $MyValue.ToUpper()
    }
    return $MyValue
}
function Dismount-PSRegistryPath {
    <#
    .SYNOPSIS
    Dismounts a registry path.
 
    .DESCRIPTION
    This function dismounts a registry path specified by the MountPoint parameter. It unloads the registry path using reg.exe command.
 
    .PARAMETER MountPoint
    Specifies the registry path to be dismounted.
 
    .PARAMETER Suppress
    Suppresses the output if set to $true.
 
    .EXAMPLE
    Dismount-PSRegistryPath -MountPoint "HKLM:\Software\MyApp" -Suppress
    Dismounts the registry path "HKLM:\Software\MyApp" without displaying any output.
 
    .EXAMPLE
    Dismount-PSRegistryPath -MountPoint "HKCU:\Software\Settings"
    Dismounts the registry path "HKCU:\Software\Settings" and displays output if successful.
 
    #>

    [alias('Dismount-RegistryPath')]
    [cmdletbinding()]
    param(
        [Parameter(Mandatory)][string] $MountPoint,
        [switch] $Suppress
    )

    [gc]::Collect()

    $pinfo = [System.Diagnostics.ProcessStartInfo]::new()
    $pinfo.FileName = "reg.exe"
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = " unload $MountPoint"
    $pinfo.CreateNoWindow = $true
    $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
    $p = [System.Diagnostics.Process]::new()
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $Output = $p.StandardOutput.ReadToEnd()
    $Errors = $p.StandardError.ReadToEnd()

    if ($Errors) {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw $Errors
        }
        else {
            Write-Warning -Message "Dismount-PSRegistryPath - Couldn't unmount $MountPoint. $Errors"
        }
    }
    else {
        if ($Output -like "*operation completed*") {
            if (-not $Suppress) {
                return $true
            }
        }
    }
    if (-not $Suppress) {
        return $false
    }
}

function Get-PSRegistry {
    <#
    .SYNOPSIS
    Get registry key values.
 
    .DESCRIPTION
    Get registry key values.
 
    .PARAMETER RegistryPath
    The registry path to get the values from.
 
    .PARAMETER ComputerName
    The computer to get the values from. If not specified, the local computer is used.
 
    .PARAMETER ExpandEnvironmentNames
    Expand environment names in the registry value.
    By default it doesn't do that. If you want to expand environment names, use this parameter.
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName AD1
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters'
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName AD1,AD2,AD3 | ft -AutoSize
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service'
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell' | Format-Table -AutoSize
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' -ComputerName AD1 -Advanced
 
    .EXAMPLE
    Get-PSRegistry -RegistryPath "HKLM:\Software\Microsoft\Powershell\1\Shellids\Microsoft.Powershell\"
 
    .EXAMPLE
    # Get default key and it's value
    Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key ""
 
    .EXAMPLE
    # Get default key and it's value (alternative)
    Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -DefaultKey
 
    .NOTES
    General notes
    #>

    [cmdletbinding()]
    param(
        [alias('Path')][string[]] $RegistryPath,
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [string] $Key,
        [switch] $Advanced,
        [switch] $DefaultKey,
        [switch] $ExpandEnvironmentNames,
        [Parameter(DontShow)][switch] $DoNotUnmount
    )
    $Script:CurrentGetCount++
    Get-PSRegistryDictionaries

    $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath

    [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName

    [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent

    if ($PSBoundParameters.ContainsKey("Key") -or $DefaultKey) {
        [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary -Key $Key
        foreach ($Computer in $Computers[0]) {
            foreach ($R in $RegistryValues) {
                Get-PSSubRegistry -Registry $R -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent
            }
        }
        foreach ($Computer in $Computers[1]) {
            foreach ($R in $RegistryValues) {
                Get-PSSubRegistry -Registry $R -ComputerName $Computer -Remote -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent
            }
        }
    }
    else {
        [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary
        foreach ($Computer in $Computers[0]) {
            foreach ($R in $RegistryValues) {

                Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent
            }
        }
        foreach ($Computer in $Computers[1]) {
            foreach ($R in $RegistryValues) {
                Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Remote -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent
            }
        }
    }
    $Script:CurrentGetCount--
    if ($Script:CurrentGetCount -eq 0) {
        if (-not $DoNotUnmount) {
            Unregister-MountedRegistry
        }
    }
}
function Mount-PSRegistryPath {
    <#
    .SYNOPSIS
    Mounts a registry path to a specified location.
 
    .DESCRIPTION
    This function mounts a registry path to a specified location using the reg.exe utility.
 
    .PARAMETER MountPoint
    Specifies the registry mount point where the registry path will be mounted.
 
    .PARAMETER FilePath
    Specifies the file path of the registry hive to be mounted.
 
    .EXAMPLE
    Mount-PSRegistryPath -MountPoint 'HKEY_USERS\.DEFAULT_USER111' -FilePath 'C:\Users\Default\NTUSER.DAT'
    Mounts the registry hive located at 'C:\Users\Default\NTUSER.DAT' to the registry key 'HKEY_USERS\.DEFAULT_USER111'.
 
    .NOTES
    This function requires administrative privileges to mount registry paths.
    #>

    [alias('Mount-RegistryPath')]
    [cmdletbinding()]
    param(
        [Parameter(Mandatory)][string] $MountPoint,
        [Parameter(Mandatory)][string] $FilePath
    )

    $pinfo = [System.Diagnostics.ProcessStartInfo]::new()
    $pinfo.FileName = "reg.exe"
    $pinfo.RedirectStandardError = $true
    $pinfo.RedirectStandardOutput = $true
    $pinfo.UseShellExecute = $false
    $pinfo.Arguments = " load $MountPoint $FilePath"
    $pinfo.CreateNoWindow = $true
    $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
    $p = [System.Diagnostics.Process]::new()
    $p.StartInfo = $pinfo
    $p.Start() | Out-Null
    $p.WaitForExit()
    $Output = $p.StandardOutput.ReadToEnd()
    $Errors = $p.StandardError.ReadToEnd()
    if ($Errors) {
        if ($PSBoundParameters.ErrorAction -eq 'Stop') {
            throw $Errors
        }
        else {
            Write-Warning -Message "Mount-PSRegistryPath - Couldn't mount $MountPoint. $Errors"
        }
    }
    else {
        if ($Output -like "*operation completed*") {
            if (-not $Suppress) {
                return $true
            }
        }
    }
    if (-not $Suppress) {
        return $false
    }
}

function New-PSRegistry {
    <#
    .SYNOPSIS
    Provides a way to create new registry paths
 
    .DESCRIPTION
    Provides a way to create new registry paths
 
    .PARAMETER ComputerName
    The computer to run the command on. Defaults to local computer.
 
    .PARAMETER RegistryPath
    Registry Path to Create
 
    .PARAMETER Suppress
    Suppresses the output of the command. By default the command outputs PSObject with the results of the operation.
 
    .EXAMPLE
    New-PSRegistry -RegistryPath "HKCU:\\Tests1\CurrentControlSet\Control\Lsa" -Verbose -WhatIf
 
    .EXAMPLE
    New-PSRegistry -RegistryPath "HKCU:\\Tests1\CurrentControlSet\Control\Lsa"
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [Parameter(Mandatory)][string] $RegistryPath,
        [switch] $Suppress
    )
    Get-PSRegistryDictionaries

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath

    [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary

    foreach ($Registry in $RegistryTranslated) {
        $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -ReverseTypesDictionary $Script:ReverseTypesDictionary

        if ($RegistryValue.HiveKey) {
            foreach ($Computer in $ComputersSplit[0]) {

                New-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
            foreach ($Computer in $ComputersSplit[1]) {

                New-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Remote -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
        }
        else {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Unregister-MountedRegistry
                throw
            }
            else {

                Write-Warning "New-PSRegistry - Setting registry to $RegistryPath have failed. Couldn't translate HIVE."
            }
        }
    }
    Unregister-MountedRegistry
}
function Remove-PSRegistry {
    <#
    .SYNOPSIS
    Remove registry keys and folders
 
    .DESCRIPTION
    Remove registry keys and folders using .NET methods
 
    .PARAMETER ComputerName
    The computer to run the command on. Defaults to local computer.
 
    .PARAMETER RegistryPath
    The registry path to remove.
 
    .PARAMETER Key
    The registry key to remove.
 
    .PARAMETER Recursive
    Forces deletion of registry folder and all keys, including nested folders
 
    .PARAMETER Suppress
    Suppresses the output of the command. By default the command outputs PSObject with the results of the operation.
 
    .EXAMPLE
    Remove-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests\Ok\MaybeNot" -Recursive
 
    .EXAMPLE
    Remove-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests\Ok\MaybeNot" -Key "LimitBlankPass1wordUse"
 
    .EXAMPLE
    Remove-PSRegistry -RegistryPath "HKCU:\Tests\Ok"
 
    .NOTES
    General notes
    #>

    [cmdletBinding(SupportsShouldProcess)]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [Parameter(Mandatory)][string] $RegistryPath,
        [Parameter()][string] $Key,
        [switch] $Recursive,
        [switch] $Suppress
    )
    Get-PSRegistryDictionaries

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath

    [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary

    foreach ($Registry in $RegistryTranslated) {
        $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -ReverseTypesDictionary $Script:ReverseTypesDictionary

        if ($RegistryValue.HiveKey) {
            foreach ($Computer in $ComputersSplit[0]) {

                Remove-PrivateRegistry -Key $Key -RegistryValue $RegistryValue -Computer $Computer -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
            foreach ($Computer in $ComputersSplit[1]) {

                Remove-PrivateRegistry -Key $Key -RegistryValue $RegistryValue -Computer $Computer -Remote -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
        }
        else {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Unregister-MountedRegistry
                throw
            }
            else {

                Write-Warning "Remove-PSRegistry - Removing registry $RegistryPath have failed (recursive: $($Recursive.IsPresent)). Couldn't translate HIVE."
            }
        }
    }
    Unregister-MountedRegistry
}
function Set-PSRegistry {
    <#
    .SYNOPSIS
    Sets/Updates registry entries locally and remotely using .NET methods.
 
    .DESCRIPTION
    Sets/Updates registry entries locally and remotely using .NET methods. If the registry path to key doesn't exists it will be created.
 
    .PARAMETER ComputerName
    The computer to run the command on. Defaults to local computer.
 
    .PARAMETER RegistryPath
    Registry Path to Update
 
    .PARAMETER Type
    Registry type to use. Options are: REG_SZ, REG_EXPAND_SZ, REG_BINARY, REG_DWORD, REG_MULTI_SZ, REG_QWORD, string, expandstring, binary, dword, multistring, qword
 
    .PARAMETER Key
    Registry key to set. If the path to registry key doesn't exists it will be created.
 
    .PARAMETER Value
    Registry value to set.
 
    .PARAMETER Suppress
    Suppresses the output of the command. By default the command outputs PSObject with the results of the operation.
 
    .EXAMPLE
    Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_DWORD -Key "16 LDAP Interface Events" -Value 2 -ComputerName AD1
 
    .EXAMPLE
    Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_SZ -Key "LDAP Interface Events" -Value 'test' -ComputerName AD1
 
    .EXAMPLE
    Set-PSRegistry -RegistryPath "HKCU:\\Tests" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD
 
    .EXAMPLE
    Set-PSRegistry -RegistryPath "HKCU:\\Tests\MoreTests\Tests1" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD
 
    .EXAMPLE
    # Setting default value
 
    $ValueData = [byte[]] @(
        0, 1, 0, 0, 9, 0, 0, 0, 128, 0, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
        0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
        0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
        0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
        0, 3, 0, 0, 0, 5, 0, 10, 0, 14, 0, 3, 0, 5, 0, 6, 0, 6, 0, 4, 0, 4, 0
    )
    Set-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key '' -Value $ValueData -Type 'NONE'
 
    .NOTES
    General notes
    #>

    [cmdletbinding(SupportsShouldProcess)]
    param(
        [string[]] $ComputerName = $Env:COMPUTERNAME,
        [Parameter(Mandatory)][string] $RegistryPath,
        [Parameter(Mandatory)][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type,
        [Parameter()][string] $Key,
        [Parameter(Mandatory)][object] $Value,
        [switch] $Suppress
    )
    Unregister-MountedRegistry

    Get-PSRegistryDictionaries

    [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName

    $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath

    [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary

    foreach ($Registry in $RegistryTranslated) {
        $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -Value $Value -Type $Type -ReverseTypesDictionary $Script:ReverseTypesDictionary
        if ($RegistryValue.HiveKey) {
            foreach ($Computer in $ComputersSplit[0]) {

                Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
            foreach ($Computer in $ComputersSplit[1]) {

                Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Remote -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference
            }
        }
        else {
            if ($PSBoundParameters.ErrorAction -eq 'Stop') {
                Unregister-MountedRegistry
                throw
            }
            else {

                Write-Warning "Set-PSRegistry - Setting registry to $Registry have failed. Couldn't translate HIVE."
            }
        }
    }
    Unregister-MountedRegistry
}
function Test-PSRegistry {
    <#
    .SYNOPSIS
    Tests the existence of a specified registry key on a remote or local computer.
 
    .DESCRIPTION
    This function checks if a specified registry key exists on a remote or local computer.
 
    .PARAMETER RegistryPath
    Specifies the path to the registry key(s) to be checked.
 
    .PARAMETER ComputerName
    Specifies the name of the remote computer to check. Defaults to the local computer.
 
    .PARAMETER Key
    Specifies the specific registry key to check for existence.
 
    .EXAMPLE
    Test-PSRegistry -RegistryPath 'HKLM:\Software\Microsoft' -Key 'Windows'
 
    Description
    -----------
    Checks if the 'Windows' key exists under 'HKLM:\Software\Microsoft' on the local computer.
 
    .EXAMPLE
    Test-PSRegistry -RegistryPath 'HKLM:\Software\Microsoft' -ComputerName 'RemoteComputer' -Key 'Windows'
 
    Description
    -----------
    Checks if the 'Windows' key exists under 'HKLM:\Software\Microsoft' on the 'RemoteComputer'.
 
    #>

    [cmdletbinding()]
    param(
        [alias('Path')][string[]] $RegistryPath,
        [string] $ComputerName = $Env:COMPUTERNAME,
        [string] $Key
    )
    $Output = Get-PSRegistry -RegistryPath $RegistryPath -ComputerName $ComputerName
    if ($Output.PSConnection -eq $true -and $Output.PSError -eq $false) {
        if ($Key) {
            if ($null -ne $Output.$Key) {
                return $true
            }
            else {
                return $false
            }
        }
        else {
            return $true
        }
    }
    else {
        return $false
    }
}
function New-Runspace {
    <#
    .SYNOPSIS
    Creates a new runspace pool with the specified minimum and maximum runspaces.
 
    .DESCRIPTION
    This function creates a new runspace pool with the specified minimum and maximum runspaces. It allows for concurrent execution of PowerShell scripts.
 
    .PARAMETER minRunspaces
    The minimum number of runspaces to be created in the runspace pool. Default is 1.
 
    .PARAMETER maxRunspaces
    The maximum number of runspaces to be created in the runspace pool. Default is the number of processors plus 1.
 
    .EXAMPLE
    $pool = New-Runspace -minRunspaces 2 -maxRunspaces 5
    Creates a runspace pool with a minimum of 2 and a maximum of 5 runspaces.
 
    .EXAMPLE
    $pool = New-Runspace
    Creates a runspace pool with default minimum and maximum runspaces.
 
    #>

    [cmdletbinding()]
    param (
        [int] $minRunspaces = 1,
        [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1
    )
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces)

    $RunspacePool.Open()
    return $RunspacePool
}
function Start-Runspace {
    <#
    .SYNOPSIS
    Starts a new runspace with the provided script block, parameters, and runspace pool.
 
    .DESCRIPTION
    This function creates a new runspace using the specified script block, parameters, and runspace pool. It then starts the runspace and returns an object containing the runspace and its status.
 
    .PARAMETER ScriptBlock
    The script block to be executed in the new runspace.
 
    .PARAMETER Parameters
    The parameters to be passed to the script block.
 
    .PARAMETER RunspacePool
    The runspace pool in which the new runspace will be created.
 
    .EXAMPLE
    $scriptBlock = {
        Get-Process
    }
    $parameters = @{
        Name = 'explorer.exe'
    }
    $runspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5)
    $runspacePool.Open()
    $result = Start-Runspace -ScriptBlock $scriptBlock -Parameters $parameters -RunspacePool $runspacePool
    $result.Pipe | Receive-Job -Wait
 
    This example starts a new runspace that retrieves information about the 'explorer.exe' process.
 
    #>

    [cmdletbinding()]
    param (
        [ScriptBlock] $ScriptBlock,
        [System.Collections.IDictionary] $Parameters,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool
    )
    if ($ScriptBlock -ne '') {
        $runspace = [PowerShell]::Create()
        $null = $runspace.AddScript($ScriptBlock)
        if ($null -ne $Parameters) {
            $null = $runspace.AddParameters($Parameters)
        }
        $runspace.RunspacePool = $RunspacePool

        [PSCustomObject]@{
            Pipe   = $runspace
            Status = $runspace.BeginInvoke()
        }
    }
}
function Stop-Runspace {
    <#
    .SYNOPSIS
    Stops and cleans up the specified runspaces.
 
    .DESCRIPTION
    This function stops and cleans up the specified runspaces by checking their status and handling any errors, warnings, and verbose messages. It also provides an option for extended output.
 
    .PARAMETER Runspaces
    Specifies the array of runspaces to stop.
 
    .PARAMETER FunctionName
    Specifies the name of the function associated with the runspaces.
 
    .PARAMETER RunspacePool
    Specifies the runspace pool to close and dispose of.
 
    .PARAMETER ExtendedOutput
    Indicates whether to include extended output in the result.
 
    .EXAMPLE
    Stop-Runspace -Runspaces $runspaceArray -FunctionName "MyFunction" -RunspacePool $pool -ExtendedOutput
    Stops the specified runspaces in the $runspaceArray associated with the function "MyFunction" using the runspace pool $pool and includes extended output.
 
    #>

    [cmdletbinding()]
    param(
        [Array] $Runspaces,
        [string] $FunctionName,
        [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool,
        [switch] $ExtendedOutput
    )

    [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) {
        foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) {
            $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) {
                Write-Error -ErrorRecord $e
                $e
            }
            foreach ($w in $($Runspace.Pipe.Streams.Warning)) {
                Write-Warning -Message $w
            }
            foreach ($v in $($Runspace.Pipe.Streams.Verbose)) {
                Write-Verbose -Message $v
            }
            if ($ExtendedOutput) {
                @{
                    Output = $Runspace.Pipe.EndInvoke($Runspace.Status)
                    Errors = $Errors
                }
            }
            else {
                $Runspace.Pipe.EndInvoke($Runspace.Status)
            }
            $Runspace.Status = $null
        }
    }
    $RunspacePool.Close()
    $RunspacePool.Dispose()
    if ($List.Count -eq 1) {
        return , $List
    }
    else {
        return $List
    }
}
function Get-PSService {
    <#
    .SYNOPSIS
    Alternative way to Get-Service
 
    .DESCRIPTION
    Alternative way to Get-Service which works using CIM queries
 
    .PARAMETER ComputerName
    ComputerName(s) to query for services
 
    .PARAMETER Protocol
    Protocol to use to gather services
 
    .PARAMETER Service
    Limit output to just few services
 
    .PARAMETER All
    Return all data without filtering
 
    .PARAMETER Extended
    Return more data
 
    .EXAMPLE
    Get-PSService -ComputerName AD1, AD2, AD3, AD4 -Service 'Dnscache', 'DNS', 'PeerDistSvc', 'WebClient', 'Netlogon'
 
    .EXAMPLE
    Get-PSService -ComputerName AD1, AD2 -Extended
 
    .EXAMPLE
    Get-PSService
 
    .EXAMPLE
    Get-PSService -Extended
 
    .NOTES
    General notes
    #>

    [cmdletBinding()]
    param(
        [alias('Computer', 'Computers')][string[]] $ComputerName = $Env:COMPUTERNAME,
        [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default',
        [alias('Services')][string[]] $Service,
        [switch] $All,
        [switch] $Extended
    )
    [string] $Class = 'win32_service'
    [string] $Properties = '*'

    if ($Service) {
        $CachedServices = @{}
        foreach ($S in $Service) {
            $CachedServices[$S] = $true
        }
    }
    $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties
    if ($All) {
        if ($Service) {
            foreach ($Entry in $Information) {
                if ($CachedServices[$Entry.Name]) {
                    $Entry
                }
            }
        }
        else {
            $Information
        }
    }
    else {
        foreach ($Data in $Information) {

            if ($Service) {
                if (-not $CachedServices[$Data.Name]) {
                    continue
                }
            }
            $OutputService = [ordered] @{
                ComputerName = if ($Data.PSComputerName) {
                    $Data.PSComputerName 
                }
                else {
                    $Env:COMPUTERNAME 
                }
                Status       = $Data.State
                Name         = $Data.Name
                ServiceType  = $Data.ServiceType
                StartType    = $Data.StartMode
                DisplayName  = $Data.DisplayName
            }
            if ($Extended) {
                $OutputServiceExtended = [ordered] @{
                    StatusOther             = $Data.Status
                    ExitCode                = $Data.ExitCode
                    DesktopInteract         = $Data.DesktopInteract
                    ErrorControl            = $Data.ErrorControl
                    PathName                = $Data.PathName
                    Caption                 = $Data.Caption
                    Description             = $Data.Description

                    Started                 = $Data.Started
                    SystemName              = $Data.SystemName
                    AcceptPause             = $Data.AcceptPause
                    AcceptStop              = $Data.AcceptStop
                    ServiceSpecificExitCode = $Data.ServiceSpecificExitCode
                    StartName               = $Data.StartName

                    TagId                   = $Data.TagId
                    CheckPoint              = $Data.CheckPoint
                    DelayedAutoStart        = $Data.DelayedAutoStart
                    ProcessId               = $Data.ProcessId
                    WaitHint                = $Data.WaitHint
                }
                [PSCustomObject] ($OutputService + $OutputServiceExtended)
            }
            else {
                [PSCustomObject] $OutputService
            }
        }
    }
}
function Set-ServiceRecovery {
    <#
    .SYNOPSIS
    Configures the recovery options for a specified Windows service.
 
    .DESCRIPTION
    This function sets the recovery options for a Windows service on a remote server. It allows you to define the actions to take upon service failure and the time intervals between these actions.
 
    .PARAMETER ServiceDisplayName
    The display name of the service for which recovery options need to be set.
 
    .PARAMETER Server
    The name of the server where the service is located.
 
    .PARAMETER action1
    The action to take for the first failure. Default is "restart".
 
    .PARAMETER time1
    The time interval (in milliseconds) before the first action is taken. Default is 30000 milliseconds.
 
    .PARAMETER action2
    The action to take for the second failure. Default is "restart".
 
    .PARAMETER time2
    The time interval (in milliseconds) before the second action is taken. Default is 30000 milliseconds.
 
    .PARAMETER actionLast
    The action to take for subsequent failures. Default is "restart".
 
    .PARAMETER timeLast
    The time interval (in milliseconds) before the subsequent action is taken. Default is 30000 milliseconds.
 
    .PARAMETER resetCounter
    The time interval (in seconds) after which the failure counter is reset. Default is 4000 seconds.
 
    .EXAMPLE
    Set-ServiceRecovery -ServiceDisplayName "Pulseway" -Server "MAIL1"
    Configures the recovery options for the "Pulseway" service on the server "MAIL1" with default settings.
 
    .NOTES
    For more information on service recovery options, refer to: https://technet.microsoft.com/en-us/library/cc742019.aspx
    #>

    [alias('Set-Recovery')]
    param
    (
        [string] [Parameter(Mandatory = $true)] $ServiceDisplayName,
        [string] [Parameter(Mandatory = $true)] $Server,
        [string] $action1 = "restart",
        [int] $time1 = 30000, # in miliseconds
        [string] $action2 = "restart",
        [int] $time2 = 30000, # in miliseconds
        [string] $actionLast = "restart",
        [int] $timeLast = 30000, # in miliseconds
        [int] $resetCounter = 4000 # in seconds
    )
    $serverPath = "\\" + $server
    $services = Get-CimInstance -ClassName 'Win32_Service' -ComputerName $Server | Where-Object { $_.DisplayName -imatch $ServiceDisplayName }
    $action = $action1 + "/" + $time1 + "/" + $action2 + "/" + $time2 + "/" + $actionLast + "/" + $timeLast
    foreach ($service in $services) {

        $output = sc.exe $serverPath failure $($service.Name) actions= $action reset= $resetCounter
    }
}
function Get-SqlQueryColumnInformation {
    <#
    .SYNOPSIS
    Retrieves column information for a specified table in a SQL database.
 
    .DESCRIPTION
    This function retrieves column information for a specified table in a SQL database using the INFORMATION_SCHEMA.COLUMNS view.
 
    .PARAMETER SqlServer
    The SQL Server instance where the database is located.
 
    .PARAMETER SqlDatabase
    The name of the SQL database.
 
    .PARAMETER Table
    The name of the table for which column information is to be retrieved.
 
    .EXAMPLE
    Get-SqlQueryColumnInformation -SqlServer "localhost" -SqlDatabase "MyDatabase" -Table "MyTable"
    Retrieves column information for the table "MyTable" in the database "MyDatabase" on the SQL Server instance "localhost".
 
    #>

    [CmdletBinding()]
    param (
        [string] $SqlServer,
        [string] $SqlDatabase,
        [string] $Table
    )
    $Table = $Table.Replace("dbo.", '').Replace('[', '').Replace(']', '') 
    $SqlDatabase = $SqlDatabase.Replace('[', '').Replace(']', '') 
    $SqlDatabase = "[$SqlDatabase]"
    $Query = "SELECT * FROM $SqlDatabase.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$Table'"
    $SqlReturn = @(
        try {
            Invoke-DbaQuery -ErrorAction Stop -SqlInstance $SqlServer -Query $Query 
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            "Error occured (Get-SqlQueryColumnInformation): $ErrorMessage" 
        }
    )
    return $SQLReturn
}
function New-SqlQuery {
    <#
    .SYNOPSIS
    Creates and executes SQL queries based on provided parameters.
 
    .DESCRIPTION
    The New-SqlQuery function generates SQL queries for inserting data into a specified table. It adds additional fields for tracking when the data was added and by whom. The function utilizes a mapping table to map object properties to table columns.
 
    .PARAMETER SqlSettings
    The SQL connection settings.
 
    .PARAMETER Object
    The object containing data to be inserted into the SQL table.
 
    .PARAMETER TableMapping
    A hashtable mapping object properties to table columns.
 
    .EXAMPLE
    $sqlSettings = Get-SqlSettings
    $object = [PSCustomObject]@{
        DomainController = 'AD1.ad.evotec.xyz'
        Action = 'Event log automatic backup'
        BackupPath = 'C:\Windows\System32\Winevt\Logs\Archive-Security-2018-09-25-14-12-52-658.evtx'
        LogType = 'Security'
        Who = 'Automatic Backup'
        When = '2018-09-25 16:12:53'
        EventID = '1105'
        RecordID = '2434391'
    }
    $tableMapping = @{
        DomainController = 'DomainController'
        Action = 'Action'
        BackupPath = 'BackupPath'
        LogType = 'LogType'
        Who = 'Who'
        When = 'When'
        EventID = 'EventID'
        RecordID = 'RecordID'
    }
    New-SqlQuery -SqlSettings $sqlSettings -Object $object -TableMapping $tableMapping
    #>

    [CmdletBinding()]
    param (
        [Object] $SqlSettings,
        [Object] $Object,
        [Object] $TableMapping
    )

    $ArraySQLQueries = New-ArrayList
    if ($null -ne $Object) {

        foreach ($O in $Object) {
            $ArrayMain = New-ArrayList
            $ArrayKeys = New-ArrayList
            $ArrayValues = New-ArrayList

            if (-not $O.AddedWhen) {
                Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWhen" -Value (Get-Date) -Force
            }
            if (-not $O.AddedWho) {
                Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWho" -Value ($Env:USERNAME) -Force
            }
            $DuplicateString = [System.Text.StringBuilder]::new()
            foreach ($E in $O.PSObject.Properties) {
                $FieldName = $E.Name
                $FieldValue = $E.Value

                foreach ($MapKey in $TableMapping.Keys) {
                    if ($FieldName -eq $MapKey) {
                        $MapValue = $TableMapping.$MapKey
                        $MapValueSplit = $MapValue -Split ','

                        if ($FieldValue -is [DateTime]) {
                            $FieldValue = Get-Date $FieldValue -Format "yyyy-MM-dd HH:mm:ss" 
                        }
                        if ($FieldValue -like "*'*") {
                            $FieldValue = $FieldValue -Replace "'", "''" 
                        }
                        Add-ToArray -List $ArrayKeys -Element "[$($MapValueSplit[0])]"
                        if ([string]::IsNullOrWhiteSpace($FieldValue)) {

                            Add-ToArray -List $ArrayValues -Element "NULL"
                        }
                        else {
                            foreach ($ColumnName in $SqlSettings.SqlCheckBeforeInsert) {
                                $DuplicateColumn = $ColumnName.Replace("[", '').Replace("]", '') 

                                if ($MapValueSplit[0] -eq $DuplicateColumn) {
                                    if ($DuplicateString.Length -ne 0) {

                                        $null = $DuplicateString.Append(" AND ")
                                    }
                                    $null = $DuplicateString.Append("[$DuplicateColumn] = '$FieldValue'")
                                }
                            }
                            Add-ToArray -List $ArrayValues -Element "'$FieldValue'"
                        }
                    }
                }
            }
            if ($ArrayKeys) {
                if ($null -ne $SqlSettings.SqlCheckBeforeInsert -and $DuplicateString.Length -gt 0) {
                    Add-ToArray -List $ArrayMain -Element "IF NOT EXISTS ("
                    Add-ToArray -List $ArrayMain -Element "SELECT 1 FROM "
                    Add-ToArray -List $ArrayMain -Element "$($SqlSettings.SqlTable) "
                    Add-ToArray -List $ArrayMain -Element "WHERE $($DuplicateString.ToString())"
                    Add-ToArray -List $ArrayMain -Element ")"
                }
                Add-ToArray -List $ArrayMain -Element "BEGIN"
                Add-ToArray -List $ArrayMain -Element "INSERT INTO $($SqlSettings.SqlTable) ("
                Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
                Add-ToArray -List $ArrayMain -Element ') VALUES ('
                Add-ToArray -List $ArrayMain -Element ($ArrayValues -join ',')
                Add-ToArray -List $ArrayMain -Element ')'
                Add-ToArray -List $ArrayMain -Element "END"
                Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
            }
        }
    }
    return $ArraySQLQueries
}
function New-SqlQueryAlterTable {
    <#
    .SYNOPSIS
    Creates SQL queries to add new columns to an existing table.
 
    .DESCRIPTION
    This function generates SQL queries to add new columns to an existing SQL table based on the provided TableMapping and ExistingColumns.
 
    .PARAMETER SqlSettings
    An object containing SQL connection settings.
 
    .PARAMETER TableMapping
    An object representing the mapping of new columns to be added. Keys are column names, values are column definitions.
 
    .PARAMETER ExistingColumns
    An array of existing column names in the table.
 
    .EXAMPLE
    $sqlSettings = Get-SqlSettings
    $tableMapping = @{
        "NewColumn1" = "Column1Name, nvarchar(50)"
        "NewColumn2" = "Column2Name, int"
    }
    $existingColumns = @("Column1Name", "Column3Name")
 
    New-SqlQueryAlterTable -SqlSettings $sqlSettings -TableMapping $tableMapping -ExistingColumns $existingColumns
    # Generates SQL queries to add "NewColumn1" and "NewColumn2" to the table.
 
    #>

    [CmdletBinding()]
    param (
        [Object]$SqlSettings,
        [Object]$TableMapping,
        [string[]] $ExistingColumns
    )
    $ArraySQLQueries = New-ArrayList
    $ArrayMain = New-ArrayList
    $ArrayKeys = New-ArrayList

    foreach ($MapKey in $TableMapping.Keys) {
        $MapValue = $TableMapping.$MapKey
        $Field = $MapValue -Split ','

        if ($ExistingColumns -notcontains $MapKey -and $ExistingColumns -notcontains $Field[0]) {
            if ($Field.Count -eq 1) {
                Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL"
            }
            elseif ($Field.Count -eq 2) {
                Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL"
            }
            elseif ($Field.Count -eq 3) {
                Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])"
            }
        }
    }

    if ($ArrayKeys) {
        Add-ToArray -List $ArrayMain -Element "ALTER TABLE $($SqlSettings.SqlTable) ADD"
        Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
        Add-ToArray -List $ArrayMain -Element ';'
        Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
    }
    return $ArraySQLQueries
}
function New-SqlQueryCreateTable {
    <#
    .SYNOPSIS
    Creates SQL query to generate a new table based on provided table mapping.
 
    .DESCRIPTION
    This function generates a SQL query to create a new table in a database based on the table mapping provided. The table mapping should be a hashtable where the keys represent the column names and the values represent the column data types and constraints.
 
    .PARAMETER SqlSettings
    An object containing SQL connection settings.
 
    .PARAMETER TableMapping
    A hashtable containing the mapping of column names to data types and constraints.
 
    .EXAMPLE
    $sqlSettings = @{ SqlTable = "MyTable" }
    $tableMapping = @{
        Column1 = "int",
        Column2 = "nvarchar(50) NULL",
        Column3 = "datetime NOT NULL"
    }
    New-SqlQueryCreateTable -SqlSettings $sqlSettings -TableMapping $tableMapping
    #>

    [CmdletBinding()]
    param (
        [Object]$SqlSettings,
        [Object]$TableMapping
    )
    $ArraySQLQueries = New-ArrayList
    $ArrayMain = New-ArrayList
    $ArrayKeys = New-ArrayList

    foreach ($MapKey in $TableMapping.Keys) {
        $MapValue = $TableMapping.$MapKey
        $Field = $MapValue -Split ','
        if ($Field.Count -eq 1) {
            Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL"
        }
        elseif ($Field.Count -eq 2) {
            Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL"
        }
        elseif ($Field.Count -eq 3) {
            Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])"
        }
    }
    if ($ArrayKeys) {
        Add-ToArray -List $ArrayMain -Element "CREATE TABLE $($SqlSettings.SqlTable) ("
        Add-ToArray -List $ArrayMain -Element "ID int IDENTITY(1,1) PRIMARY KEY,"
        Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',')
        Add-ToArray -List $ArrayMain -Element ')'
        Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "")
    }
    return $ArraySQLQueries
}
function New-SqlTableMapping {
    <#
    .SYNOPSIS
    Creates a new SQL table mapping based on the provided parameters.
 
    .DESCRIPTION
    This function creates a new SQL table mapping based on the provided parameters. It generates a mapping for each property in the object based on its data type.
 
    .PARAMETER SqlTableMapping
    The existing SQL table mapping to update.
 
    .PARAMETER Object
    The object for which the SQL table mapping is being created.
 
    .PARAMETER Properties
    The properties of the object for which the SQL table mapping is being created.
 
    .PARAMETER BasedOnSqlTable
    Indicates whether the mapping should be based on an existing SQL table.
 
    .EXAMPLE
    $sqlTableMapping = New-SqlTableMapping -Object $myObject -Properties $myProperties
 
    Creates a new SQL table mapping for the object $myObject using the properties $myProperties.
 
    .EXAMPLE
    $sqlTableMapping = New-SqlTableMapping -SqlTableMapping $existingMapping -Object $myObject -Properties $myProperties -BasedOnSqlTable
 
    Updates the existing SQL table mapping $existingMapping based on the object $myObject and its properties $myProperties.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $SqlTableMapping,
        [Object] $Object,
        $Properties,
        [switch] $BasedOnSqlTable
    )
    if ($SqlTableMapping) {
        $TableMapping = $SqlTableMapping
    }
    else {
        $TableMapping = @{}
        if ($BasedOnSqlTable) {
            foreach ($Property in $Properties) {
                $FieldName = $Property
                $FieldNameSql = $Property
                $TableMapping.$FieldName = $FieldNameSQL
            }
        }
        else {

            foreach ($O in $Properties.HighestObject) {

                foreach ($Property in $Properties.Properties) {

                    $FieldName = $Property
                    $FieldValue = $O.$Property

                    $FieldNameSQL = $FieldName.Replace(' ', '') 

                    if ($FieldValue -is [DateTime]) {
                        $TableMapping.$FieldName = "$FieldNameSQL,[datetime],null"
                    }
                    elseif ($FieldValue -is [int] -or $FieldValue -is [Int64]) {
                        $TableMapping.$FieldName = "$FieldNameSQL,[bigint]"
                    }
                    elseif ($FieldValue -is [bool]) {
                        $TableMapping.$FieldName = "$FieldNameSQL,[bit]"
                    }
                    else {
                        $TableMapping.$FieldName = "$FieldNameSQL"
                    }
                }
            }
        }
    }

    return $TableMapping
}
function Send-SqlInsert {
    <#
    .SYNOPSIS
    Send data to a SQL table with optional table creation and alteration capabilities.
 
    .DESCRIPTION
    This function sends data to a specified SQL table. It provides options for table creation and alteration based on the provided settings.
 
    .PARAMETER Object
    Array of objects to be inserted into the SQL table.
 
    .PARAMETER SqlSettings
    Dictionary containing SQL server, database, and table information along with optional settings for table operations.
 
    .EXAMPLE
    Send-SqlInsert -Object $DataArray -SqlSettings $SqlConfig
 
    .NOTES
    General notes
    #>

    [CmdletBinding()]
    param(
        [Array] $Object,
        [System.Collections.IDictionary] $SqlSettings
    )
    if ($SqlSettings.SqlTableTranspose) {
        $Object = Format-TransposeTable -Object $Object
    }
    $SqlTable = Get-SqlQueryColumnInformation -SqlServer $SqlSettings.SqlServer -SqlDatabase $SqlSettings.SqlDatabase -Table $SqlSettings.SqlTable
    $PropertiesFromAllObject = Get-ObjectPropertiesAdvanced -Object $Object -AddProperties 'AddedWhen', 'AddedWho'
    $PropertiesFromTable = $SqlTable.Column_name

    if ($null -eq $SqlTable) {
        if ($SqlSettings.SqlTableCreate) {
            Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, table creation is allowed, mapping will be done either on properties from object or from TableMapping defined in config"
            $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            $CreateTableSQL = New-SqlQueryCreateTable -SqlSettings $SqlSettings -TableMapping $TableMapping
        }
        else {
            Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, no table creation is allowed. Terminating"
            return "Error occured: SQL Table doesn't exists. SqlTableCreate option is disabled"
        }
    }
    else {
        if ($SqlSettings.SqlTableAlterIfNeeded) {
            if ( $SqlSettings.SqlTableMapping) {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, but SqlTableMapping is already defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            }
            else {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, and SqlTableMapping is not defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
                $AlterTableSQL = New-SqlQueryAlterTable -SqlSettings $SqlSettings -TableMapping $TableMapping -ExistingColumns $SqlTable.Column_name
            }
        }
        else {
            if ( $SqlSettings.SqlTableMapping) {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is already defined"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject
            }
            else {
                Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is not defined, using SqlTable Columns"
                $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromTable -BasedOnSqlTable
            }
        }
    }
    $Queries = @(
        if ($CreateTableSQL) {
            foreach ($Sql in $CreateTableSQL) {
                $Sql
            }
        }
        if ($AlterTableSQL) {
            foreach ($Sql in $AlterTableSQL) {
                $Sql
            }
        }
        $SqlQueries = New-SqlQuery -Object $Object -SqlSettings $SqlSettings -TableMapping $TableMapping
        foreach ($Sql in $SqlQueries) {
            $Sql
        }
    )

    $ReturnData = foreach ($Query in $Queries) {
        try {
            if ($Query) {
                $Query 
                Invoke-DbaQuery -SqlInstance "$($SqlSettings.SqlServer)" -Database "$($SqlSettings.SqlDatabase)" -Query $Query -ErrorAction Stop 
            }
        }
        catch {
            $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " "
            "Error occured (Send-SqlInsert): $ErrorMessage" 
        }
    }
    return $ReturnData
}
function Find-TypesNeeded {
    <#
    .SYNOPSIS
    Finds if specific types are needed among the required types.
 
    .DESCRIPTION
    This function checks if any of the specified types in $TypesNeeded are among the required types in $TypesRequired. Returns $true if any type is found, otherwise $false.
 
    .PARAMETER TypesRequired
    Specifies an array of types that are required.
 
    .PARAMETER TypesNeeded
    Specifies an array of types to check if they are needed.
 
    .EXAMPLE
    Find-TypesNeeded -TypesRequired @('System.String', 'System.Int32') -TypesNeeded @('System.Int32')
    Checks if 'System.Int32' is among the required types 'System.String' and 'System.Int32'.
 
    .EXAMPLE
    Find-TypesNeeded -TypesRequired @('System.Management.Automation.PSCredential', 'System.Net.IPAddress') -TypesNeeded @('System.Net.IPAddress')
    Checks if 'System.Net.IPAddress' is needed among the required types 'System.Management.Automation.PSCredential' and 'System.Net.IPAddress'.
    #>

    [CmdletBinding()]
    param (
        [Array] $TypesRequired,
        [Array] $TypesNeeded
    )
    [bool] $Found = $False
    foreach ($Type in $TypesNeeded) {
        if ($TypesRequired -contains $Type) {
            $Found = $true
            break
        }
    }
    return $Found
}
Function Get-ModulesAvailability {
    <#
    .SYNOPSIS
    Checks the availability of a specified module and imports it if available.
 
    .DESCRIPTION
    This function checks if a specified module is available. If the module is not loaded, it attempts to import it. Returns $true if the module is successfully loaded, otherwise $false.
 
    .PARAMETER Name
    Specifies the name of the module to check and potentially import.
 
    .EXAMPLE
    Get-ModulesAvailability -Name "AzureRM"
    Checks if the "AzureRM" module is available and imports it if not already loaded.
 
    .EXAMPLE
    Get-ModulesAvailability -Name "ActiveDirectory"
    Checks the availability of the "ActiveDirectory" module and imports it if necessary.
 
    #>

    [cmdletBinding()]
    param(
        [string]$Name
    )
    if (-not(Get-Module -Name $Name)) {
        if (Get-Module -ListAvailable | Where-Object { $_.Name -eq $Name }) {
            try {
                Import-Module -Name $Name
                return $true
            }
            catch {
                return $false
            }
        }
        else {

            return $false
        }
    }
    else {
        return $true
    } 
}
function Search-Command {
    <#
    .SYNOPSIS
    Searches for a specific command by name.
 
    .DESCRIPTION
    This function checks if a command with the specified name exists in the current session.
 
    .PARAMETER CommandName
    Specifies the name of the command to search for.
 
    .EXAMPLE
    Search-Command -CommandName "Get-Process"
    Returns $true if the command "Get-Process" exists, otherwise $false.
 
    .EXAMPLE
    Search-Command -CommandName "UnknownCommand"
    Returns $false as "UnknownCommand" does not exist as a command.
 
    #>

    [cmdletbinding()]
    param (
        [string] $CommandName
    )
    return [bool](Get-Command -Name $CommandName -ErrorAction SilentlyContinue)
}
function Test-AvailabilityCommands {
    <#
    .SYNOPSIS
    Tests the availability of specified commands.
 
    .DESCRIPTION
    The Test-AvailabilityCommands function checks whether the specified commands are available in the current environment.
 
    .PARAMETER Commands
    Specifies an array of command names to test for availability.
 
    .EXAMPLE
    Test-AvailabilityCommands -Commands "Get-Process", "Get-Service"
    This example tests the availability of the "Get-Process" and "Get-Service" commands.
 
    .EXAMPLE
    Test-AvailabilityCommands -Commands "Get-Command", "Get-Help"
    This example tests the availability of the "Get-Command" and "Get-Help" commands.
 
    #>

    [cmdletBinding()]
    param (
        [string[]] $Commands
    )
    $CommandsStatus = foreach ($Command in $Commands) {
        $Exists = Get-Command -Name $Command -ErrorAction SilentlyContinue
        if ($Exists) {
            Write-Verbose "Test-AvailabilityCommands - Command $Command is available."
        }
        else {
            Write-Verbose "Test-AvailabilityCommands - Command $Command is not available."
        }
        $Exists
    }
    return $CommandsStatus
}
function Test-ComputerAvailability {
    <#
    .SYNOPSIS
    Tests the availability of specified servers using various methods.
 
    .DESCRIPTION
    This function tests the availability of specified servers by performing ping, WinRM, and port open tests. It provides detailed information about the availability status of each server.
 
    .PARAMETER Servers
    Specifies an array of server names to test.
 
    .PARAMETER Test
    Specifies the type of tests to perform. Valid values are 'All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen'. Default is 'All'.
 
    .PARAMETER Ports
    Specifies an array of TCP ports to test for port open. Default is 135.
 
    .PARAMETER PortsTimeout
    Specifies the timeout value (in milliseconds) for testing port open. Default is 100.
 
    .PARAMETER PingCount
    Specifies the number of ping attempts to make. Default is 1.
 
    .EXAMPLE
    Test-ComputerAvailability -Servers "Server1", "Server2" -Test Ping+WinRM
    Tests the availability of Server1 and Server2 using both ping and WinRM methods.
 
    .EXAMPLE
    Test-ComputerAvailability -Servers "Server3" -Test PortOpen -Ports 80,443 -PortsTimeout 200
    Tests the availability of Server3 by checking if ports 80 and 443 are open within a timeout of 200 milliseconds.
 
    #>

    [CmdletBinding()]
    param(
        [string[]] $Servers,
        [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All',
        [int[]] $Ports = 135,
        [int] $PortsTimeout = 100,
        [int] $PingCount = 1
    )
    $OutputList = @(
        foreach ($Server in $Servers) {
            $Output = [ordered] @{ }
            $Output.ServerName = $Server
            if ($Test -eq 'All' -or $Test -like 'Ping*') {
                $Output.Pingable = Test-Connection -ComputerName $Server -Quiet -Count $PingCount
            }
            if ($Test -eq 'All' -or $Test -like '*WinRM*') {
                $Output.WinRM = (Test-WinRM -ComputerName $Server).Status
            }
            if ($Test -eq 'All' -or '*PortOpen*') {
                $Output.PortOpen = (Test-ComputerPort -Server $Server -PortTCP $Ports -Timeout $PortsTimeout).Status
            }
            [PSCustomObject] $Output
        }
    )
    return $OutputList
}

function Test-ComputerPort {
    <#
    .SYNOPSIS
    Tests the connectivity of a computer on specified TCP and UDP ports.
 
    .DESCRIPTION
    The Test-ComputerPort function tests the connectivity of a computer on specified TCP and UDP ports. It checks if the specified ports are open and reachable on the target computer.
 
    .PARAMETER ComputerName
    Specifies the name of the computer to test the port connectivity.
 
    .PARAMETER PortTCP
    Specifies an array of TCP ports to test connectivity.
 
    .PARAMETER PortUDP
    Specifies an array of UDP ports to test connectivity.
 
    .PARAMETER Timeout
    Specifies the timeout value in milliseconds for the connection test. Default is 5000 milliseconds.
 
    .EXAMPLE
    Test-ComputerPort -ComputerName "Server01" -PortTCP 80,443 -PortUDP 53 -Timeout 3000
    Tests the connectivity of Server01 on TCP ports 80 and 443, UDP port 53 with a timeout of 3000 milliseconds.
 
    .EXAMPLE
    Test-ComputerPort -ComputerName "Server02" -PortTCP 3389 -PortUDP 123
    Tests the connectivity of Server02 on TCP port 3389, UDP port 123 with the default timeout of 5000 milliseconds.
    #>

    [CmdletBinding()]
    param (
        [alias('Server')][string[]] $ComputerName,
        [int[]] $PortTCP,
        [int[]] $PortUDP,
        [int]$Timeout = 5000
    )
    begin {
        if ($Global:ProgressPreference -ne 'SilentlyContinue') {
            $TemporaryProgress = $Global:ProgressPreference
            $Global:ProgressPreference = 'SilentlyContinue'
        }
    }
    process {
        foreach ($Computer in $ComputerName) {
            foreach ($P in $PortTCP) {
                $Output = [ordered] @{
                    'ComputerName' = $Computer
                    'Port'         = $P
                    'Protocol'     = 'TCP'
                    'Status'       = $null
                    'Summary'      = $null
                    'Response'     = $null
                }

                $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue
                if ($TcpClient.TcpTestSucceeded) {
                    $Output['Status'] = $TcpClient.TcpTestSucceeded
                    $Output['Summary'] = "TCP $P Successful"
                }
                else {
                    $Output['Status'] = $false
                    $Output['Summary'] = "TCP $P Failed"
                    $Output['Response'] = $Warnings
                }
                [PSCustomObject]$Output
            }
            foreach ($P in $PortUDP) {
                $Output = [ordered] @{
                    'ComputerName' = $Computer
                    'Port'         = $P
                    'Protocol'     = 'UDP'
                    'Status'       = $null
                    'Summary'      = $null
                }
                $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P)
                $UdpClient.Client.ReceiveTimeout = $Timeout

                $Encoding = [System.Text.ASCIIEncoding]::new()
                $byte = $Encoding.GetBytes("Evotec")
                [void]$UdpClient.Send($byte, $byte.length)
                $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0)
                try {
                    $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint)
                    [string]$Data = $Encoding.GetString($Bytes)
                    If ($Data) {
                        $Output['Status'] = $true
                        $Output['Summary'] = "UDP $P Successful"
                        $Output['Response'] = $Data
                    }
                }
                catch {
                    $Output['Status'] = $false
                    $Output['Summary'] = "UDP $P Failed"
                    $Output['Response'] = $_.Exception.Message
                }
                $UdpClient.Close()
                $UdpClient.Dispose()
                [PSCustomObject]$Output
            }
        }
    }
    end {

        if ($TemporaryProgress) {
            $Global:ProgressPreference = $TemporaryProgress
        }
    }
}

function Test-ConfigurationCredentials {
    <#
    .SYNOPSIS
    Tests the configuration credentials for any null or empty values.
 
    .DESCRIPTION
    This function tests the configuration credentials provided to ensure that no keys have null or empty values.
 
    .PARAMETER Configuration
    The configuration object containing the credentials to be tested.
 
    .PARAMETER AllowEmptyKeys
    Specifies whether empty keys are allowed to be present in the configuration.
 
    .EXAMPLE
    Test-ConfigurationCredentials -Configuration $Config -AllowEmptyKeys $true
    Tests the configuration credentials in $Config allowing empty keys.
 
    .EXAMPLE
    Test-ConfigurationCredentials -Configuration $Config -AllowEmptyKeys $false
    Tests the configuration credentials in $Config without allowing empty keys.
    #>

    [CmdletBinding()]
    param (
        [Object] $Configuration,
        $AllowEmptyKeys
    )
    $Object = foreach ($Key in $Configuration.Keys) {
        if ($AllowEmptyKeys -notcontains $Key -and [string]::IsNullOrWhiteSpace($Configuration.$Key)) {
            Write-Verbose "Test-ConfigurationCredentials - Configuration $Key is Null or Empty! Terminating"
            @{ Status = $false; Output = $User.SamAccountName; Extended = "Credentials configuration $Key is Null or Empty!" }
        }
    }
    return $Object
}
function Test-ForestConnectivity {
    <#
    .SYNOPSIS
    Tests the connectivity to the Active Directory forest.
 
    .DESCRIPTION
    This function tests the connectivity to the Active Directory forest by attempting to retrieve the forest information.
 
    .EXAMPLE
    Test-ForestConnectivity
    Tests the connectivity to the Active Directory forest.
 
    #>

    [CmdletBinding()]
    param(

    )
    Try {
        $null = Get-ADForest
        return $true
    }
    catch {

        return $False
    }
}
function Test-Key {
    <#
    .SYNOPSIS
    Checks if a specific key exists in a configuration table.
 
    .DESCRIPTION
    The Test-Key function checks if a specified key exists in a given configuration table. It returns true if the key exists, and false otherwise.
 
    .PARAMETER ConfigurationTable
    The configuration table to search for the key.
 
    .PARAMETER ConfigurationSection
    The section within the configuration table where the key is located.
 
    .PARAMETER ConfigurationKey
    The key to check for existence in the configuration table.
 
    .PARAMETER DisplayProgress
    Specifies whether to display progress messages.
 
    .EXAMPLE
    Test-Key -ConfigurationTable $configTable -ConfigurationSection "Section1" -ConfigurationKey "Key1" -DisplayProgress $true
    Checks if the key "Key1" exists in the "Section1" of the $configTable and displays a progress message.
 
    .EXAMPLE
    Test-Key -ConfigurationTable $configTable -ConfigurationKey "Key2"
    Checks if the key "Key2" exists in the $configTable without displaying progress messages.
    #>

    [CmdletBinding()]
    param(
        $ConfigurationTable,
        $ConfigurationSection = "",
        $ConfigurationKey,
        $DisplayProgress = $false
    )
    if ($null -eq $ConfigurationTable) {
        return $false 
    }
    try {
        $value = $ConfigurationTable.ContainsKey($ConfigurationKey)
    }
    catch {
        $value = $false
    }
    if ($value -eq $true) {
        if ($DisplayProgress -eq $true) {
            Write-Color @script:WriteParameters -Text "[i] ", "Parameter in configuration of ", "$ConfigurationSection.$ConfigurationKey", " exists." -Color White, White, Green, White
        }
        return $true
    }
    else {
        if ($DisplayProgress -eq $true) {
            Write-Color @script:WriteParameters -Text "[i] ", "Parameter in configuration of ", "$ConfigurationSection.$ConfigurationKey", " doesn't exist." -Color White, White, Red, White
        }
        return $false
    }
}
function Test-ModuleAvailability {
    <#
    .SYNOPSIS
    Tests the availability of required modules.
 
    .DESCRIPTION
    This function checks if the required modules are available for use.
 
    .EXAMPLE
    Test-ModuleAvailability
    Checks if the 'Get-AdForest' module is available.
 
    #>

    [CmdletBinding()]
    param(

    )
    if (Search-Command -CommandName 'Get-AdForest') {
    }
    else {
        Write-Warning 'Modules required to run not found.'
        Exit
    }
}
function Test-WinRM {
    <#
    .SYNOPSIS
    Tests the WinRM connectivity on the specified computers.
 
    .DESCRIPTION
    The Test-WinRM function tests the WinRM connectivity on the specified computers and returns the status of the connection.
 
    .PARAMETER ComputerName
    Specifies the names of the computers to test WinRM connectivity on.
 
    .EXAMPLE
    Test-WinRM -ComputerName "Server01", "Server02"
    Tests the WinRM connectivity on Server01 and Server02.
 
    .EXAMPLE
    Test-WinRM -ComputerName "Server03"
    Tests the WinRM connectivity on Server03.
 
    #>

    [CmdletBinding()]
    param (
        [alias('Server')][string[]] $ComputerName
    )
    $Output = foreach ($Computer in $ComputerName) {
        $Test = [PSCustomObject] @{
            Output       = $null
            Status       = $null
            ComputerName = $Computer
        }
        try {
            $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop
            $Test.Status = $true
        }
        catch {
            $Test.Status = $false
        }
        $Test
    }
    $Output
}

function Get-TimeSettings {
    <#
    .SYNOPSIS
    Retrieves and displays time synchronization settings for the specified computer(s).
 
    .DESCRIPTION
    The Get-TimeSettings function retrieves and displays time synchronization settings for the specified computer(s). It provides information on the type of time synchronization mechanism being used, NTP server flags, cross-site synchronization flags, and announce flags.
 
    .PARAMETER ComputerName
    Specifies the computer(s) for which time synchronization settings are to be retrieved. If not specified, the local computer name is used.
 
    .PARAMETER Formatted
    Switch parameter to format the output in a structured manner.
 
    .PARAMETER Splitter
    Specifies the character used to split the output if Formatted parameter is used.
 
    .EXAMPLE
    Get-TimeSettings -ComputerName 'Server01'
    Retrieves time synchronization settings for a single computer named 'Server01'.
 
    .EXAMPLE
    Get-TimeSettings -ComputerName 'Server01','Server02' -Formatted -Splitter ','
    Retrieves time synchronization settings for multiple computers named 'Server01' and 'Server02' in a formatted output separated by commas.
 
    #>

    [alias('Get-TimeSynchronization')]
    param(
        [string[]] $ComputerName,
        # [string] $Domain,
        [switch] $Formatted,
        [string] $Splitter
    )

    $Types = @{
        NT5DS   = 'The time service synchronizes from the domain hierarchy.' 
        NTP     = 'The time service synchronizes from the servers specified in the NtpServer registry entry.'  
        ALLSync = 'The time service uses all the available synchronization mechanisms.'
        NoSync  = 'The time service does not synchronize with other sources.'
    }

    [flags()]
    enum NtpServerFlags {
        None = 0
        SpecialInterval = 0x1 
        UseAsFallbackOnly = 0x2 
        SymmetricActive = 0x4 
        Client = 0x8 
    }

    $CrossSiteSyncFlags = @{
        '0' = 'None'
        '1' = 'PdcOnly'
        '2' = 'All'
    }

    $AnnounceFlags = @{
        '0'  = 'Not a time server'
        '1'  = 'Always time server'
        '2'  = 'Automatic time server'
        '4'  = 'Always reliable time server'
        '8'  = 'Automatic reliable time server'
        '10' = 'The default value for domain members is 10. The default value for stand-alone clients and servers is 10.'
    }

    if ($null -eq $ComputerName) {
        $ComputerName = $env:COMPUTERNAME
    }
    foreach ($_ in $ComputerName) {
        [bool] $AppliedGPO = $false
        $TimeParameters = Get-PSRegistry -ComputerName $_ -RegistryPath "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32time\Parameters"
        if ($null -eq $TimeParameters.NtpServer) {
            $TimeParameters = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters"
            $AppliedGPO = $true
        }

        $TimeConfig = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config"

        $TimeNTPClient = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SOFTWARE\Policies\Microsoft\W32time\TimeProviders\NtpClient"
        if ($null -eq $TimeNTPClient.CrossSiteSyncFlags) {
            $TimeNTPClient = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NTPClient"
        }
        $TimeNTPServer = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NTPServer"

        $TimeVMProvider = Get-PSRegistry -ComputerName $_ -RegistryPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider"

        $SecureTimeSeeding = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" -Key 'UtilizeSslTimeData'

        $NtpServers = $TimeParameters.NtpServer -split ' '
        $Ntp = foreach ($NtpServer in $NtpServers) {
            $SplitNTP = $NtpServer -split ','

            if ($SplitNTP.Count -eq 2) {

                if ($flagVal = $SplitNTP[1] -as [int]) {

                    if ($flags = $flagVal -as [NtpServerFlags]) {
                        $Intervals = $flags.ToString().Replace(', ', '+')
                    }
                    else {
                        Write-Warning -Message "Get-TimeSettings - NtpServer flag value `"$flagVal`" could not be converted to NtpServerFlags enum"
                        $Intervals = 'Incorrect'
                    }
                }
                else {
                    Write-Warning -Message "Get-TimeSettings - NtpServer flag value `"$($SplitNTP[1])`" could not be parsed as an integer"
                    $Intervals = 'Incorrect'
                }
            }
            else {
                $Intervals = 'Missing'
            }

            [PSCustomObject] @{
                NtpServer = $SplitNTP[0]
                Intervals = $Intervals
            }
        }

        if ($null -eq $TimeConfig.UtilizeSslTimeData) {
            $WSTSType = $true
        }
        elseif ($TimeConfig.UtilizeSslTimeData -eq 0) {
            $WSTSType = $false
        }
        else {
            $WSTSType = $true
        }

        if ($null -eq $SecureTimeSeeding.PSType) {
            $WSTSStatus = $false 
            $WSTSType = $true
        }
        elseif ($SecureTimeSeeding.PSType -eq 'DWord' -and $SecureTimeSeeding.PSValue -eq 0) {
            $WSTSStatus = $false 
            $WSTSType = $true
        }
        elseif ($SecureTimeSeeding.PSType -eq 'DWord' -and $SecureTimeSeeding.PSValue -eq 1) {
            $WSTSStatus = $true 
            $WSTSType = $true
        }
        else {
            $WSTSStatus = $true 
            $WSTSType = $false
        }

        [PSCustomObject] @{
            ComputerName                        = $_

            NtpServer                           = if ($Splitter) {
                $Ntp.NtpServer -join $Splitter 
            }
            else {
                $Ntp.NtpServer 
            }
            NtpServerIntervals                  = if ($Splitter) {
                $Ntp.Intervals -join $Splitter 
            }
            else {
                $Ntp.Intervals 
            }
            NtpType                             = $TimeParameters.Type
            NtpTypeComment                      = $Types["$($TimeParameters.Type)"]
            AppliedGPO                          = $AppliedGPO
            VMTimeProvider                      = [bool] $TimeVMProvider.Enabled

            WindowsSecureTimeSeeding            = $WSTSStatus
            WindowsSecureTimeSeedingTypeCorrect = $WSTSType
            AnnounceFlags                       = $TimeConfig.AnnounceFlags
            AnnounceFlagsComment                = $AnnounceFlags["$($TimeConfig.AnnounceFlags)"]
            NtpServerEnabled                    = [bool]$TimeNTPServer.Enabled
            NtpServerInputProvider              = [bool]$TimeNTPServer.InputProvider
            MaxPosPhaseCorrection               = $TimeConfig.MaxPosPhaseCorrection
            MaxnegPhaseCorrection               = $TimeConfig.MaxnegPhaseCorrection
            MaxAllowedPhaseOffset               = $TimeConfig.MaxAllowedPhaseOffset
            MaxPollInterval                     = $TimeConfig.MaxPollInterval
            MinPollInterval                     = $TimeConfig.MinPollInterval
            UpdateInterval                      = $TimeConfig.UpdateInterval
            ResolvePeerBackoffMinutes           = $TimeNTPClient.ResolvePeerBackoffMinutes
            ResolvePeerBackoffMaxTimes          = $TimeNTPClient.ResolvePeerBackoffMaxTimes
            SpecialPollInterval                 = $TimeNTPClient.SpecialPollInterval
            EventLogFlags                       = $TimeConfig.EventLogFlags
            NtpClientEnabled                    = [bool] $TimeNTPClient.Enabled
            NtpClientCrossSiteSyncFlags         = $CrossSiteSyncFlags["$($TimeNTPClient.CrossSiteSyncFlags)"]
            NtpClientInputProvider              = [bool] $TimeNTPClient.InputProvider
            TimeNTPClient                       = $TimeNTPClient.SpecialPollInterval
        }
    }
}

function Get-TimeZoneAdvanced {
    <#
    .SYNOPSIS
    Retrieves the time zone information for the specified computer(s).
 
    .DESCRIPTION
    This function retrieves the time zone information for the specified computer(s) including the computer name, time zone caption, and current local time.
 
    .PARAMETER ComputerName
    Specifies the name(s) of the computer(s) to retrieve the time zone information from. Default is the local computer.
 
    .PARAMETER Credential
    Specifies the credentials to use for accessing remote computers.
 
    .EXAMPLE
    Get-TimeZoneAdvanced
    # Retrieves time zone information for the local computer.
 
    .EXAMPLE
    Get-TimeZoneAdvanced -ComputerName "Server01", "Server02" -Credential $cred
    # Retrieves time zone information for Server01 and Server02 using specified credentials.
 
    #>

    param(
        [string[]]$ComputerName = $Env:COMPUTERNAME,
        [System.Management.Automation.PSCredential] $Credential = [System.Management.Automation.PSCredential]::Empty
    )
    foreach ($computer in $computerName) {
        $TimeZone = Get-WmiObject -Class win32_timezone -ComputerName $computer -Credential $Credential
        $LocalTime = Get-WmiObject -Class win32_localtime -ComputerName $computer -Credential $Credential
        $Output = @{
            'ComputerName' = $localTime.__SERVER;
            'TimeZone'     = $timeZone.Caption;
            'CurrentTime'  = (Get-Date -Day $localTime.Day -Month $localTime.Month);
        }
        $Object = New-Object -TypeName PSObject -Property $Output
        Write-Output $Object
    }
}
function Get-TimeZoneLegacy () {
    <#
    .SYNOPSIS
    Retrieves the standard name of the current time zone.
 
    .DESCRIPTION
    The Get-TimeZoneLegacy function retrieves the standard name of the current time zone using the legacy method.
 
    .EXAMPLE
    Get-TimeZoneLegacy
    # Output: "Pacific Standard Time"
 
    #>

    return ([System.TimeZone]::CurrentTimeZone).StandardName
}

function Measure-Collection {
    <#
    .SYNOPSIS
    Measures the execution time of a script block and outputs the duration.
 
    .DESCRIPTION
    This function measures the time taken to execute a given script block and outputs the duration in days, hours, minutes, seconds, milliseconds, and ticks.
 
    .PARAMETER Name
    Specifies the name of the measurement.
 
    .PARAMETER ScriptBlock
    Specifies the script block to be executed and measured.
 
    .EXAMPLE
    Measure-Collection -Name "Example" -ScriptBlock { Start-Sleep -Seconds 5 }
    # Outputs: Name: Example, 0 days, 0 hours, 0 minutes, 5 seconds, 0 milliseconds, ticks 5000000
 
    .EXAMPLE
    Measure-Collection -Name "Another Example" -ScriptBlock { Get-Process }
    # Outputs: Name: Another Example, 0 days, 0 hours, 0 minutes, X seconds, Y milliseconds, ticks Z
 
    #>

    param(
        [string] $Name,
        [ScriptBlock] $ScriptBlock
    )
    $Time = [System.Diagnostics.Stopwatch]::StartNew()
    Invoke-Command -ScriptBlock $ScriptBlock
    $Time.Stop()
    "Name: $Name, $($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds, ticks $($Time.Elapsed.Ticks)"
}
function Set-TimeSynchronization {
    <#
    .SYNOPSIS
    Configures time synchronization settings on the local machine.
 
    .DESCRIPTION
    This function sets up time synchronization on the local machine by configuring the time source, server type, NTP settings, and time correction parameters.
 
    .PARAMETER TimeSource
    Specifies the time source to synchronize with. Default is 'time.windows.com'.
 
    .PARAMETER MaxPosPhaseCorrection
    Specifies the maximum positive time correction in seconds. Default is 86400 seconds (24 hours).
 
    .PARAMETER MaxnegPhaseCorrection
    Specifies the maximum negative time correction in seconds. Default is 86400 seconds (24 hours).
 
    .PARAMETER PollInterval
    Specifies the poll interval in seconds. Default is 1800 seconds (30 minutes).
 
    .EXAMPLE
    Set-TimeSynchronization -TimeSource 'time.windows.com' -MaxPosPhaseCorrection 86400 -MaxnegPhaseCorrection 86400 -PollInterval 1800
    Configures time synchronization using default settings.
 
    .EXAMPLE
    Set-TimeSynchronization -TimeSource 'pool.ntp.org' -MaxPosPhaseCorrection 43200 -MaxnegPhaseCorrection 43200 -PollInterval 3600
    Configures time synchronization with a different time source and shorter time correction limits.
 
    #>

    param(
        [string[]] $TimeSource = 'time.windows.com',
        [int] $MaxPosPhaseCorrection = 86400,
        [int] $MaxnegPhaseCorrection = 86400,
        [int] $PollInterval = 1800
    )

    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters -Name Type -Value 'NTP'
    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name AnnounceFlags -Value 5

    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer -Name Enabled -Value 1

    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters -Name NtpServer -Value "$TimeSource,0x1"

    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient -Name SpecialPollInterval -Value $PollInterval

    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name MaxPosPhaseCorrection -Value $MaxPosPhaseCorrection
    Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name MaxnegPhaseCorrection -Value $MaxnegPhaseCorrection

    Stop-Service -Name W32Time
    Start-Service -Name W32Time
}
function Start-TimeLog {
    <#
    .SYNOPSIS
    Starts a new stopwatch for logging time.
 
    .DESCRIPTION
    This function starts a new stopwatch that can be used for logging time durations.
 
    .EXAMPLE
    Start-TimeLog
    Starts a new stopwatch for logging time.
 
    #>

    [CmdletBinding()]
    param()
    [System.Diagnostics.Stopwatch]::StartNew()
}
function Stop-TimeLog {
    <#
    .SYNOPSIS
    Stops the stopwatch and returns the elapsed time in a specified format.
 
    .DESCRIPTION
    The Stop-TimeLog function stops the provided stopwatch and returns the elapsed time in a specified format. The function can output the elapsed time as a single string or an array of days, hours, minutes, seconds, and milliseconds.
 
    .PARAMETER Time
    Specifies the stopwatch object to stop and retrieve the elapsed time from.
 
    .PARAMETER Option
    Specifies the format in which the elapsed time should be returned. Valid values are 'OneLiner' (default) or 'Array'.
 
    .PARAMETER Continue
    Indicates whether the stopwatch should continue running after retrieving the elapsed time.
 
    .EXAMPLE
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    # Perform some operations
    Stop-TimeLog -Time $stopwatch
    # Output: "0 days, 0 hours, 0 minutes, 5 seconds, 123 milliseconds"
 
    .EXAMPLE
    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    # Perform some operations
    Stop-TimeLog -Time $stopwatch -Option Array
    # Output: ["0 days", "0 hours", "0 minutes", "5 seconds", "123 milliseconds"]
    #>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time,
        [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner',
        [switch] $Continue
    )
    Begin {
    }
    Process {
        if ($Option -eq 'Array') {
            $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds"
        }
        else {
            $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds"
        }
    }
    End {
        if (-not $Continue) {
            $Time.Stop()
        }
        return $TimeToExecute
    }
}
function Show-Array {
    <#
    .SYNOPSIS
    Displays the elements of an ArrayList with optional type information.
 
    .DESCRIPTION
    The Show-Array function displays each element of the provided ArrayList. Optionally, it can also show the type of each element.
 
    .PARAMETER List
    Specifies the ArrayList containing the elements to display.
 
    .PARAMETER WithType
    Switch parameter to include type information along with each element.
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    $myList.Add("Apple")
    $myList.Add(42)
    Show-Array -List $myList
    # Output:
    # Apple
    # 42
 
    .EXAMPLE
    $myList = New-Object System.Collections.ArrayList
    $myList.Add("Banana")
    $myList.Add(3.14)
    Show-Array -List $myList -WithType
    # Output:
    # Banana (Type: String)
    # 3.14 (Type: Double)
    #>

    [CmdletBinding()]
    param(
        [System.Collections.ArrayList] $List,
        [switch] $WithType
    )
    foreach ($Element in $List) {
        $Type = Get-ObjectType -Object $Element
        if ($WithType) {
            Write-Output "$Element (Type: $($Type.ObjectTypeName))"
        }
        else {
            Write-Output $Element
        }
    }
}
function Show-DataInVerbose {
    <#
    .SYNOPSIS
    Displays the properties of an object in a verbose manner.
 
    .DESCRIPTION
    This function takes an object as input and displays each property of the object in a verbose format.
 
    .PARAMETER Object
    Specifies the object whose properties will be displayed.
 
    .EXAMPLE
    $data = [PSCustomObject]@{
        Name = "John Doe"
        Age = 30
        City = "New York"
    }
    Show-DataInVerbose -Object $data
 
    Description:
    Displays the properties of the $data object in a verbose manner.
 
    #>

    [CmdletBinding()]
    param(
        [Object] $Object
    )
    foreach ($O in $Object) {
        foreach ($E in $O.PSObject.Properties) {
            $FieldName = $E.Name
            $FieldValue = $E.Value
            Write-Verbose "Display-DataInVerbose - FieldName: $FieldName FieldValue: $FieldValue"
        }
    }
}

function Show-TableVisualization {
    <#
    .SYNOPSIS
    Displays a table visualization of the input object.
 
    .DESCRIPTION
    The Show-TableVisualization function displays a table visualization of the input object using Format-Table. It also provides additional information about the table data.
 
    .PARAMETER Object
    Specifies the input object to be visualized as a table.
 
    .EXAMPLE
    PS C:\> Get-Process | Show-TableVisualization
    Displays a table visualization of the processes retrieved by Get-Process.
 
    .EXAMPLE
    PS C:\> $data = Get-Service | Where-Object { $_.Status -eq 'Running' } | Select-Object Name, DisplayName, Status
    PS C:\> $data | Show-TableVisualization
    Displays a table visualization of the selected service data.
 
    #>

    [CmdletBinding()]
    param (
        [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] $Object
    )
    if ($Color) {
        Write-Color "[i] This is how table looks like in Format-Table" -Color Yellow 
    }
    Write-Verbose '[i] This is how table looks like in Format-Table'
    $Object | Format-Table -AutoSize
    $Data = Format-PSTable $Object 

    Write-Verbose "[i] Rows Count $($Data.Count) Column Count $($Data[0].Count)"
    $RowNr = 0
    if ($Color) {
        Write-Color "[i] Presenting table after conversion" -Color Yellow 
    }
    foreach ($Row in $Data) {
        $ColumnNr = 0
        foreach ($Column in $Row) {
            Write-Verbose "Row: $RowNr Column: $ColumnNr Data: $Column"
            $ColumnNr++
        }
        $RowNr++
    }
}
function Save-XML {
    <#
    .SYNOPSIS
    Saves an XML document to a specified file path.
 
    .DESCRIPTION
    This function saves an XML document to a specified file path using UTF-8 encoding without BOM.
 
    .PARAMETER FilePath
    Specifies the path where the XML document will be saved.
 
    .PARAMETER xml
    Specifies the XML document to be saved.
 
    .EXAMPLE
    Save-XML -FilePath "C:\Documents\example.xml" -xml $xmlDocument
    Saves the XML document $xmlDocument to the file "example.xml" located in the "C:\Documents" directory.
 
    #>

    param (
        [string] $FilePath,
        [System.Xml.XmlNode] $xml
    )
    $utf8WithoutBom = New-Object System.Text.UTF8Encoding($false)
    $writer = New-Object System.IO.StreamWriter($FilePath, $false, $utf8WithoutBom)
    $xml.Save( $writer )
    $writer.Close()
}
function Set-XML {
    <#
    .SYNOPSIS
    Sets a specific node value in an XML file.
 
    .DESCRIPTION
    This function sets the value of a specified node in an XML file at the given path.
 
    .PARAMETER FilePath
    The path to the XML file.
 
    .PARAMETER Paths
    An array of paths to navigate through the XML structure.
 
    .PARAMETER Node
    The name of the node to set the value for.
 
    .PARAMETER Value
    The value to set for the specified node.
 
    .EXAMPLE
    Set-XML -FilePath "C:\example.xml" -Paths "Root", "Child" -Node "Value" -Value "NewValue"
    Sets the value of the "Value" node under "Root/Child" path in the XML file to "NewValue".
 
    .NOTES
    File encoding is assumed to be UTF-8.
 
    #>

    param (
        [string] $FilePath,
        [string[]]$Paths,
        [string] $Node,
        [string] $Value
    )
    [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8
    $XmlElement = $xmlDocument
    foreach ($Path in $Paths) {
        $XmlElement = $XmlElement.$Path
    }
    $XmlElement.$Node = $Value
    $xmlDocument.Save($FilePath)
}
function Get-ProtocolDefaults {
    <#
    .SYNOPSIS
    Gets a list of default settings for SSL/TLS protocols
 
    .DESCRIPTION
    Gets a list of default settings for SSL/TLS protocols
 
    .PARAMETER WindowsVersion
    Windows Version to search for
 
    .PARAMETER AsList
    If true, returns a list of protocol names for all Windows Versions, otherwise returns a single entry for the specified Windows Version
 
    .EXAMPLE
    Get-ProtocolDefaults -AsList | Format-Table
 
    .EXAMPLE
    Get-ProtocolDefaults -WindowsVersion 'Windows 10 1809' | Format-Table
 
    .NOTES
    Based on: https://docs.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp-
 
    According to this https://github.com/MicrosoftDocs/windowsserverdocs/issues/2783 SCHANNEL service requires direct enablement so the list is kind of half useful
    #>

    [cmdletbinding(DefaultParameterSetName = 'WindowsVersion')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'WindowsVersion')][string] $WindowsVersion,
        [Parameter(Mandatory, ParameterSetName = 'AsList')][switch] $AsList
    )

    $Defaults = [ordered] @{
        'Windows Server 2022'        = [ordered] @{
            'Version'     = 'Windows Server 2022'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Enabled'
            'TLS13Server' = 'Enabled'
        }
        'Windows Server 2019 20H2'   = [ordered] @{
            'Version'     = 'Windows Server 2019 20H2'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2019 2004'   = [ordered] @{
            'Version'     = 'Windows Server 2019 2004'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2019 1909'   = [ordered] @{
            'Version'     = 'Windows Server 2019 1909'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows Server 2019 1903"   = [ordered] @{
            'Version'     = 'Windows Server 2019 1903'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows Server 2019 1809"   = [ordered] @{
            'Version'     = 'Windows Server 2019 1809'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows Server 2016 1803"   = [ordered] @{
            'Version'     = 'Windows Server 2016 1803'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows Server 2016 1607"   = [ordered] @{
            'Version'     = 'Windows Server 2019 1607'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2012 R2'     = [ordered] @{
            'Version'     = 'Windows Server 2012 R2'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Disabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2012'        = [ordered] @{
            'Version'     = 'Windows Server 2012'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Disabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2008 R2'     = [ordered] @{
            'Version'     = 'Windows Server 2008 R2'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Enabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Disabled'
            'TLS11Server' = 'Disabled'
            'TLS12Client' = 'Disabled'
            'TLS12Server' = 'Disabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows Server 2008'        = [ordered] @{
            'Version'     = 'Windows Server 2008'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Enabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Disabled'
            'TLS11Server' = 'Disabled'
            'TLS12Client' = 'Disabled'
            'TLS12Server' = 'Disabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }

        'Windows 11 21H2'            = [ordered] @{
            'Version'     = 'Windows 11 21H2'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Enabled'
            'TLS13Server' = 'Enabled'
        }
        'Windows 10 21H1'            = [ordered] @{
            'Version'     = 'Windows 10 21H1'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows 10 20H2'            = [ordered] @{
            'Version'     = 'Windows 10 20H2'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows 10 2004'            = [ordered] @{
            'Version'     = 'Windows 10 2004'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        'Windows 10 Insider Preview' = [ordered] @{
            'Version'     = 'Windows 10 Insider Preview'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1909"            = [ordered] @{
            'Version'     = 'Windows 10 1909'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1903"            = [ordered] @{
            'Version'     = 'Windows 10 1903'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1809"            = [ordered] @{
            'Version'     = 'Windows 10 1809'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1803"            = [ordered] @{
            'Version'     = 'Windows 10 1803'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1709"            = [ordered] @{
            'Version'     = 'Windows 10 1709'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1703"            = [ordered] @{
            'Version'     = 'Windows 10 1703'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1607"            = [ordered] @{
            'Version'     = 'Windows 10 1607'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Not supported'
            'SSL2Server'  = 'Not supported'
            'SSL3Client'  = 'Disabled'
            'SSL3Server'  = 'Disabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1511"            = [ordered] @{
            'Version'     = 'Windows 10 1511'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Disabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
        "Windows 10 1507"            = [ordered] @{
            'Version'     = 'Windows 10 1507'
            'PCT10'       = 'Not supported'
            'SSL2Client'  = 'Disabled'
            'SSL2Server'  = 'Disabled'
            'SSL3Client'  = 'Enabled'
            'SSL3Server'  = 'Enabled'
            'TLS10Client' = 'Enabled'
            'TLS10Server' = 'Enabled'
            'TLS11Client' = 'Enabled'
            'TLS11Server' = 'Enabled'
            'TLS12Client' = 'Enabled'
            'TLS12Server' = 'Enabled'
            'TLS13Client' = 'Not supported'
            'TLS13Server' = 'Not supported'
        }
    }
    if ($AsList) {
        foreach ($Key in $Defaults.Keys) {
            [PSCustomObject] $Defaults[$Key]
        }
    }
    else {
        if ($Defaults[$WindowsVersion]) {
            $Defaults[$WindowsVersion]
        }
        else {
            [ordered] @{
                'Version'     = 'Unknown'
                'PCT10'       = 'Unknown'
                'SSL2Client'  = 'Unknown'
                'SSL2Server'  = 'Unknown'
                'SSL3Client'  = 'Unknown'
                'SSL3Server'  = 'Unknown'
                'TLS10Client' = 'Unknown'
                'TLS10Server' = 'Unknown'
                'TLS11Client' = 'Unknown'
                'TLS11Server' = 'Unknown'
                'TLS12Client' = 'Unknown'
                'TLS12Server' = 'Unknown'
                'TLS13Client' = 'Unknown'
                'TLS13Server' = 'Unknown'
            }
        }
    }
}
Add-Type -TypeDefinition @"
public enum RGBColors {
    None,
    Black,
    Navy,
    DarkBlue,
    MediumBlue,
    Blue,
    DarkGreen,
    Green,
    Teal,
    DarkCyan,
    DeepSkyBlue,
    DarkTurquoise,
    MediumSpringGreen,
    Lime,
    SpringGreen,
    Aqua,
    Cyan,
    MidnightBlue,
    DodgerBlue,
    LightSeaGreen,
    ForestGreen,
    SeaGreen,
    DarkSlateGray,
    DarkSlateGrey,
    LimeGreen,
    MediumSeaGreen,
    Turquoise,
    RoyalBlue,
    SteelBlue,
    DarkSlateBlue,
    MediumTurquoise,
    Indigo,
    DarkOliveGreen,
    CadetBlue,
    CornflowerBlue,
    MediumAquamarine,
    DimGray,
    DimGrey,
    SlateBlue,
    OliveDrab,
    SlateGray,
    SlateGrey,
    LightSlateGray,
    LightSlateGrey,
    MediumSlateBlue,
    LawnGreen,
    Chartreuse,
    Aquamarine,
    Maroon,
    Purple,
    Olive,
    Grey,
    Gray,
    //Grey,
    SkyBlue,
    LightSkyBlue,
    BlueViolet,
    DarkRed,
    DarkMagenta,
    SaddleBrown,
    DarkSeaGreen,
    LightGreen,
    MediumPurple,
    DarkViolet,
    PaleGreen,
    DarkOrchid,
    YellowGreen,
    Sienna,
    Brown,
    DarkGray,
    DarkGrey,
    LightBlue,
    GreenYellow,
    PaleTurquoise,
    LightSteelBlue,
    PowderBlue,
    FireBrick,
    DarkGoldenrod,
    MediumOrchid,
    RosyBrown,
    DarkKhaki,
    Silver,
    MediumVioletRed,
    IndianRed,
    Peru,
    Chocolate,
    Tan,
    LightGray,
    LightGrey,
    Thistle,
    Orchid,
    Goldenrod,
    PaleVioletRed,
    Crimson,
    Gainsboro,
    Plum,
    BurlyWood,
    LightCyan,
    Lavender,
    DarkSalmon,
    Violet,
    PaleGoldenrod,
    LightCoral,
    Khaki,
    AliceBlue,
    Honeydew,
    Azure,
    SandyBrown,
    Wheat,
    Beige,
    WhiteSmoke,
    MintCream,
    GhostWhite,
    Salmon,
    AntiqueWhite,
    Linen,
    LightGoldenrodYellow,
    OldLace,
    Red,
    Fuchsia,
    Magenta,
    DeepPink,
    OrangeRed,
    Tomato,
    HotPink,
    Coral,
    DarkOrange,
    LightSalmon,
    Orange,
    LightPink,
    Pink,
    Gold,
    PeachPuff,
    NavajoWhite,
    Moccasin,
    Bisque,
    MistyRose,
    BlanchedAlmond,
    PapayaWhip,
    LavenderBlush,
    Seashell,
    Cornsilk,
    LemonChiffon,
    FloralWhite,
    Snow,
    Yellow,
    LightYellow,
    Ivory,
    White
}
"@


Export-ModuleMember -Function @('Add-ToArray', 'Add-ToArrayAdvanced', 'Add-ToHashTable', 'Add-WinADUserGroups', 'Clear-DataInformation', 'Compare-MultipleObjects', 'Compare-ObjectProperties', 'Compare-ObjectsAdvanced', 'Convert-ADGuidToSchema', 'Convert-ADSchemaToGuid', 'Convert-BinaryToHex', 'Convert-BinaryToString', 'Convert-Color', 'Convert-CountryCodeToCountry', 'Convert-CountryToContinent', 'Convert-CountryToCountryCode', 'Convert-DomainFqdnToNetBIOS', 'Convert-DomainToSid', 'Convert-ExchangeEmail', 'Convert-ExchangeItems', 'Convert-ExchangeRecipient', 'Convert-ExchangeSize', 'Convert-HexToBinary', 'Convert-Identity', 'Convert-IpAddressToPtr', 'Convert-KeyToKeyValue', 'Convert-Office365License', 'Convert-Size', 'Convert-TimeToDays', 'Convert-ToDateTime', 'Convert-ToTimeSpan', 'Convert-TwoArraysIntoOne', 'Convert-UAC', 'Convert-UserAccountControl', 'ConvertFrom-Color', 'ConvertFrom-DistinguishedName', 'ConvertFrom-ErrorRecord', 'ConvertFrom-LanguageCode', 'ConvertFrom-NetbiosName', 'ConvertFrom-ObjectToString', 'ConvertFrom-OperationType', 'ConvertFrom-ScriptBlock', 'ConvertFrom-SID', 'ConvertFrom-X500Address', 'ConvertTo-DistinguishedName', 'ConvertTo-FlatHashtable', 'ConvertTo-FlatObject', 'ConvertTo-Identity', 'ConvertTo-ImmutableID', 'ConvertTo-JsonLiteral', 'ConvertTo-NormalizedString', 'ConvertTo-OperatingSystem', 'ConvertTo-OrderedDictionary', 'ConvertTo-PrettyObject', 'ConvertTo-SID', 'Copy-Dictionary', 'Copy-DictionaryManual', 'Dismount-PSRegistryPath', 'Find-ADConnectServer', 'Find-DatesCurrentDayMinusDayX', 'Find-DatesCurrentDayMinuxDaysX', 'Find-DatesCurrentHour', 'Find-DatesDayPrevious', 'Find-DatesDayToday', 'Find-DatesMonthCurrent', 'Find-DatesMonthPast', 'Find-DatesPastHour', 'Find-DatesPastWeek', 'Find-DatesQuarterCurrent', 'Find-DatesQuarterLast', 'Find-ExchangeServer', 'Find-HyperVServer', 'Find-MyProgramData', 'Find-ServerTypes', 'Find-TypesNeeded', 'Find-UsersProxyAddressesStatus', 'Format-Dictionary', 'Format-FirstXChars', 'Format-PSTable', 'Format-Stream', 'Format-StringToSentence', 'Format-ToTitleCase', 'Format-TransposeTable', 'Format-View', 'Get-ADADministrativeGroups', 'Get-ADEncryptionTypes', 'Get-ADTrustAttributes', 'Get-CimData', 'Get-Colors', 'Get-Computer', 'Get-ComputerApplication', 'Get-ComputerBios', 'Get-ComputerCPU', 'Get-ComputerCulture', 'Get-ComputerDevice', 'Get-ComputerDisk', 'Get-ComputerDiskLogical', 'Get-ComputerFirewall', 'Get-ComputerInstalledUpdates', 'Get-ComputerMemory', 'Get-ComputerMissingDrivers', 'Get-ComputerNetFramework', 'Get-ComputerNetwork', 'Get-ComputerOemInformation', 'Get-ComputerOperatingSystem', 'Get-ComputerRAM', 'Get-ComputerRDP', 'Get-ComputerRoles', 'Get-ComputerService', 'Get-ComputerSMB', 'Get-ComputerSMBShare', 'Get-ComputerSMBShareList', 'Get-ComputerSMBSharePermissions', 'Get-ComputerStartup', 'Get-ComputerSystem', 'Get-ComputerTask', 'Get-ComputerTime', 'Get-ComputerTimeNtp', 'Get-ComputerWindowsFeatures', 'Get-ComputerWindowsUpdates', 'Get-DataInformation', 'Get-FileEncoding', 'Get-FileInformation', 'Get-FileMetaData', 'Get-FileName', 'Get-FileOwner', 'Get-FilePermission', 'Get-FilesInFolder', 'Get-FileSize', 'Get-GitHubLatestRelease', 'Get-GitHubVersion', 'Get-HashMaxValue', 'Get-HTML', 'Get-IPAddressInformation', 'Get-IPAddressRangeInformation', 'Get-Logger', 'Get-MimeType', 'Get-ModulesAvailability', 'Get-MyIpAddress', 'Get-ObjectCount', 'Get-ObjectData', 'Get-ObjectEnumValues', 'Get-ObjectKeys', 'Get-ObjectProperties', 'Get-ObjectPropertiesAdvanced', 'Get-ObjectTitles', 'Get-ObjectType', 'Get-OperatingSystem', 'Get-PathSeparator', 'Get-PathTemporary', 'Get-ProtocolDefaults', 'Get-PSRegistry', 'Get-PSService', 'Get-RandomCharacters', 'Get-RandomFileName', 'Get-RandomPassword', 'Get-RandomStringName', 'Get-SqlQueryColumnInformation', 'Get-TemporaryDirectory', 'Get-TimeSettings', 'Get-TimeZoneAdvanced', 'Get-TimeZoneLegacy', 'Get-Types', 'Get-WinADDSAGuid', 'Get-WinADForestControllers', 'Get-WinADForestDetails', 'Get-WinADForestOptions', 'Get-WinADOrganizationalUnitData', 'Get-WinADOrganizationalUnitFromDN', 'Get-WinADUsersByDN', 'Get-WinADUsersByOU', 'Get-WinADUserSnapshot', 'Initialize-ModulePortable', 'Invoke-CommandCustom', 'Join-Uri', 'Join-UriQuery', 'Measure-Collection', 'Merge-Array', 'Merge-Objects', 'Mount-PSRegistryPath', 'New-ArrayList', 'New-GenericList', 'New-PSRegistry', 'New-Runspace', 'New-SqlQuery', 'New-SqlQueryAlterTable', 'New-SqlQueryCreateTable', 'New-SqlTableMapping', 'Remove-DuplicateObjects', 'Remove-EmptyValue', 'Remove-FilePermission', 'Remove-FromArray', 'Remove-ObjectsExistingInTarget', 'Remove-PSRegistry', 'Remove-WhiteSpace', 'Remove-WinADUserGroups', 'Rename-LatinCharacters', 'Rename-UserValuesFromHash', 'Save-XML', 'Search-Command', 'Select-Properties', 'Send-Email', 'Send-SqlInsert', 'Set-DnsServerIpAddress', 'Set-EmailBody', 'Set-EmailBodyPreparedTable', 'Set-EmailBodyReplacement', 'Set-EmailBodyReplacementTable', 'Set-EmailFormatting', 'Set-EmailHead', 'Set-EmailReportBranding', 'Set-EmailWordReplacements', 'Set-EmailWordReplacementsHash', 'Set-FileInheritance', 'Set-FileOwner', 'Set-FilePermission', 'Set-PasswordRemotely', 'Set-PSRegistry', 'Set-ServiceRecovery', 'Set-TimeSynchronization', 'Set-WinADGroupSynchronization', 'Set-WinADUserFields', 'Set-WinADUserSettingGAL', 'Set-WinADUserStatus', 'Set-XML', 'Show-Array', 'Show-DataInVerbose', 'Show-TableVisualization', 'Split-Array', 'Start-InternalFunction', 'Start-MyProgram', 'Start-Runspace', 'Start-TimeLog', 'Stop-Runspace', 'Stop-TimeLog', 'Test-AvailabilityCommands', 'Test-ComputerAvailability', 'Test-ComputerPort', 'Test-ConfigurationCredentials', 'Test-ForestConnectivity', 'Test-IsDistinguishedName', 'Test-Key', 'Test-ModuleAvailability', 'Test-PSRegistry', 'Test-WinRM') -Alias @('Add-ADUserGroups', 'Convert-ExchangeRecipientDetails', 'Convert-FromColor', 'Copy-Hashtable', 'Copy-OrderedHashtable', 'Dismount-RegistryPath', 'Find-ADSyncServer', 'Format-AddSpaceToSentence', 'Format-Debug', 'Format-ListStream', 'Format-TableStream', 'Format-Verbose', 'Format-Warning', 'FS', 'FV', 'Get-ADUserSnapshot', 'Get-ComputerApplications', 'Get-ComputerNetworkCard', 'Get-ComputerServices', 'Get-ComputerTasks', 'Get-FilePermissions', 'Get-MyIP', 'Get-PSPermissions', 'Get-RDPSecurity', 'Get-ServerRoles', 'Get-TimeSynchronization', 'Get-WinADDomainControllers', 'Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs', 'Join-Url', 'Join-UrlQuery', 'Mount-RegistryPath', 'Remove-ADUserGroups', 'Remove-EmptyValues', 'Remove-StringLatinCharacters', 'Set-ADUserName', 'Set-ADUserSettingGAL', 'Set-ADUserStatus', 'Set-EmailBodyTableReplacement', 'Set-Recovery', 'Sort-Dictionary', 'Test-IsDN')
# SIG # Begin signature block
# MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDu7JNDoz1PwtqF
# bT6WQoLbN8ng5i4UDY8BDZ1sRLObv6CCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt
# 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa
# Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E
# MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy
# unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF
# xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1
# 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB
# MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR
# WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6
# nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB
# YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S
# UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x
# q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB
# NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP
# TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC
# AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
# Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
# bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0
# aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB
# LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc
# Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov
# Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy
# oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW
# juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF
# mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z
# twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG
# SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw
# aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK
# EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm
# dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu
# d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD
# eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1
# XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld
# QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS
# YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm
# M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT
# QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx
# fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
# VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq
# hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4
# XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ
# aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg
# X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk
# apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL
# FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy
# 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u
# KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54
# zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8
# 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8
# aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w
# ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx
# CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
# dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH
# NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT
# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1
# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS
# g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9
# /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn
# HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0
# VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f
# sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj
# gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0
# QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv
# mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T
# /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk
# 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r
# mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E
# FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n
# P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG
# CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
# Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
# aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV
# HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB
# AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp
# wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl
# zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ
# cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe
# Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j
# Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh
# IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6
# OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw
# N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR
# 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2
# VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ
# CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV
# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
# MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw
# MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k
# jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9
# NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9
# URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY
# E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS
# 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa
# wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w
# c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR
# Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2
# 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK
# ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC
# AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2
# O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P
# MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB
# AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr
# BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1
# c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH
# BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6
# mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/
# SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY
# gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9
# kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ
# 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew
# Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm
# Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA
# SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr
# y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR
# ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu
# v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp
# BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy
# NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD
# VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD
# ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD
# Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo
# 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO
# H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R
# eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX
# j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV
# DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J
# idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C
# acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N
# Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X
# CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB
# AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s
# BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X
# LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l
# U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho
# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl
# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU
# aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS
# R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB
# b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC
# zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1
# UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp
# ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE
# sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8
# a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF
# YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q
# EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE
# deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J
# ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq
# F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT
# aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX
# DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz
# dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ
# cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA
# 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh
# vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn
# 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr
# W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w
# iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP
# lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf
# QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28
# eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4
# fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0
# 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC
# AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE
# FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz
# ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j
# cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu
# Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB
# CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E
# BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc
# 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ
# f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51
# 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev
# xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx
# h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY
# D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/
# Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq
# G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH
# v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP
# AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw
# PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2
# IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF
# AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor
# BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3
# DQEJBDEiBCBN4JvsBhNrWwtfuPvyNRsV1rWCXK+i4TfHF3nciowNmjANBgkqhkiG
# 9w0BAQEFAASCAgBsaaSJeq07m3jocis7JaT/q/nXjHJojTtULoz1nIJqUdkuz5D7
# OH81cJeM7NcMDMEBsRB+SL7s7M5jUzFbSNl9Y5VuuJz0ZsKrgV+Yq1Kl9a7y6Uz2
# 6wM94ZWS21Wq8q3bYXoJWh5lANpPjMJowh9p6gU3xhwBXxCZI0RgSx0GrOjYmQod
# 8L5v+GgWOkzqns5JxmrzCdFuY1yxkIu/EudV8u2XveTNGaulMHLaOu6G+xIe+eY+
# HL3cYKg1htZ1Loz37frxDMTDviygxctrVZas3JLmxE5/Ykpru/junISEXfQvDu5L
# StNeWRxgyFz3QYCt9tTEyPQVDMNvS6FDVZOn2t5TFzIA/PBXToRF+dQ7hKnTs8dP
# qIWbQV94WOu1z4f4QAu3eghqsc4U7iNGs1rIvFpkv77ClnkQpAF/dL+FziQK720c
# IN0OyGWaFlAqSorNVU4ZTErZQYm6//Z/usL+NAAC0UF91dEmqCjQo7DBwgVD1jku
# At9XmNBdDXedHxBF+sd/NjraJo5diDfSqmd07A4POpFZOGHxZBnhSRKVrjuQqQ0L
# lcV6SrFHzqlplkRQE8+Wv3Jy+UCsYr2aIMlWTPiIGAv227mqJhv62eS6FFBI9s7o
# lh628uBBZDwpjHGoXG3YApJRCQARpCCon86Rq2HxmE9S7E4GHf2ovy80eaGCAyAw
# ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD
# VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH
# NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj
# MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG
# CSqGSIb3DQEJBTEPFw0yNDEwMjYwNzQyMjhaMC8GCSqGSIb3DQEJBDEiBCAeoGFs
# pEVmuguifyzmiFA+WN7gggpjo82pZylNjoPN5jANBgkqhkiG9w0BAQEFAASCAgA2
# osViknDXVS4BQ9M2ucYi3hAq1FcNFqAYB0iGBFFr2QFIAvinh/qSONoby2Fmdrz0
# HKuEwSpMADJZs/DMzbgMyxC31eyrkuooYzt4zzXb4B9U80M4LUilEqnnxEof1JIw
# lW4V5pv2qBYRjKYDVuAk1JXS1MzQUWg1Pn+NCZyEpEYfWzC9PgMexgda97wWtJRy
# avCJAhysZHWNNItnpIfvGKonwuXO4QEe1N5s5QTw/ixA5TS6s9cI+vf5cU0ZmEsC
# 7KThVdkWRIy7/GBrNLudU5E8ykxp8S4wCghCh4ore1GhsFd7of/i41aQNvHnHTV5
# O6Y+2p84TQ7aGtFZirhDdIoXLR2esXu4XAtJVIX16VgvVQUCBHDFhxuYCNkQQARH
# nCfJsJKwRPHbTR5bl0ZIgoCUunW7s/K5SvNuSItTPLqov/29qQi2UsJZMhUeC04u
# GzsonflRu3VC3i5M/JRyz7Rz3sw2BxnPMQQ7wfYqEfwW8pp4/bacy/j0IT2VYLhz
# QrM5LnT2BmfRB1mQ3dJEmAepZWLez2Oa1PWWXsmLHPl11Q/AROZeww2/jBAGaH5s
# NwIzshKCITihzO6r+Xjnueu4z5qYSbd52qLHc9iXLxCUw6pRI9rYoS9R1MJVHMjH
# FsWtSk4x3K0PhDt3tcEDqod7ilWMxqbhz3b68yJE8A==
# SIG # End signature block