MSTerminalSettings.psm1

using namespace System.Collections.Generic
using namespace System.Collections.ObjectModel
using namespace System.Management.Automation
using namespace WindowsTerminal
$Script:ModuleRoot = $PSScriptRoot

$Script:DEV_PATH = "packages/WindowsTerminalDev_8wekyb3d8bbwe/LocalState"
$Script:DEV_PATH_ALT = "packages/WindowsTerminalDev_8wekyb3d8bbwe/RoamingState"
$Script:RELEASE_PATH = "packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"
$Script:RELEASE_PATH_ALT = "packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/RoamingState"
$Script:STANDALONE_PATH = "Microsoft/Windows Terminal"

#Load the validated schema types if not already loaded
try {
    $null = [WindowsTerminal.TerminalSettings]
} catch [Management.Automation.RuntimeException] {
    if ([String]$psitem -match 'Unable to find type') {
        if (Test-Path "$PSSCRIPTROOT/lib/TerminalSettings.dll") {
            Add-Type -Path "$PSSCRIPTROOT/lib/*.dll"
        } elseif (Test-Path "$PSSCRIPTROOT/Private/Build-TerminalSettingsAssembly.ps1") {
            . $PSSCRIPTROOT/Private/Build-TerminalSettingsAssembly.ps1
            Build-TerminalSettingsAssembly
        } else {
            throw 'TerminalSettings: Could not find compiled DLL or the src files. This is probably a bug.'
        }
    } else {throw $PSItem}
}

$ColorSchemeCompleter = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $ParentBoundParameters
    )
    #Override the completion information
    $PSBoundParameters.CommandName = 'Get-MSTerminalColorScheme'
    $PSBoundParameters.ParameterName = 'Name'
    Get-ArgumentCompleter @PSBoundParameters
}
Register-ArgumentCompleter -CommandName "Get-MSTerminalColorScheme","Add-MSTerminalColorScheme","Remove-MSTerminalColorScheme" -ParameterName Name -ScriptBlock $ColorSchemeCompleter
Register-ArgumentCompleter -CommandName "Set-MSTerminalProfile","Add-MSTerminalProfile" -ParameterName ColorScheme -ScriptBlock $ColorSchemeCompleter

