PSHelperTools.psm1

function New-TestEnvironment(){
    Set-Location $env:temp
    $Folder = -join ((65..90) + (97..122) | Get-Random -Count 20 | % {[char]$_})
    New-Item $Folder -ItemType Directory
    Set-Location $Folder
}


function Test-Symlink(){
    [Cmdletbinding()]
    param($Path)
    if(Test-Path $Path){
        ((Get-Item $Path).Attributes.ToString() -match "ReparsePoint")
    }
    else{
        $False
    }
}
function Get-SymlinkTarget(){
    [Cmdletbinding()]
    param($Path)
    if(Test-Symlink $Path){
        try{
            ((Get-Item $Path | Select-Object -ExpandProperty Target) -replace "^UNC\\", "\\")
        }
        catch{
            ""
        }
    }
    else{
        ""
    }
}
function Join-StringCustom{
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [String[]]
        $Strings,
        [String]
        $Separator
    )
    Begin{
        $Array = [System.Collections.ArrayList]@()
    }
    Process{
        $Array.Add($Strings[0]) | Out-Null
    }
    End{
        $Return = ""
        for($i = 0; $i -lt $Array.Count - 1; $i = $i + 1){
            $Return = -join($Return, $Array[$i], $Separator)
        }
        $Return = -join($Return, $Array[$Array.Count - 1])
        $Return
    }
}
#Which Verb? Format? Optimize? Convert? Resolve? Unify is unapproved but fitting
function Format-Path(){
    [Cmdletbinding()]
    param($Path)

    Write-Verbose "Format-Path: $Path"
    if($Path.StartsWith(".")){
        #Get Absolute path if path starts with "."
        $Path = Resolve-Path -Path $Path
        Write-Verbose "Resolved Path: $Path"
    }
    if($Path -match "^.*::(.*)"){
        $Path = $Path -replace "^.*::(.*)", '$1'
        Write-Verbose "Replaced Powershell providers: $Path"
    }
    $Path = $Path -replace "/"                      , "\" `
                  -replace "^\\\\\.\\"              , "" `
                  -replace "^\\\\\?\\"              , "" `
                  -replace "^UNC\\"                 , "\\"
    Write-Verbose "Replaced UNC conventions: $Path"

    if($Path -match "^\\\\([A-Za-z]+)(@SSL)?"){
        $Path = $Path -replace "^\\\\([A-Za-z]+)(@SSL)?", "\\$((Resolve-DnsName $matches[1] -Type "A").IPAddress)"
        Write-Verbose "Resolve name into IP: $Path"
    }

    return $Path.TrimEnd("\")
}
function Resolve-Symlink(){
    [Cmdletbinding()]
    param($Path)

    Write-Verbose "Resolve-Symlink: $Path"
    $Path = Format-Path $Path
    Write-Verbose "Formatted: $Path"
    $Current = $Path
    while($Current){
        Write-Verbose "Current step: $Current"
        if(Test-Symlink $Current){
            $Target = Get-SymlinkTarget $Current
            Write-Verbose "Step is Symlink, replacing with target: $Target"
            $Path = $Path -replace "^$([System.Text.RegularExpressions.Regex]::Escape($Current))", "$Target"
            $Current = $Target
        }
        else{
            $Current = Split-Path -Path $Current -Parent -ErrorAction Stop
        }
    }
    Write-Verbose "Resolved Symlink: $Path"
    return Format-Path $Path
}
function Compare-Paths(){
    [Cmdletbinding()]
    param([String]$First, [String]$Second)

    Write-Verbose "Comparing-Paths: '$First' to '$Second'"
    return (Resolve-Symlink $First -ErrorAction Stop) -eq (Resolve-Symlink $Second -ErrorAction Stop)
}



<#
.SYNOPSIS
    Set File Type Association Windows 8/10/11
 
.DESCRIPTION
    Set File/Protocol Type Association Default Application Windows 8/10/11
 
.NOTES
    Version : 1.2.0
    Author(s) : Danyfirex & Dany3j
    Credits : https://bbs.pediy.com/thread-213954.htm
                 LMongrain - Hash Algorithm PureBasic Version
    License : MIT License
    Copyright : 2022 Danysys. <danysys.com>
 
.EXAMPLE
    Get-FTA
    Show All Application Program Id
 
.EXAMPLE
    Get-FTA .pdf
    Show Default Application Program Id for an Extension
 
.EXAMPLE
    Set-FTA AcroExch.Document.DC .pdf
    Set Acrobat Reader DC as Default .pdf reader
 
.EXAMPLE
    Set-FTA Applications\SumatraPDF.exe .pdf
    Set Sumatra PDF as Default .pdf reader
 
.EXAMPLE
    Set-PTA ChromeHTML http
    Set Google Chrome as Default for http Protocol
 
.EXAMPLE
    Register-FTA "C:\SumatraPDF.exe" .pdf -Icon "shell32.dll,100"
    Register Application and Set as Default for .pdf reader
 
.LINK
    https://github.com/DanysysTeam/PS-SFTA
 
#>

function Get-FTA {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [String]
        $Extension
    )


    if ($Extension) {
        Write-Verbose "Get File Type Association for $Extension"

        $assocFile = (Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice" -ErrorAction SilentlyContinue).ProgId
        Write-Output $assocFile
    }
    else {
        Write-Verbose "Get File Type Association List"

        $assocList = Get-ChildItem HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\* |
                ForEach-Object {
                    $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId
                    if ($progId) {
                        "$($_.PSChildName), $progId"
                    }
                }
        Write-Output $assocList
    }

}

function Get-PTA {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [String]
        $Protocol
    )

    if ($Protocol) {
        Write-Verbose "Get Protocol Type Association for $Protocol"

        $assocFile = (Get-ItemProperty "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice" -ErrorAction SilentlyContinue).ProgId
        Write-Output $assocFile
    }
    else {
        Write-Verbose "Get Protocol Type Association List"

        $assocList = Get-ChildItem HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\* |
                ForEach-Object {
                    $progId = (Get-ItemProperty "$($_.PSParentPath)\$($_.PSChildName)\UserChoice" -ErrorAction SilentlyContinue).ProgId
                    if ($progId) {
                        "$($_.PSChildName), $progId"
                    }
                }
        Write-Output $assocList
    }
}

