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" |