$ProfileCompleter = {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $ParentBoundParameters
    )
    #Override the completion information
    $PSBoundParameters.CommandName = 'Get-MSTerminalProfile'
    $PSBoundParameters.ParameterName = 'Name'
    Get-ArgumentCompleter @PSBoundParameters
}
Register-ArgumentCompleter -CommandName 'Get-MSTerminalProfile','Add-MSTerminalProfile' -ParameterName Name -ScriptBlock $ProfileCompleter
Register-ArgumentCompleter -CommandName 'Set-MSTerminalProfile','Remove-MSTerminalProfile' -ParameterName InputObject -ScriptBlock $ProfileCompleter
function Add-TerminalSettingsParams {
    param (
        [Parameter(Mandatory, ValueFromPipeline)][RuntimeDefinedParameterDictionary]$InputObject,
        #Which Type passed from the dynamic parameters
        [Type]$Type,

        [ValidateNotNullOrEmpty()]
        [IO.FileInfo]
        $SchemaPath = $TerminalSettingsSchemaPath
    )

    begin {
        #Gather important schema dictionaries and settings
        $WTSchema = (Import-JsonWithComments $SchemaPath).definitions
    }

    process {
        $definitionName = switch ($Type) {
            ([ProfileList]) {
                'profile'
                break
            }
            ([SchemeList]) {
                'scheme'
                break
            }
            ([TerminalSettings]) {
                'globals'
                break
            }
            default {
                $PSItem.Name
            }
        }
        $propertyList = $WTSchema.$definitionName.properties
        foreach ($paramName in $InputObject.keys) {
            $paramItem = $propertyList.$ParamName
            $paramAttributes = $inputObject[$paramName].Attributes
            #$paramAttributes[0].ValueFromPipelineByPropertyName = $true
            if ($paramItem.description) {
                $parameterAttribute = $inputObject[$paramName].Attributes.where{$PSItem -is [ParameterAttribute]}
                $parameterAttribute[0].HelpMessage = $paramItem.description
            }
            #TODO: Multiple type struct support
            if (($paramItem.maximum -or $paramItem.minimum) -and $paramItem.Type.Count -eq 1) {
                if ('integer' -in $ParamItem.Type) {
                    $maxValue = [long]::MaxValue
                    $minValue = [long]::MinValue
                    $paramMax = [long]$paramItem.maximum
                    $paramMin = [long]$paramItem.minimum
                } elseif ('number' -in $ParamItem.Type) {
                    $maxValue = [decimal]::MaxValue
                    $minValue = [decimal]::MinValue
                    $paramMax = [decimal]$paramItem.maximum
                    $paramMin = [decimal]$paramItem.minimum
                }

                $minimum = if ($null -eq $paramItem.minimum) {$minValue} else {$paramMin}
                $maximum =  if ($null -eq $paramItem.maximum) {$maxValue} else {$paramMax}
                $paramAttributes.Add([ValidateRange]::new($minimum,$maximum))
            }
            if ($ParamItem.minlength -or $paramItem.maxlength) {
                $minLength = if ($null -eq $paramItem.minLength) {[int]::MinValue} else {$paramItem.minlength}
                $maxLength = if ($null -eq $paramItem.maxLength) {[int]::MaxValue} else {$paramItem.maxlength}
                $paramAttributes.Add([ValidateLength]::new($minLength,$maxLength))
            }
            if ($ParamItem.'$ref' -match '^#/definitions/(\w+)$') {
                if ($WTSchema.($matches[1]).pattern) {
                    $paramAttributes.Add([ValidatePattern]::new($WTSchema.($matches[1]).pattern))
                }
            }
        }
    }
}
#This function dynamically generates types and is used for strong type definition
function Build-TerminalSettingsAssembly {
    param (
        #Output the assembly to a specified DLL location
        $OutputAssembly
    )
    $ReferencedAssemblies = @(
        ([newtonsoft.json.jsonpropertyattribute].assembly)
        'System.Collections, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
        'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'
        'System.Runtime.Extensions, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
    )

    $AddTypeParams = @{
        ReferencedAssemblies = $referencedAssemblies
        Path = "$PSSCRIPTROOT/../src/TerminalSettings.cs"
    }
    if ($OutputAssembly) {$AddTypeParams.OutputAssembly = $OutputAssembly}
    Write-Debug "Compiling $($AddTypeParams.Path)"
    Add-Type @AddTypeParams
}
function ConvertFrom-Iterm2ColorScheme {
    [cmdletbinding(DefaultParameterSetName = 'Path')]
    param(
        [parameter(
            Mandatory = $true,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path,

        [parameter(
            Mandatory = $true,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,
        [Switch]$AsHashtable
    )
    begin {
        function HandleDict {
            param(
                $Dict
            )
            $Hashtable = @{}
            while($Dict.HasChildNodes) {
                do {
                    $FirstChild = $Dict.RemoveChild($Dict.FirstChild)
                } while($FirstChild.Name -eq "#comment")
                $Key = $FirstChild.InnerText
                $Value = HandleValue $Dict.RemoveChild($Dict.FirstChild)
                $Hashtable[$Key] = $Value
            }
            $Hashtable
        }
        function HandleValue {
            param(
                $Value
            )
            switch($Value.Name) {
                "dict" {
                    HandleDict $Value
                }
                "real" {
                    [float]$Value.InnerText
                }
                default {
                    $Value.Value
                }
            }
        }
        function ToRGB {
            param(
                $ColorTable
            )
            [int]$R = $ColorTable["Red Component"] * 255
            [int]$G = $ColorTable["Green Component"] * 255
            [int]$B = $ColorTable["Blue Component"] * 255
            "#{0:X2}{1:X2}{2:X2}" -f $R, $G, $B
        }
    }
    process {
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            $ResolvedPaths = Resolve-Path -Path $Path
        } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $ResolvedPaths = Resolve-Path -LiteralPath $LiteralPath
        }

        $ResolvedPaths | ForEach-Object {
            $Xml = [xml](Get-Content -LiteralPath $_.Path) #New-Object System.Xml.XmlDocument
            #$Xml.Load( $_.Path )
            $ItermHT = HandleDict $Xml.DocumentElement.FirstChild
            $AnsiColorMap = @{
                0 = "black"
                1 = "red"
                2 = "green"
                3 = "yellow"
                4 = "blue"
                5 = "purple"
                6 = "cyan"
                7 = "white"
                8 = "brightBlack"
                9 = "brightRed"
                10 = "brightGreen"
                11 = "brightYellow"
                12 = "brightBlue"
                13 = "brightPurple"
                14 = "brightCyan"
                15 = "brightWhite"
            }
            $Colors = @{}
            $AnsiColorMap.Keys | ForEach-Object {
                $ColorName = $AnsiColorMap[$_]
                $Colors[$ColorName] = ToRGB $ItermHT["Ansi $_ Color"]
            }

            $Colors["foreground"] = ToRGB $ITermHT["Foreground Color"]
            $Colors["background"] = ToRGB $ITermHT["Background Color"]
            if($AsHashtable) {
                $Colors
            } else {
                [PSCustomobject]$Colors
            }
        }
    }
}
<#
.SYNOPSIS
This command will use a variety of probing methods to determine what the current powershell profile is.
.NOTES
THis is an imperfect process, a better method would be to correlate the WT_SESSION to the profile, if an API ever exists for this.
#>

function Find-CurrentTerminalProfile {
    $ErrorActionPreference = 'Stop'
    if (-not $env:WT_SESSION) { throwUser "This only works in Windows Terminal currently. Please try running this command again inside a Windows Terminal powershell session." }
    #Detection Method 1: Profile Environment Variable
    if ($env:WT_PROFILE_ID) {
        $profileName = $env:WT_PROFILE_ID
        write-debug "Terminal Detection: Detected WT_PROFILE_ID is set to $profileName, fetching if profile exists"
        if ($profileName -as [Guid]) {
            return Get-MSTerminalProfile -Guid $profileName
        } else {
            return Get-MSTerminalProfile -Name $profileName
        }
    }

    #Detection Method 2: Check the powershell executable type and if only one profile that doesn't have WT_PROFILE_ID already defined matches, return that.
    $psExe = Get-Process -PID $pid
    $psExePath = $psExe.Path
    $psExeName = $psExe.ProcessName
    $WTProfile = Get-MSTerminalProfile

    if ($psExeName -eq 'pwsh') {
        $candidateProfiles = $WTProfile.where{
            $PSItem.source -eq 'Windows.Terminal.PowershellCore' -or $PSItem.commandline -match [regex]::Escape($psExeName)
        }
    } else {
        $candidateProfiles = $WTProfile.where{$PSItem.commandline -match [regex]::Escape($psExeName)}
    }

    #The PSCustomObject array cast is to enable count to work properly in PS5.1 (it returns nothing on a non-array). Unnecessary in PS6+
    [PSCustomObject[]]$candidateProfiles = $candidateProfiles | Where-Object commandline -notmatch 'WT_PROFILE_ID'

    #If there were no matches, bail out gracefully
    if (-not $candidateprofiles) {
        write-debug "Terminal Detection: No profiles found that match $psExeName"
        throwUser "Unable to detect your currently running profile. Please specify the -Name parameter, or set the WT_PROFILE_ID environment variable"
    }

    #If there was only one result, return it
    if ($candidateProfiles.count -eq 1) {
        write-debug ("Terminal Detection: Found single profile that matches $psExeName, returning {0} {1}." -f $candidateProfiles[0].Name,$candidateProfiles[0].Guid)
        return $candidateProfiles[0]
    }

    #If there were multiple results, try matching by absolute path, otherwise fail with ambiguous
    if ($candidateProfiles.count -gt 1) {
        $absolutePathProfile = $candidateProfiles | Where-Object commandline -eq $PSExePath
        if ($absolutePathProfile.count -eq 1) {return $absolutePathProfile}

        #Fail if multiple profiles were found but could not be determined which was ours
        throwUser "Multiple ambiguous profiles for $psExe were found: {0}. Please specify a profile with the -Name parameter or set the WT_PROFILE_ID environment variable within your session" -f $candidateProfiles.Name -join ', '
    }

    #Failsafe code path
    throwUser "A profile could not be located. This is a bug, this exception shouldn't be reached"
}
function Get-ArgumentCompleter {
    param(
        $CommandName,
        $ParameterName,
        $WordToComplete,
        $CommandAst,
        $ParentBoundParameters
    )

    $params = @{}
    if ($WordToComplete) {$params.$ParameterName = "${WordToComplete}*"}
    (. $CommandName @params).$ParameterName.foreach{
        if ($PSItem -match ' ') {
            "'$PSItem'"
        } else {$PSItem}
    }
}
function Get-ObjectDynamicParameters {
    param(
        #The type to retrieve the dynamic parameters
        [Parameter(Mandatory)][Type]$Type,
        #Which Properties should be Mandatory
        [String[]]$MandatoryParameters,
        #If some parameters need to be positional, specify them in the position order that is required
        [String[]]$ParameterOrder,
        #Exclude some parameters, if present
        [String[]]$Exclude,
        #Rename parameters in hashtable syntax @{Name='NewName'}
        [Hashtable]$Rename
    )
    $dynamicParams = [RuntimeDefinedParameterDictionary]::new()
    foreach ($PropertyItem in $Type.declaredproperties) {
        if ($PropertyItem.Name -in $Exclude) {continue}
        if ($Rename.($PropertyItem.Name)) {
            $PropertyItem = [PSCustomObject]@{
                Name = $Rename.($PropertyItem.Name)
                PropertyType = $PropertyItem.PropertyType
            }
        }

        $attributes = [Collection[Attribute]]@()
        if ($PropertyItem.Name -in $MandatoryParameters) {
            $attributes.Add([ParameterAttribute]@{Mandatory=$true})
        } else {
            $attributes.Add([ParameterAttribute]@{})
        }

        #Convert Booleans to switches
        if ([String]$PropertyItem.PropertyType -match 'bool') {
            $PropertyType = [switch]
        } else {
            $PropertyType = $PropertyItem.PropertyType
        }

        $Param = [runtimedefinedparameter]::new(
            $PropertyItem.Name,         #string name
            $PropertyType,              #type ParameterType
            $attributes                 #System.Collections.ObjectModel.Collection[System.Attribute] attributes
        )
        $dynamicParams[$Param.Name] = $Param
    }
    $i=0
    $ParameterOrder.foreach{
        $dynamicParams[$PSItem].Attributes[0].Position = $i
        $i++
    }

    return $dynamicParams
}
#A simple wrapper for 5.1 compatibility with Json that has comments embedded
function Import-JsonWithComments ($Path) {
    if ($PSEdition -eq 'Desktop') {
        Get-Content $Path | Where-Object {$_ -notmatch '//'} | Out-String | ConvertFrom-Json
    } else {
        #More performant method in newer PS versions
        Get-Content -Raw $Path | ConvertFrom-Json
    }
}
function Resolve-MSTerminalProfile {
    param (
        [Parameter(Mandatory,ValueFromPipeline)][Alias('Name','Guid')]$InputObject
    )
    process{ foreach ($ProfileItem in $InputObject) {
        switch ($true) {
            ($ProfileItem -is [Profile]) {break}
            ($ProfileItem -is [ProfileList]) {break}
            ($null -ne ($ProfileItem -as [Guid])) {$ProfileItem=Get-MSTerminalProfile -Guid $ProfileItem;break}
            ($null -ne ($ProfileItem -as [String])) {$ProfileItem=Get-MSTerminalProfile -Name $ProfileItem;break}
        }
        $ProfileItem
    }}
}

function ResolveWellKnownPaths {
    $Paths = [PSCustomObject]@{
        LocalAppData = ""
        AppData = ""
    }
    if($PSVersionTable["platform"] -eq "Unix") {
        $SystemDrive = cmd.exe /c "echo %SystemDrive%" 2>> /dev/null
        $MntPath = "/mnt/$($SystemDrive.Trim(":").ToLower())"
        $LocalAppData = cmd.exe /c "echo %LOCALAPPDATA%" 2>> /dev/null
        $Paths.LocalAppData = $LocalAppData.Replace("\","/").Replace($SystemDrive, $MntPath)
        $AppData = cmd.exe /c "echo %APPDATA%" 2>> /dev/null
        $Paths.AppData = $AppData.Replace("\","/").Replace($SystemDrive, $MntPath)
    } else {
        $Paths.LocalAppData = $env:LOCALAPPDATA
        $Paths.AppData = $env:APPDATA
    }
    $Paths
}
function throwUser {
<#
.SYNOPSIS
Throws a terminating exception record that shows the cmdlet as the source of the error, rather than the inner "throw". Makes for more user friendly errors than simply using "throw"
.INPUTS
[String]
[Exception]
[Object]
.OUTPUTS
[Management.Automation.ErrorRecord]
.LINK
https://powershellexplained.com/2017-04-10-Powershell-exceptions-everything-you-ever-wanted-to-know/ - Section on $PSCmdlet.ThrowTerminatingError()
.EXAMPLE
ThrowException "Some Error Occured"
.EXAMPLE
ThrowException [System.ApplicationException]
#>

    [CmdletBinding()]
    param (
        #Use anything you would normally use for "throw"
        [Parameter(Mandatory)]$InputObject
    )

    #Generate an error record from "throw"
    try {
        throw $InputObject
    } catch {
        $errorRecord = $PSItem
    }

    #Because this command is itself a cmdlet, we need the parent Cmdlet "context" to show the proper line numbers of the error, which is why scope 1 is used. If that doesn't exist then just use normal scope
    try {
        $myPSContext = (Get-Variable -Scope 2 'PSCmdlet' -Erroraction Stop).Value
    } catch [ItemNotFoundException] {
        $myPSContext = $PSCmdlet
    }
    $myPSContext.ThrowTerminatingError($errorRecord)
}
function Update-WTQuickType {
    param (
        $Path = 'https://aka.ms/terminal-profiles-schema',
        $JsonOutPath = "$PSSCRIPTROOT\..\TerminalSettingsSchema.json",
        $Destination = "$PSSCRIPTROOT\..\src\TerminalSettings.cs"
    )

    if (-not (Get-Command 'quicktype' -ErrorAction SilentlyContinue)) {
        throw 'This command requires quicktype. Install NodeJS and run npm install quicktype -g'
    }

    #Fix a bug where oneOf goes the wrong way
    $jsonContent = [String](iwr -useb $Path) | ConvertFrom-Json -AsHashtable

    $profileKey = $jsoncontent['allOf'].properties.profiles['oneOf']
    $jsonContent['allOf'].properties.profiles['oneOf'] = @($profileKey | Where-Object '$ref' -match 'ProfilesObject')
    $jsonContent | ConvertTo-Json -Depth 10 | Out-File -FilePath $jsonOutPath

    & quicktype -s schema --namespace WindowsTerminal --number-type decimal --density normal --array-type list -o $Destination $jsonOutPath

    #Inject some cleaner formatting options
    $settingsRegex = [regex]::new('(public static readonly JsonSerializerSettings Settings .+?{)','SingleLine')
    $formattingCode = @'
 
            Formatting = Formatting.Indented,
            NullValueHandling = NullValueHandling.Ignore,
            DefaultValueHandling = DefaultValueHandling.Ignore,
'@


    $TerminalSettingsCode = Get-Content -raw $Destination

    $TerminalSettingsCode = $settingsRegex.Replace($TerminalSettingsCode, "`$1$formattingCode")

    $stringDefaultRegex = [Regex]::new('( +?public string \w+ \{ get\; set\; \})','SingleLine')
    $stringDefaultCode = @'
        [DefaultValue("")]
'@

    $TerminalSettingsCode = $stringDefaultRegex.Replace($TerminalSettingsCode, "$stringDefaultCode `$1")

    $CMRegex = [Regex]::new('(using System;)','SingleLine')
    $CMCode = '
    using System.ComponentModel;'

    $TerminalSettingsCode = $CMRegex.Replace($TerminalSettingsCode, "`$1 $CMCode")

    $TerminalSettingsCode | Out-File -FilePath $Destination

}
function Add-MSTerminalColorScheme {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipeline)][ValidateNotNullOrEmpty()][WindowsTerminal.TerminalSettings]$TerminalSettings = (Get-MSTerminalConfig),
        [Switch]$Force
    )
    DynamicParam {
        $dynamicParams = Get-ObjectDynamicParameters 'WindowsTerminal.SchemeList' -MandatoryParameters 'Name' -ParameterOrder 'Name'
        $dynamicParams | Add-TerminalSettingsParams
        $dynamicParams
    }
    process {
        $settings = [HashTable]$PSBoundParameters
        foreach ($settingItem in $PSBoundParameters.keys) {
            #Skip any custom parameters we may have added in the param block
            if ($settingItem -notin [SchemeList].DeclaredProperties.Name) { $settings.remove($settingItem) }
        }

        $newScheme = [SchemeList]$settings
        if ($PSCmdlet.ShouldProcess($TerminalSettings.Path, "Adding Color Scheme $nameToCompare")) {
            $nameToCompare = $newScheme.Name
            $ExistingSchema = $TerminalSettings.Schemes | Where-Object Name -eq $nameToCompare
            if ($ExistingSchema) {
                if (-not $Force) {
                    throwUser "$nameToCompare already exists as a color scheme, please specify -Force to overwrite."
                }
                [Void]$TerminalSettings.Schemes.Remove($ExistingSchema)
            }
            $TerminalSettings.Schemes.Add($newScheme) > $null
            Save-MSTerminalConfig -TerminalConfig $TerminalSettings
        }
    }
}
function Add-MSTerminalProfile {
    param(
        [Parameter(ValueFromPipeline)][ValidateNotNullOrEmpty()][WindowsTerminal.TerminalSettings]$InputObject = (Get-MSTerminalConfig),
        [Switch]$MakeDefault,
        [Switch]$Force
    )
    DynamicParam {
        $dynamicParams = Get-ObjectDynamicParameters 'WindowsTerminal.ProfileList' -MandatoryParameters 'Name' -ParameterOrder 'Name'
        Add-TerminalSettingsParams $dynamicParams 'WindowsTerminal.ProfileList'
        $dynamicParams
    }
    process {
        $settings = [HashTable]$PSBoundParameters
        foreach ($settingItem in $PSBoundParameters.keys) {
            #Skip any custom parameters we may have added in the param block
            if ($settingItem -notin [ProfileList].DeclaredProperties.Name) { [void]$settings.remove($settingItem) }
        }

        $newprofile = [ProfileList]$settings

        if (-not $newprofile.Guid) {
            #Generate a Guid if one wasn't specified
            $newprofile.Guid = [Guid]::newGuid().tostring('B')
        } else {
            $existingProfile = $InputObject.profiles.list | Where-Object guid -eq $newProfile.guid
        }

        if ($existingProfile -and -not $force) {
            throw "A profile with guid $($newProfile.Guid) already exists. If you wish to overwrite it please add the -Force parameter"
        }

        if ($existingProfile) {
            [void]$InputObject.Profiles.list.Remove($existingProfile)
        }

        $InputObject.Profiles.list.Add($NewProfile) > $null

        Save-MSTerminalConfig -TerminalConfig $InputObject

        if ($MakeDefault) {
            Set-MSTerminalConfig $InputObject -DefaultProfile $NewProfile.Guid
        }
    }
}
function Add-MSTerminalWordDelimiter {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        $Delimiter
    )
    if ($PSEdition -eq 'Desktop') {throw [NotImplementedException]'Word Delimiter commands do not work on Powershell 5.1 due to a Newtonsoft.Json issue. Please try again in Powershell 7+'}
    $Settings = Get-MSTerminalConfig
    $Delimiter.ToCharArray() | ForEach-Object {
        if($Settings.wordDelimiters -and !$Settings.wordDelimiters.Contains($_) -and $PSCmdlet.ShouldProcess("Add delimiters $Delimiter")) {
            $Settings.WordDelimiters += $_
            Set-MSTerminalConfig -WordDelimiters $Settings.WordDelimiters
        }
    }
}
function Disable-MSTerminalProfile {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)][Alias('Name','Guid')]$InputObject = (Get-MSTerminalProfile -DefaultSettings)
    )
    process {
        if ($PSCmdlet.ShouldProcess("$($InputObject.Name) $($InputObject.Guid)","Disable Profile")) {
            $InputObject | Set-MSTerminalProfile -Hidden
        }
    }
}
function Enable-MSTerminalProfile {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)][Alias('Name','Guid')]$InputObject = (Get-MSTerminalProfile -DefaultSettings)
    )
    process {
        if ($PSCmdlet.ShouldProcess("$($InputObject.Name) $($InputObject.Guid)","Disable Profile")) {
            $InputObject | Set-MSTerminalProfile -Hidden:$False
        }
    }
}
function Find-MSTerminalFolder {
    if($Script:TERMINAL_FOLDER) {
        $Script:TERMINAL_FOLDER
    } else {


        $WellKnownPaths = ResolveWellKnownPaths

        $Paths = @(
            (Join-Path $WellKnownPaths.LocalAppData $Script:RELEASE_PATH),
            (Join-Path $WellKnownPaths.LocalAppData $Script:RELEASE_PATH_ALT),
            (Join-Path $WellKnownPaths.AppData $Script:STANDALONE_PATH),
            (Join-Path $WellKnownPaths.LocalAppData $Script:DEV_PATH),
            (Join-Path $WellKnownPaths.LocalAppData $Script:DEV_PATH_ALT)
        )
        $FoundPath = $null
        foreach($Path in $Paths) {
            if(Test-Path $Path) {
                $FoundPath = $Path
                break
            }
        }
        if($FoundPath) {
            $FoundPath
        } else {
            Write-Error "Unable to locate Terminal settings.json file." -ErrorAction Stop
        }
    }
}