function Register-FTA {
    [CmdletBinding()]
    param (
        [Parameter( Position = 0, Mandatory = $true)]
        [ValidateScript( { Test-Path $_ })]
        [String]
        $ProgramPath,

        [Parameter( Position = 1, Mandatory = $true)]
        [Alias("Protocol")]
        [String]
        $Extension,

        [Parameter( Position = 2, Mandatory = $false)]
        [String]
        $ProgId,

        [Parameter( Position = 3, Mandatory = $false)]
        [String]
        $Icon
    )

    Write-Verbose "Register Application + Set Association"
    Write-Verbose "Application Path: $ProgramPath"
    if ($Extension.Contains(".")) {
        Write-Verbose "Extension: $Extension"
    }
    else {
        Write-Verbose "Protocol: $Extension"
    }

    if (!$ProgId) {
        $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension
    }

    $progCommand = """$ProgramPath"" ""%1"""
    Write-Verbose "ApplicationId: $ProgId"
    Write-Verbose "ApplicationCommand: $progCommand"

    try {
        $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$Extension\OpenWithProgids"
        [Microsoft.Win32.Registry]::SetValue( $keyPath, $ProgId, ([byte[]]@()), [Microsoft.Win32.RegistryValueKind]::None)
        $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$ProgId\shell\open\command"
        [Microsoft.Win32.Registry]::SetValue($keyPath, "", $progCommand)
        Write-Verbose "Register ProgId and ProgId Command OK"
    }
    catch {
        throw "Register ProgId and ProgId Command FAILED"
    }

    Set-FTA -ProgId $ProgId -Extension $Extension -Icon $Icon
}


function Remove-FTA {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Alias("ProgId")]
        [String]
        $ProgramPath,

        [Parameter(Mandatory = $true)]
        [String]
        $Extension
    )

    function local:Remove-UserChoiceKey {
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [String]
            $Key
        )

        $code = @'
    using System;
    using System.Runtime.InteropServices;
    using Microsoft.Win32;
 
    namespace Registry {
      public class Utils {
        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult);
 
        [DllImport("advapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
        private static extern uint RegDeleteKey(UIntPtr hKey, string subKey);
 
        public static void DeleteKey(string key) {
          UIntPtr hKey = UIntPtr.Zero;
          RegOpenKeyEx((UIntPtr)0x80000001u, key, 0, 0x20019, out hKey);
          RegDeleteKey((UIntPtr)0x80000001u, key);
        }
      }
    }
'@


        try {
            Add-Type -TypeDefinition $code
        }
        catch {}

        try {
            [Registry.Utils]::DeleteKey($Key)
        }
        catch {}
    }

    function local:Update-Registry {
        $code = @'
    [System.Runtime.InteropServices.DllImport("Shell32.dll")]
    private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
    public static void Refresh() {
        SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero);
    }
'@


        try {
            Add-Type -MemberDefinition $code -Namespace SHChange -Name Notify
        }
        catch {}

        try {
            [SHChange.Notify]::Refresh()
        }
        catch {}
    }

    if (Test-Path -Path $ProgramPath) {
        $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgramPath).replace(" ", "") + $Extension
    }
    else {
        $ProgId = $ProgramPath
    }

    try {
        $keyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice"
        Write-Verbose "Remove User UserChoice Key If Exist: $keyPath"
        Remove-UserChoiceKey $keyPath

        $keyPath = "HKCU:\SOFTWARE\Classes\$ProgId"
        Write-Verbose "Remove Key If Exist: $keyPath"
        Remove-Item -Path $keyPath -Recurse -ErrorAction Stop | Out-Null

    }
    catch {
        Write-Verbose "Key No Exist: $keyPath"
    }

    try {
        $keyPath = "HKCU:\SOFTWARE\Classes\$Extension\OpenWithProgids"
        Write-Verbose "Remove Property If Exist: $keyPath Property $ProgId"
        Remove-ItemProperty -Path $keyPath -Name $ProgId -ErrorAction Stop | Out-Null

    }
    catch {
        Write-Verbose "Property No Exist: $keyPath Property: $ProgId"
    }

    Update-Registry
    Write-Output "Removed: $ProgId"
}