function Get-MSTerminalColorScheme {
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)][ValidateNotNullOrEmpty()][WindowsTerminal.TerminalSettings]$InputObject = (Get-MSTerminalConfig),
        #Exclude the default color schemes
        [Switch]$ExcludeDefault
    )
    dynamicParam {
        Get-ObjectDynamicParameters 'WindowsTerminal.SchemeList' -ParameterOrder Name
    }
    begin {
        if (-not $ExcludeDefault) {
            if (Get-Command 'get-appxpackage' -erroraction silentlyContinue) {
                $appxLocation = (get-appxpackage -erroraction silentlyContinue 'Microsoft.WindowsTerminal').installLocation
                if ($appxLocation) {
                    $defaultSettingsPath = Join-Path $appxLocation 'defaults.json'
                }
            }
            #If we cant get the defaults.json from the current terminal for whatever reason, use the module one
            if (-not $defaultSettingsPath) {
                Write-Debug "Unable to detect Windows Terminal, it may not be installed. Falling back to module-included default settings"
                if (Test-Path $moduleroot/TerminalSettingsDefaults.json) {
                    $defaultSettingsPath = Resolve-Path $moduleroot/TerminalSettingsDefaults.json
                } else {
                    $defaultSettingsPath = Resolve-Path $moduleroot/src/TerminalSettingsDefaults.json
                }
            }

            if (-not (Test-Path $defaultSettingsPath)) {
                #Don't issue warning for appveyor as this is expected
                if (-not $env:APPVEYOR) {
                    write-warning "Unable to detect default settings file, skipping the include of the default themes"
                }
            } else {
                #Powershell 5.1 doesn't support comments in Json
                #TODO: Remove Replace statement after deprecating 5.1 support
                #FIXME: Replace this with Get-MSTerminalConfig when this is fixed: https://github.com/microsoft/terminal/issues/5276
                [List[SchemeList]]$ColorScheme = (Import-JsonWithComments $DefaultSettingsPath).schemes
            }
        } else {
            [List[SchemeList]]$ColorScheme = @()
        }
    }
    process {
        foreach ($schemeItem in $InputObject.Schemes) {$ColorScheme.Add($schemeItem)}
        $filters = [HashTable]$PSBoundParameters
        $PSBoundParameters.keys.foreach{
            if ($PSItem -notin [SchemeList].DeclaredProperties.Name) { [void]$filters.Remove($PSItem) }
        }
        foreach ($filterItem in $filters.keys) {
            $ColorScheme = [SchemeList[]]$ColorScheme.where{$PSItem.$FilterItem -like $Filters.$FilterItem}
        }
        return [List[SchemeList]]$ColorScheme
    }
}
function Get-MSTerminalConfig {
    [CmdletBinding()]
    param (
        #Path to the settings.json settings file you want to work with. Defaults to the default location
        [String]$Path = (Join-Path (Find-MSTerminalFolder) 'settings.json')
    )
    try {
        [String]$jsonContent = if ($Path -match '^http') {
            Invoke-WebRequest -UseBasicParsing -ContentType 'application/json' -Uri $Path
        } else {
            Get-Content -raw $Path
        }
        $terminalSetting = [TerminalSettings]::FromJson(
            (
                $JsonContent
            )
        )

        #Append the path, this won't affect reserialization
        $terminalSetting | Add-Member -NotePropertyName 'Path' -NotePropertyValue $Path -Force
        return $terminalSetting

    } catch [Newtonsoft.Json.JsonSerializationException] {
        if ($PSItem -match "cannot deserialize.+ProfilesObject.+requires a json object") {
            throwuser "Error while parsing $Path`: This module only supports the newer 'Defaults and List' method of defining Windows Terminal profiles. Please edit your profile accordingly. See https://github.com/microsoft/terminal/blob/master/doc/user-docs/UsingJsonSettings.md#default-settings for details. The default settings.json file conforms to this format, so you can delete or move your profile and restart Windows Terminal to have it automatically created."
        } else {
            throw $PSItem
        }
    }
}
function Get-MSTerminalProfile {
    [CmdletBinding(DefaultParameterSetName='Filter')]
    param (
        [Parameter(ValueFromPipeline)][ValidateNotNull()][WindowsTerminal.TerminalSettings]$TerminalConfig = (Get-MSTerminalConfig),
        #Return a profile object representing the "defaults" section for the default settings for all profiles
        [Parameter(Mandatory,ParameterSetName='DefaultSettings')][Switch]$DefaultSettings,
        #Return the default configured profile
        [Parameter(Mandatory,ParameterSetName='Default')][Switch]$Default,
        #Return the current profile, if relevant
        [Parameter(Mandatory,ParameterSetName='Current')][Switch]$Current
    )
    dynamicParam {
        $dynamicParams = Get-ObjectDynamicParameters 'WindowsTerminal.ProfileList' -ParameterOrder Name,guid
        Add-TerminalSettingsParams $dynamicParams -Type 'WindowsTerminal.ProfileList'
        $dynamicParams.keys.foreach{
            $dynamicParams.$PSItem.attributes[0].ParameterSetName = 'Filter'
        }
        $dynamicParams
    }
    process {
        $WTProfile = switch ($PSCmdlet.ParameterSetName) {
            'DefaultSettings' {$TerminalConfig.Profiles.Defaults;break}
            'Default' {
                $TerminalConfig.Profiles.List.where{
                    [Guid]($_.Guid) -eq [Guid]$TerminalConfig.DefaultProfile
                }
                break
            }
            'Current' {
                Find-CurrentTerminalProfile
                break
            }
            Default {
                $filters = [HashTable]$PSBoundParameters
                $PSBoundParameters.keys.foreach{
                    if ($PSItem -notin [ProfileList].DeclaredProperties.Name) { [void]$filters.remove($PSItem) }
                }
                $ProfileList = $TerminalConfig.Profiles.List
                foreach ($filterItem in $filters.keys) {
                    $ProfileList = $ProfileList.where{$PSItem.$FilterItem -like $Filters.$FilterItem}
                }
                $ProfileList
            }
        }

        #Add the parent to the item to reference later for saving
        $WTProfile | Add-Member -NotePropertyName 'TerminalConfig' -NotePropertyValue $TerminalConfig -Force -PassThru
    }
}
function Import-Iterm2ColorScheme {
    [cmdletbinding(DefaultParameterSetName = 'Path')]
    param(
        [parameter(
            Mandatory = $true,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path,

        [parameter(
            Mandatory = $true,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        $Name,

        [Switch]$Force
    )
    process {
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            $ResolvedPaths = Resolve-Path -Path $Path
        } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $ResolvedPaths = Resolve-Path -LiteralPath $LiteralPath
        }

        $ResolvedPaths | ForEach-Object {
            $FileInfo = [System.IO.FileInfo]$_.Path
            $Colors = ConvertFrom-Iterm2ColorScheme -LiteralPath $_.Path -AsHashtable
            if(!$PSBoundParameters.ContainsKey("Name")) {
                $Name = $FileInfo.BaseName
            }
            Add-MSTerminalColorScheme -Name $Name @Colors -Force:$Force
        }
    }
}
function Invoke-MSTerminalGif {
    <#
.SYNOPSIS
    Plays a gif from a URI to the terminal. Useful when used as part of programs or build scripts to show "reaction gifs" to the terminal to events.
.DESCRIPTION
    This command plays animated GIFs on the Windows Terminal. It performs the operation in a background runspace and only allows one playback at a time. It also remembers your previous windows terminal settings and puts them back after it is done
.EXAMPLE
    PS C:\> Invoke-MSTerminalGif https://media.giphy.com/media/g9582DNuQppxC/giphy.gif
    Triggers a gif in the current Windows Terminal
#>

    [CmdletBinding(DefaultParameterSetName='Uri',SupportsShouldProcess)]
    param (
        #The URI of the GIF you want to display
        [Parameter(ParameterSetName='Uri',Position=0,Mandatory)][uri]$Uri,
        #The name or GUID of the Windows Terminal Profile in which to play the Gif.
        [Parameter(ValueFromPipeline)][Alias('Name','Guid')]$InputObject = (Get-MSTerminalProfile -Current),
        #How to resize the background image in the window. Options are None, Fill, Uniform, and UniformToFill
        [Parameter(ParameterSetName='Uri')][WindowsTerminal.BackgroundImageStretchMode]$BackgroundImageStretchMode = 'uniformToFill',
        #How transparent to make the background image. Default is 60% (.6)
        [Parameter(ParameterSetName='Uri')][float]$BackgroundImageOpacity = 0.6,
        #Specify this to use the Acrylic visual effect (semi-transparency)
        [Parameter(ParameterSetName='Uri')][switch]$UseAcrylic,
        #Maximum duration of the gif invocation in seconds
        [Parameter(ParameterSetName='Uri')][int]$MaxDuration = 5,
        #Occasionally, Invoke-TerminalGif may fail and leave your profile in an inconsistent state, run this command to restore the backed up profile
        [Parameter(ParameterSetName='Restore',Mandatory)][Switch]$Restore,
        #By default the backup is deleted after restoration, specify this to preserve it.
        [Parameter(ParameterSetName='Restore')][Switch]$NoClean,
        #Perform the task in the main process, rather than a separate runspace. Useful for troubleshooting or waiting for the gif to complete
        [Switch]$NoAsync
    )
    $ErrorActionPreference = 'Stop'
    #Sanity Checks
    if ($PSEdition -eq 'Desktop' -and -not (Get-Command start-threadjob -erroraction silentlycontinue)) {
        throw "This command requires the ThreadJob module on Windows Powershell 5.1. You can install it with the command Install-Module Threadjob -Scope CurrentUser"
        return
    }

    $wtProfile = Resolve-MSTerminalProfile $InputObject
    if (-not $wtProfile) { throw "Could not find the terminal profile $InputObject." }

    $profileBackupPath = Join-Path ([io.path]::GetTempPath()) "WTBackup-$($wtProfile.Guid).clixml"
    $profileBackupPathExists = Test-Path $profileBackupPath

    if ($Restore -or $profileBackupPathExists) {
        if (-not $Restore) {
            throwUser "A profile backup was found at $profileBackupPath. This usually means Invoke-MSTerminalGif was run incorrectly or terminated unexpectedly. Please run Invoke-MSTerminalGif -Restore to restore the profile or delete the file to continue."
        }
        if (-not $profileBackupPathExists) {
            throwUser "Restore was requested but no backup file was found at $profileBackupPath. This usually means it was already restored and you can continue normally."
        }
        if ($PSCmdlet.ShouldProcess($profileBackupPath, "Restoring $profileBackupPath Profile")) {
            $existingProfileSettings = Import-Clixml $profileBackupPath
            Set-MSTerminalProfile -InputObject $wtProfile @existingProfileSettings
            if (-not $NoClean) {
                Remove-Item $profileBackupPath
            }
        }
        #Exit after restoring the backup profile
        return
    }

    #Pseudo Singleton to ensure only one prompt job is running at a time
    $InvokeTerminalGifJobName = "InvokeTerminalGif-$($InputObject.Guid)"
    $InvokeTerminalGifJob = Get-Job $InvokeTerminalGifJobName -Erroraction SilentlyContinue
    if ($invokeTerminalGifJob) {
        if ($invokeTerminalGifJob.state -notmatch 'Completed|Failed') {
            Write-Warning "Last Terminal Gif Is Still Running"
            return
        } elseif ($invokeTerminalGifJob.state -eq 'Failed') {
            #TODO: Replace this with an event hook on job completion maybe: https://stackoverflow.com/a/38912216/12927399
            Write-Warning "Previous InvokeTerminalGif job failed! Output is below..."
            Receive-Job $InvokeTerminalGifJob -ErrorAction Continue
            Remove-Job $InvokeTerminalGifJob
        } else {
            Remove-Job $InvokeTerminalGifJob
        }
    }

    #Prepare arguments for the threadjob
    $terminalProfile = $wtProfile
    $ModulePath = Join-Path $ModuleRoot 'MSTerminalSettings.psd1'
    $BackgroundImage = $Uri

    $InvokeTerminalGifArgs = @(
        'TerminalProfile',
        'Uri',
        'ProfileBackupPath',
        'ModulePath',
        'BackgroundImage',
        'BackgroundImageOpacity',
        'BackgroundImageStretchMode',
        'MaxDuration',
        'UseAcrylic'
    ).foreach{
        Get-Variable -Name $PSItem -ValueOnly -ErrorAction Stop
    }

    if ($InvokeTerminalGifJob -and $InvokeTerminalGifJob.state -notmatch 'Completed|Failed') {
        Write-Warning "Invoke Terminal Already Running"
        return
    }

    $InvokeTerminalGifScriptBlock = {
        param(
            [Parameter(Mandatory)]$TerminalProfile,
            [Parameter(Mandatory)][Uri]$Uri,
            [Parameter(Mandatory)][String]$ProfileBackupPath,
            [Parameter(Mandatory)]$ModulePath,
            $BackgroundImage,
            $BackgroundImageOpacity,
            $BackgroundImageStretchMode,
            $MaxDuration,
            $UseAcrylic
        )
        $ErrorActionPreference = 'stop'
        try {
            #Back up the relevant existing settings
            $existingProfileSettings = @{}
            ('BackgroundImage','UseAcrylic','BackgroundImageOpacity','BackgroundImageStretchMode').foreach{
                $existingProfileSettings[$PSItem] = $terminalprofile.$PSItem
            }

            Export-Clixml -InputObject $existingProfileSettings -Path $profileBackupPath
            Import-Module $ModulePath

            if (-not $terminalProfile) { throw "Could not find the terminal profile $($terminalProfile.Name)." }

            Write-Verbose "Playing $uri in $($terminalProfile.Name) for $($MaxDuration) seconds"

            $SetMSTerminalParams = @{
                InputObject = $TerminalProfile
                BackgroundImage = $Uri
                UseAcrylic = $UseAcrylic
                BackgroundImageOpacity = $BackgroundImageOpacity
                BackgroundImageStretchMode = $BackgroundImageStretchMode
            }
            Set-MSTerminalProfile @SetMSTerminalParams
            Start-Sleep $Maxduration
        } catch {
            Invoke-MSTerminalGif -Restore
            Write-Error "Error Encountered: $PSItem. Restored existing backup $profileBackupPath"
        } finally {
            Write-Debug "===Settings to Revert==="
            $ExistingProfileSettings | Out-String | Write-Debug
            #Revert the previous settings
            $wtProfile = Get-MSTerminalProfile -Guid ([Guid]$TerminalProfile.Guid).ToString('B')
            $existingProfileSettings.keys.foreach{
                $wtProfile.$PSItem = $existingProfileSettings[$PSItem]
            }
            Write-Debug "===Expected Result==="
            $wtProfile | Format-List | Out-String | Write-Debug
            Write-Debug "Action: Saving to File"
            Save-MSTerminalConfig -TerminalConfig $wtProfile.TerminalConfig -ErrorAction Stop
            Remove-Item $profileBackupPath
        }
    }

    $startJobParams = @{
        ScriptBlock = $InvokeTerminalGifScriptBlock
        ArgumentList = $InvokeTerminalGifArgs
    }

    $invokeTerminalGifPostRunHandler = {
        $ErrorActionPreference = 'Stop'
        try {
            switch ($eventArgs.JobStateInfo) {
                'Completed' {
                    Write-Verbose "Invoke-MSTerminalGif Job Completed"
                    Remove-Job $Sender -Force
                    Remove-Job -Id $Event.EventIdentifier -Force
                }
                'Failed' {
                    Write-Host -fore Cyan (Receive-Job $Sender -ErrorAction SilentlyContinue -WarningAction silentlycontinue -ErrorVariable jobError -WarningVariable jobWarn)
                    if ($jobError) {Write-Host -fore Red "Invoke-MSTerminalGifJob ERROR: $jobError"}
                    if ($jobWarn) {Write-Host -fore Orange "Invoke-MSTerminalGifJob WARNING: $jobWarn"}
                    # $profileGuid = $sender.Name -replace 'InvokeTerminalGif-',''
                    # $profileBackupPath = Join-Path ([io.path]::GetTempPath()) "WTBackup-$profileGuid.clixml"
                    # write-host -fore yellow $profileBackupPath
                    # write-host -fore yellow (Test-Path $profileBackupPath)
                    Remove-Job $Sender -Force
                    Remove-Job -Id $Event.EventIdentifier -Force
                }
                default {return}
            }
        } catch {
            #Exceptions don't emit from event handlers
            Write-Host -fore red "InvokeTerminalGifPostRunHandler ERROR: $PSItem"
        }
    }

    if ($NoAsync) {
        Invoke-Command @startJobParams
    } else {
        $invokeTerminalGifJob = Start-ThreadJob @startJobParams -Name $InvokeTerminalGifJobName
        $invokeTerminalGifPostRunHandlerParams = @{
            InputObject = $invokeTerminalGifJob
            EventName = 'StateChanged'
            Action = $invokeTerminalGifPostRunHandler
            MaxTriggerCount = 2
        }
        Register-ObjectEvent @invokeTerminalGifPostRunHandlerParams > $null
    }
}
function Remove-MSTerminalColorScheme {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipelineByPropertyName)][String]$Name,
        [ValidateNotNullOrEmpty()][WindowsTerminal.TerminalSettings]$TerminalSettings = (Get-MSTerminalConfig)
    )
    process {
        $SchemeList = $TerminalSettings.Schemes
        $SchemeToRemove = $SchemeList.Where{[String]$_.Name -eq $Name}
        if (-not $SchemeToRemove) {throw "Could not find Scheme with Name $Name"}
        if ($SchemeToRemove.count -gt 1) {throw "Multiple Schemes found with Name $Name. Please check your configuration and remove the duplicate"}
        if ($PSCmdlet.ShouldProcess(
            $TerminalSettings.Path,
            "Removing Scheme $($SchemeToRemove.Name) $($SchemeToRemove.Name)"
        )) {
            [void]$SchemeList.Remove($SchemeToRemove[0])
            Save-MSTerminalConfig -TerminalConfig $TerminalSettings
        }
    }
}
function Remove-MSTerminalProfile {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory,ValueFromPipeline)][Alias('Name','Guid')]$InputObject
    )
    process {
        $ProfileToRemove = Resolve-MSTerminalProfile $InputObject

        if (-not $ProfileToRemove) {throw "Could not find matching profile to remove"}
        $TerminalConfig = $ProfileToRemove.TerminalConfig
        if ($PSCmdlet.ShouldProcess(
            $TerminalConfig.Path,
            "Removing Profile $($ProfileToRemove.Name) $($ProfileToRemove.Guid)"
        )) {
            [void]$TerminalConfig.Profiles.List.Remove($ProfileToRemove[0])
            Save-MSTerminalConfig -TerminalConfig $TerminalConfig
        }
    }
}
function Remove-MSTerminalWordDelimiter {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "Changed")]
    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        $Delimiter
    )
    if ($PSEdition -eq 'Desktop') {throw [NotImplementedException]'Word Delimiter commands do not work on Powershell 5.1 due to a Newtonsoft.Json issue. Please try again in Powershell 7+'}
    $Settings = Get-MSTerminalConfig
    $Changed = $false
    $Delimiter.ToCharArray() | ForEach-Object {
        if($Settings.WordDelimiters -and $Settings.wordDelimiters.Contains($_) -and $PSCmdlet.ShouldProcess("Remove delimiter $_")) {
            $Settings.wordDelimiters = $Settings.WordDelimiters.Replace([String]$_, "")
            $Changed = $true
        }
    }
    if ($Changed) {
        Set-MSTerminalConfig -WordDelimiters $Settings.wordDelimiters
    }
}
function Save-MSTerminalConfig {
    [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='Path')]
    param (
        [Parameter(Position=0,ValueFromPipeline)][WindowsTerminal.TerminalSettings]$TerminalConfig = (Get-MSTerminalConfig),
        [IO.FileInfo]$Path,
        [Switch]$PassThru
    )

    if ($TerminalConfig -and -not $Path -and -not $TerminalConfig.Path) {throw 'You specified a generated MSTerminalConfig object, and therefore must specify an output path with -Path to use this command'}
    if (-not $Path -and $TerminalConfig.Path) {$Path = $TerminalConfig.Path}
    if ($PSCmdlet.ShouldProcess($TerminalConfig.Path,'Saving Terminal Settings to File')) {

        $configToSave = [Serialize]::ToJson($TerminalConfig)
        #Powershell 5.1 Compatible Option to output UTF8 without BoM
        #TODO: Remove when Powershell 5.1 support is dropped
        if ($PSEdition -eq 'Desktop') {
            [io.file]::WriteAllLines($Path,$configToSave,[text.utf8encoding]::new($false))
        } else {
            Out-File -InputObject $configToSave -Encoding utf8NoBOM -FilePath $Path -Force
        }
        #Slight pause to allow Windows Terminal to catch up
        #TODO: Some sort of event maybe?
        sleep 0.1
    }
    if ($PassThru) {$TerminalConfig}
    #TODO: Parse the error and find where the errors are
}
function Set-MSTerminalConfig {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Position=0,ValueFromPipeline)][ValidateNotNullOrEmpty()][WindowsTerminal.TerminalSettings]$TerminalConfig = (Get-MSTerminalConfig)
    )
    DynamicParam {
        $dynamicParams = Get-ObjectDynamicParameters 'WindowsTerminal.TerminalSettings'
        Add-TerminalSettingsParams $dynamicParams 'WindowsTerminal.TerminalSettings'
        $dynamicParams
    }
    process {
        $settings = [HashTable]$PSBoundParameters
        foreach ($settingItem in $PSBoundParameters.keys) {
            #Skip any custom parameters we may have added in the param block
            if ($settingItem -notin [TerminalSettings].DeclaredProperties.Name) { continue }

            if ($PSCmdlet.ShouldProcess($TerminalConfig.Path,"Setting $settingItem to $($settings[$settingItem])")) {
                $TerminalConfig.$settingItem = $settings[$SettingItem]
            }
        }
    }
    end {
        if ($PSCmdlet.ShouldProcess($TerminalConfig.Path,"Saving Configuration")) {
            Save-MSTerminalConfig -TerminalConfig $TerminalConfig
        }
    }
}
function Set-MSTerminalProfile {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(ValueFromPipeline)][Alias('Name','Guid')]$InputObject = (Get-MSTerminalProfile -DefaultSettings),
        #Set the profile as the default profile used for new tabs and on startup
        [Switch]$MakeDefault
    )
    DynamicParam {
        Get-ObjectDynamicParameters 'WindowsTerminal.ProfileList' -Rename @{
            Name = 'NewName'
            Guid = 'NewGuid'
        }
    }
    begin {
        $terminalConfig = @{}
        [int]$profileCount = 0
    }
    process { foreach ($ProfileItem in $InputObject) {
        $profileCount++
        #Resolve the input object
        $ProfileItem = Resolve-MSTerminalProfile $ProfileItem

        if ($makeDefault -and $ProfileItem -is [Profile]) {
            throwUser 'You cannot set the defaultsettings as the default profile'
        }

        $settings = [HashTable]$PSBoundParameters

        #Translate 'New' parameters back to settings parameters
        $PSBoundParameters.keys.where{$PSItem -match '^New(\w+)$'}.foreach{
            $settings.($matches[1]) = $Settings.$PSItem
        }

        if ($settings.NewGuid) {$settings.Guid = $settings.NewGuid}
        if ($settings.NewName) {$settings.Name = $settings.NewName}

        $TerminalConfig[$ProfileItem.TerminalConfig.Path] = $ProfileItem.TerminalConfig
        #Operate on the TerminalConfig-Bound Object because foreach created a clone.
        if ($ProfileItem -is [Profile]) {
            $ProfileItem = $ProfileItem.TerminalConfig.profiles.defaults
        } else {
            $ProfileItem = $ProfileItem.TerminalConfig.profiles.list.where{$PSItem.Guid -eq $ProfileItem.Guid}[0]
        }

        foreach ($settingItem in $settings.keys) {
            #Skip any custom parameters we may have added in the param block
            if ($settingItem -notin [ProfileList].DeclaredProperties.Name) { continue }

            #Better message for the profile defaults
            $ProfileMessageName = if ($ProfileItem -is [WindowsTerminal.Profile]) {'[Default Settings]'} else {$ProfileItem.Name}

            #Prevent a blank space in the message
            $settingMessageValue = if ($null -eq $settings[$settingItem]) {'[null]'} else {$settings[$settingItem]}

            if ($PSCmdlet.ShouldProcess("Profile $ProfileMessageName","Set $settingItem to $settingMessageValue")) {
                $ProfileItem.$settingItem = $settings[$settingItem]
            }
        }
    }}
    end {
        #Sanity Checks
        if ($makeDefault -and $profileCount -gt 1) {
            throwUser 'You cannot specify -MakeDefault with more than one profile'
        }

        $terminalConfig.keys.foreach{
            if ($MakeDefault -and $PSCmdlet.ShouldProcess($ProfileItem.Name, 'Setting as Default Profile')) {
                $terminalConfig[$PSItem].DefaultProfile = $InputObject.Guid
            }
            if ($PSCmdlet.ShouldProcess($PSItem,'Saving Configuration')) {
                Save-MSTerminalConfig $terminalConfig[$PSItem]
            }
        }
    }
}
function Set-MSTerminalTargetInstallation {
    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    param(
        [Parameter(Mandatory=$true,ParameterSetName="Builtin")]
        [ValidateSet("Dev","Release","Standalone","Clear")]
        $Type,

        [Parameter(Mandatory=$true,ParameterSetName="Custom")]
        $Path
    )
            #FIXME: Remove When Refactored
            throwuser $QuickTypeNotImplementedException
    $Paths = ResolveWellKnownPaths
    if($PSCmdlet.ParameterSetName -eq "Builtin") {
        Switch ($Type) {
            "Dev" {
                $Script:TERMINAL_FOLDER = Join-Path $Paths.LocalAppData $Script:DEV_PATH
            }
            "Release" {
                $Script:TERMINAL_FOLDER = Join-Path $Paths.LocalAppData $Script:RELEASE_PATH
            }
            "Standalone" {
                $Script:TERMINAL_FOLDER = Join-Path $Paths.AppData $Script:STANDALONE_PATH
            }
            "Clear" {
                $Script:TERMINAL_FOLDER = ""
            }
        }
    } else {
        $Script:TERMINAL_FOLDER = ""
    }
}


#Add a not implemented exception for work in progress

$SCRIPT:QuickTypeNotImplementedException = [notimplementedexception]'This command has not been reimplemented for the new version yet. Stay tuned!'

#Terminal Settings Schema Path
$SCRIPT:TerminalSettingsSchemaPath = "$PSScriptRoot/TerminalSettingsSchema.json"