function Set-FTA {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [String]
        $ProgId,

        [Parameter(Mandatory = $true)]
        [Alias("Protocol")]
        [String]
        $Extension,

        [String]
        $Icon,

        [switch]
        $DomainSID
    )

    if (Test-Path -Path $ProgId) {
        $ProgId = "SFTA." + [System.IO.Path]::GetFileNameWithoutExtension($ProgId).replace(" ", "") + $Extension
    }

    Write-Verbose "ProgId: $ProgId"
    Write-Verbose "Extension/Protocol: $Extension"


    #Write required Application Ids to ApplicationAssociationToasts
    #When more than one application associated with an Extension/Protocol is installed ApplicationAssociationToasts need to be updated
    function local:Write-RequiredApplicationAssociationToasts {
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [String]
            $ProgId,

            [Parameter( Position = 1, Mandatory = $True )]
            [String]
            $Extension
        )

        try {
            $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts"
            [Microsoft.Win32.Registry]::SetValue($keyPath, $ProgId + "_" + $Extension, 0x0)
            Write-Verbose ("Write Reg ApplicationAssociationToasts OK: " + $ProgId + "_" + $Extension)
        }
        catch {
            Write-Verbose ("Write Reg ApplicationAssociationToasts FAILED: " + $ProgId + "_" + $Extension)
        }

        $allApplicationAssociationToasts = Get-ChildItem -Path HKLM:\SOFTWARE\Classes\$Extension\OpenWithList\* -ErrorAction SilentlyContinue |
                ForEach-Object {
                    "Applications\$($_.PSChildName)"
                }

        $allApplicationAssociationToasts += @(
        ForEach ($item in (Get-ItemProperty -Path HKLM:\SOFTWARE\Classes\$Extension\OpenWithProgids -ErrorAction SilentlyContinue).PSObject.Properties ) {
            if ([string]::IsNullOrEmpty($item.Value) -and $item -ne "(default)") {
                $item.Name
            }
        })


        $allApplicationAssociationToasts += Get-ChildItem -Path HKLM:SOFTWARE\Clients\StartMenuInternet\* , HKCU:SOFTWARE\Clients\StartMenuInternet\* -ErrorAction SilentlyContinue |
                ForEach-Object {
                    (Get-ItemProperty ("$($_.PSPath)\Capabilities\" + (@("URLAssociations", "FileAssociations") | Select-Object -Index $Extension.Contains("."))) -ErrorAction SilentlyContinue).$Extension
                }

        $allApplicationAssociationToasts |
                ForEach-Object { if ($_) {
                    if (Set-ItemProperty HKCU:\Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts $_"_"$Extension -Value 0 -Type DWord -ErrorAction SilentlyContinue -PassThru) {
                        Write-Verbose  ("Write Reg ApplicationAssociationToastsList OK: " + $_ + "_" + $Extension)
                    }
                    else {
                        Write-Verbose  ("Write Reg ApplicationAssociationToastsList FAILED: " + $_ + "_" + $Extension)
                    }
                }
                }

    }

    function local:Update-RegistryChanges {
        $code = @'
    [System.Runtime.InteropServices.DllImport("Shell32.dll")]
    private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
    public static void Refresh() {
        SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero);
    }
'@


        try {
            Add-Type -MemberDefinition $code -Namespace SHChange -Name Notify
        }
        catch {}

        try {
            [SHChange.Notify]::Refresh()
        }
        catch {}
    }


    function local:Set-Icon {
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [String]
            $ProgId,

            [Parameter( Position = 1, Mandatory = $True )]
            [String]
            $Icon
        )

        try {
            $keyPath = "HKEY_CURRENT_USER\SOFTWARE\Classes\$ProgId\DefaultIcon"
            [Microsoft.Win32.Registry]::SetValue($keyPath, "", $Icon)
            Write-Verbose "Write Reg Icon OK"
            Write-Verbose "Reg Icon: $keyPath"
        }
        catch {
            Write-Verbose "Write Reg Icon FAILED"
        }
    }


    function local:Write-ExtensionKeys {
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [String]
            $ProgId,

            [Parameter( Position = 1, Mandatory = $True )]
            [String]
            $Extension,

            [Parameter( Position = 2, Mandatory = $True )]
            [String]
            $ProgHash
        )


        function local:Remove-UserChoiceKey {
            param (
                [Parameter( Position = 0, Mandatory = $True )]
                [String]
                $Key
            )

            $code = @'
      using System;
      using System.Runtime.InteropServices;
      using Microsoft.Win32;
 
      namespace Registry {
        public class Utils {
          [DllImport("advapi32.dll", SetLastError = true)]
          private static extern int RegOpenKeyEx(UIntPtr hKey, string subKey, int ulOptions, int samDesired, out UIntPtr hkResult);
 
          [DllImport("advapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
          private static extern uint RegDeleteKey(UIntPtr hKey, string subKey);
 
          public static void DeleteKey(string key) {
            UIntPtr hKey = UIntPtr.Zero;
            RegOpenKeyEx((UIntPtr)0x80000001u, key, 0, 0x20019, out hKey);
            RegDeleteKey((UIntPtr)0x80000001u, key);
          }
        }
      }
'@


            try {
                Add-Type -TypeDefinition $code
            }
            catch {}

            try {
                [Registry.Utils]::DeleteKey($Key)
            }
            catch {}
        }


        try {
            $keyPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice"
            Write-Verbose "Remove Extension UserChoice Key If Exist: $keyPath"
            Remove-UserChoiceKey $keyPath
        }
        catch {
            Write-Verbose "Extension UserChoice Key No Exist: $keyPath"
        }


        try {
            $keyPath = "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$Extension\UserChoice"
            [Microsoft.Win32.Registry]::SetValue($keyPath, "Hash", $ProgHash)
            [Microsoft.Win32.Registry]::SetValue($keyPath, "ProgId", $ProgId)
            Write-Verbose "Write Reg Extension UserChoice OK"
        }
        catch {
            throw "Write Reg Extension UserChoice FAILED"
        }
    }


    function local:Write-ProtocolKeys {
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [String]
            $ProgId,

            [Parameter( Position = 1, Mandatory = $True )]
            [String]
            $Protocol,

            [Parameter( Position = 2, Mandatory = $True )]
            [String]
            $ProgHash
        )


        try {
            $keyPath = "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice"
            Write-Verbose "Remove Protocol UserChoice Key If Exist: $keyPath"
            Remove-Item -Path $keyPath -Recurse -ErrorAction Stop | Out-Null

        }
        catch {
            Write-Verbose "Protocol UserChoice Key No Exist: $keyPath"
        }


        try {
            $keyPath = "HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\$Protocol\UserChoice"
            [Microsoft.Win32.Registry]::SetValue( $keyPath, "Hash", $ProgHash)
            [Microsoft.Win32.Registry]::SetValue($keyPath, "ProgId", $ProgId)
            Write-Verbose "Write Reg Protocol UserChoice OK"
        }
        catch {
            throw "Write Reg Protocol UserChoice FAILED"
        }

    }


    function local:Get-UserExperience {
        [OutputType([string])]

        $userExperienceSearch = "User Choice set via Windows User Experience"
        $user32Path = [Environment]::GetFolderPath([Environment+SpecialFolder]::SystemX86) + "\Shell32.dll"
        $fileStream = [System.IO.File]::Open($user32Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
        $binaryReader = New-Object System.IO.BinaryReader($fileStream)
        [Byte[]] $bytesData = $binaryReader.ReadBytes(5mb)
        $fileStream.Close()
        $dataString = [Text.Encoding]::Unicode.GetString($bytesData)
        $position1 = $dataString.IndexOf($userExperienceSearch)
        $position2 = $dataString.IndexOf("}", $position1)

        Write-Output $dataString.Substring($position1, $position2 - $position1 + 1)
    }


    function local:Get-UserSid {
        [OutputType([string])]
        $userSid = ((New-Object System.Security.Principal.NTAccount([Environment]::UserName)).Translate([System.Security.Principal.SecurityIdentifier]).value).ToLower()
        Write-Output $userSid
    }

    #use in this special case
    #https://github.com/DanysysTeam/PS-SFTA/pull/7
    function local:Get-UserSidDomain {
        if (-not ("System.DirectoryServices.AccountManagement" -as [type])) {
            Add-Type -AssemblyName System.DirectoryServices.AccountManagement
        }
        [OutputType([string])]
        $userSid = ([System.DirectoryServices.AccountManagement.UserPrincipal]::Current).SID.Value.ToLower()
        Write-Output $userSid
    }



    function local:Get-HexDateTime {
        [OutputType([string])]

        $now = [DateTime]::Now
        $dateTime = [DateTime]::New($now.Year, $now.Month, $now.Day, $now.Hour, $now.Minute, 0)
        $fileTime = $dateTime.ToFileTime()
        $hi = ($fileTime -shr 32)
        $low = ($fileTime -band 0xFFFFFFFFL)
        $dateTimeHex = ($hi.ToString("X8") + $low.ToString("X8")).ToLower()
        Write-Output $dateTimeHex
    }

    function Get-Hash {
        [CmdletBinding()]
        param (
            [Parameter( Position = 0, Mandatory = $True )]
            [string]
            $BaseInfo
        )


        function local:Get-ShiftRight {
            [CmdletBinding()]
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                [long] $iValue,

                [Parameter( Position = 1, Mandatory = $true)]
                [int] $iCount
            )

            if ($iValue -band 0x80000000) {
                Write-Output (( $iValue -shr $iCount) -bxor 0xFFFF0000)
            }
            else {
                Write-Output  ($iValue -shr $iCount)
            }
        }


        function local:Get-Long {
            [CmdletBinding()]
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                [byte[]] $Bytes,

                [Parameter( Position = 1)]
                [int] $Index = 0
            )

            Write-Output ([BitConverter]::ToInt32($Bytes, $Index))
        }


        function local:Convert-Int32 {
            param (
                [Parameter( Position = 0, Mandatory = $true)]
                $Value
            )

            [byte[]] $bytes = [BitConverter]::GetBytes($Value)
            return [BitConverter]::ToInt32( $bytes, 0)
        }

        [Byte[]] $bytesBaseInfo = [System.Text.Encoding]::Unicode.GetBytes($baseInfo)
        $bytesBaseInfo += 0x00, 0x00

        $MD5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
        [Byte[]] $bytesMD5 = $MD5.ComputeHash($bytesBaseInfo)

        $lengthBase = ($baseInfo.Length * 2) + 2
        $length = (($lengthBase -band 4) -le 1) + (Get-ShiftRight $lengthBase  2) - 1
        $base64Hash = ""

        if ($length -gt 1) {

            $map = @{PDATA = 0; CACHE = 0; COUNTER = 0 ; INDEX = 0; MD51 = 0; MD52 = 0; OUTHASH1 = 0; OUTHASH2 = 0;
            R0 = 0; R1 = @(0, 0); R2 = @(0, 0); R3 = 0; R4 = @(0, 0); R5 = @(0, 0); R6 = @(0, 0); R7 = @(0, 0)
            }

            $map.CACHE = 0
            $map.OUTHASH1 = 0
            $map.PDATA = 0
            $map.MD51 = (((Get-Long $bytesMD5) -bor 1) + 0x69FB0000L)
            $map.MD52 = ((Get-Long $bytesMD5 4) -bor 1) + 0x13DB0000L
            $map.INDEX = Get-ShiftRight ($length - 2) 1
            $map.COUNTER = $map.INDEX + 1

            while ($map.COUNTER) {
                $map.R0 = Convert-Int32 ((Get-Long $bytesBaseInfo $map.PDATA) + [long]$map.OUTHASH1)
                $map.R1[0] = Convert-Int32 (Get-Long $bytesBaseInfo ($map.PDATA + 4))
                $map.PDATA = $map.PDATA + 8
                $map.R2[0] = Convert-Int32 (($map.R0 * ([long]$map.MD51)) - (0x10FA9605L * ((Get-ShiftRight $map.R0 16))))
                $map.R2[1] = Convert-Int32 ((0x79F8A395L * ([long]$map.R2[0])) + (0x689B6B9FL * (Get-ShiftRight $map.R2[0] 16)))
                $map.R3 = Convert-Int32 ((0xEA970001L * $map.R2[1]) - (0x3C101569L * (Get-ShiftRight $map.R2[1] 16) ))
                $map.R4[0] = Convert-Int32 ($map.R3 + $map.R1[0])
                $map.R5[0] = Convert-Int32 ($map.CACHE + $map.R3)
                $map.R6[0] = Convert-Int32 (($map.R4[0] * [long]$map.MD52) - (0x3CE8EC25L * (Get-ShiftRight $map.R4[0] 16)))
                $map.R6[1] = Convert-Int32 ((0x59C3AF2DL * $map.R6[0]) - (0x2232E0F1L * (Get-ShiftRight $map.R6[0] 16)))
                $map.OUTHASH1 = Convert-Int32 ((0x1EC90001L * $map.R6[1]) + (0x35BD1EC9L * (Get-ShiftRight $map.R6[1] 16)))
                $map.OUTHASH2 = Convert-Int32 ([long]$map.R5[0] + [long]$map.OUTHASH1)
                $map.CACHE = ([long]$map.OUTHASH2)
                $map.COUNTER = $map.COUNTER - 1
            }

            [Byte[]] $outHash = @(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
            [byte[]] $buffer = [BitConverter]::GetBytes($map.OUTHASH1)
            $buffer.CopyTo($outHash, 0)
            $buffer = [BitConverter]::GetBytes($map.OUTHASH2)
            $buffer.CopyTo($outHash, 4)

            $map = @{PDATA = 0; CACHE = 0; COUNTER = 0 ; INDEX = 0; MD51 = 0; MD52 = 0; OUTHASH1 = 0; OUTHASH2 = 0;
            R0 = 0; R1 = @(0, 0); R2 = @(0, 0); R3 = 0; R4 = @(0, 0); R5 = @(0, 0); R6 = @(0, 0); R7 = @(0, 0)
            }

            $map.CACHE = 0
            $map.OUTHASH1 = 0
            $map.PDATA = 0
            $map.MD51 = ((Get-Long $bytesMD5) -bor 1)
            $map.MD52 = ((Get-Long $bytesMD5 4) -bor 1)
            $map.INDEX = Get-ShiftRight ($length - 2) 1
            $map.COUNTER = $map.INDEX + 1

            while ($map.COUNTER) {
                $map.R0 = Convert-Int32 ((Get-Long $bytesBaseInfo $map.PDATA) + ([long]$map.OUTHASH1))
                $map.PDATA = $map.PDATA + 8
                $map.R1[0] = Convert-Int32 ($map.R0 * [long]$map.MD51)
                $map.R1[1] = Convert-Int32 ((0xB1110000L * $map.R1[0]) - (0x30674EEFL * (Get-ShiftRight $map.R1[0] 16)))
                $map.R2[0] = Convert-Int32 ((0x5B9F0000L * $map.R1[1]) - (0x78F7A461L * (Get-ShiftRight $map.R1[1] 16)))
                $map.R2[1] = Convert-Int32 ((0x12CEB96DL * (Get-ShiftRight $map.R2[0] 16)) - (0x46930000L * $map.R2[0]))
                $map.R3 = Convert-Int32 ((0x1D830000L * $map.R2[1]) + (0x257E1D83L * (Get-ShiftRight $map.R2[1] 16)))
                $map.R4[0] = Convert-Int32 ([long]$map.MD52 * ([long]$map.R3 + (Get-Long $bytesBaseInfo ($map.PDATA - 4))))
                $map.R4[1] = Convert-Int32 ((0x16F50000L * $map.R4[0]) - (0x5D8BE90BL * (Get-ShiftRight $map.R4[0] 16)))
                $map.R5[0] = Convert-Int32 ((0x96FF0000L * $map.R4[1]) - (0x2C7C6901L * (Get-ShiftRight $map.R4[1] 16)))
                $map.R5[1] = Convert-Int32 ((0x2B890000L * $map.R5[0]) + (0x7C932B89L * (Get-ShiftRight $map.R5[0] 16)))
                $map.OUTHASH1 = Convert-Int32 ((0x9F690000L * $map.R5[1]) - (0x405B6097L * (Get-ShiftRight ($map.R5[1]) 16)))
                $map.OUTHASH2 = Convert-Int32 ([long]$map.OUTHASH1 + $map.CACHE + $map.R3)
                $map.CACHE = ([long]$map.OUTHASH2)
                $map.COUNTER = $map.COUNTER - 1
            }

            $buffer = [BitConverter]::GetBytes($map.OUTHASH1)
            $buffer.CopyTo($outHash, 8)
            $buffer = [BitConverter]::GetBytes($map.OUTHASH2)
            $buffer.CopyTo($outHash, 12)

            [Byte[]] $outHashBase = @(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
            $hashValue1 = ((Get-Long $outHash 8) -bxor (Get-Long $outHash))
            $hashValue2 = ((Get-Long $outHash 12) -bxor (Get-Long $outHash 4))

            $buffer = [BitConverter]::GetBytes($hashValue1)
            $buffer.CopyTo($outHashBase, 0)
            $buffer = [BitConverter]::GetBytes($hashValue2)
            $buffer.CopyTo($outHashBase, 4)
            $base64Hash = [Convert]::ToBase64String($outHashBase)
        }

        Write-Output $base64Hash
    }

    Write-Verbose "Getting Hash For $ProgId $Extension"
    If ($DomainSID.IsPresent) { Write-Verbose  "Use Get-UserSidDomain" } Else { Write-Verbose  "Use Get-UserSid" }
    $userSid = If ($DomainSID.IsPresent) { Get-UserSidDomain } Else { Get-UserSid }
    $userExperience = Get-UserExperience
    $userDateTime = Get-HexDateTime
    Write-Debug "UserDateTime: $userDateTime"
    Write-Debug "UserSid: $userSid"
    Write-Debug "UserExperience: $userExperience"

    $baseInfo = "$Extension$userSid$ProgId$userDateTime$userExperience".ToLower()
    Write-Verbose "baseInfo: $baseInfo"

    $progHash = Get-Hash $baseInfo
    Write-Verbose "Hash: $progHash"

    #Write AssociationToasts List
    Write-RequiredApplicationAssociationToasts $ProgId $Extension

    #Handle Extension Or Protocol
    if ($Extension.Contains(".")) {
        Write-Verbose "Write Registry Extension: $Extension"
        Write-ExtensionKeys $ProgId $Extension $progHash

    }
    else {
        Write-Verbose "Write Registry Protocol: $Extension"
        Write-ProtocolKeys $ProgId $Extension $progHash
    }


    if ($Icon) {
        Write-Verbose  "Set Icon: $Icon"
        Set-Icon $ProgId $Icon
    }

    Update-RegistryChanges

}

function Set-PTA {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [String]
        $ProgId,

        [Parameter(Mandatory = $true)]
        [String]
        $Protocol,

        [String]
        $Icon
    )

    Set-FTA -ProgId $ProgId -Protocol $Protocol -Icon $Icon
}