Theme.WindowsTerminal.psm1

using namespace PoshCode.Pansies
#Region '.\Private\FindProfile.ps1' 0
function FindProfile {
    [CmdletBinding()]
    param(
        # A collection of profiles to search by GUID
        $Profiles = @((GetLayeredConfig -FlattenDefaultProfile).profiles.list),

        # If the only thing we care about is the color scheme, we can take shortcuts
        [Switch]$ColorSchemeReadOnly,

        # The profile guid. Default value from Env:WT_PROFILE_ID
        $ProfileId = $Env:WT_PROFILE_ID
    )

    if (!$ProfileId) {
        Write-Warning "ENV:WT_PROFILE_ID should always be set when you're in Windows Terminal. Without that, we cannot identify the current profile. Please ensure you're running Windows Terminal 0.11 or higher."
    } else {
        if (($P = $Profiles.Where({ $_.guid -eq $ProfileId }))) {
            $P
        } else {
            Write-Error "The ProfileId should be a guid set in ENV:WT_PROFILE_ID"
        }
    }
}
#EndRegion '.\Private\FindProfile.ps1' 24
#Region '.\Private\GetLayeredConfig.ps1' 0
function GetLayeredConfig {
    [CmdletBinding()]
    param(
        # If set, update the profiles with values from profiles.defaults
        [switch]$FlattenDefaultProfile
    )

    # Hypothetically the file is in the first location, but if you're using a dev copy instead, it might be elsewhere:
    if (!$script:UserConfigFile -or !$script:DefaultConfig) {
        $wt = @(Get-Process WindowsTerminal -ErrorAction Ignore)
        if ($wt.count -eq 1) {
            $wtExecutable = $wt.Path
        } else {
            $ps = Get-Process -Id $Pid
            while ($ps.ProcessName -ne "WindowsTerminal" -and $ps.Parent) {
                $ps = $ps.Parent
            }
            if ($ps.ProcessName -eq "WindowsTerminal") {
                $wtExecutable = $ps.Path
            }
        }

        if ($wtExecutable) {
            $DefaultConfigFile = Get-ChildItem ($wtExecutable | Split-Path) -Filter defaults.json | Convert-Path

            if (!$DefaultConfigFile) {
                Write-Warning "Unable to locate Windows Terminal's default.json"
            } else {
                $script:DefaultConfig = if ($PSVersionTable.PSVersion.Major -gt 5) {
                    ConvertFrom-Json (Get-Content $DefaultConfigFile -Raw)
                } else {
                    # WindowsPowerShell's JSON can't handle comments
                    ConvertFrom-Json (((Get-Content $DefaultConfigFile) -replace "^\s*//.*") -join "`n")
                }
            }
        }

        $ProductKey = switch -regex ($wtExecutable) {
            "Microsoft\.WindowsTerminalPreview_" { "Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe" }
            "WindowsTerminalDev" { "WindowsTerminalDev_8wekyb3d8bbwe" }
            # "Microsoft\.WindowsTerminal_" { "Microsoft.WindowsTerminal_8wekyb3d8bbwe" }
            default { "Microsoft.WindowsTerminal_8wekyb3d8bbwe" }
        }

        $script:UserConfigFile = Get-ChildItem @(
            "$Env:LocalAppData/packages/$ProductKey/LocalState/settings.json"
            "$Env:AppData/Microsoft/Windows Terminal/settings.json"
        ) -ErrorAction Ignore | Select-Object -First 1
    }

    if (!$UserConfigFile) {
        Write-Warning "Unable to locate Windows Terminal's settings.json"
    } else {
        $UserConfig = if ($PSVersionTable.PSVersion.Major -gt 5) {
                ConvertFrom-Json (Get-Content $UserConfigFile -Raw)
            } else { # WindowsPowerShell's JSON can't handle comments
                ConvertFrom-Json (((Get-Content $UserConfigFile) -replace "^\s*//.*") -join "`n")
            }
    }

    # The profiles are different in default, so we normalize that first ...
    # The only way to normalize it is to add the `default`:
    if (!(Get-Member Defaults -Input $UserConfig.Profiles)) {
        $UserConfig.profiles = [PSCustomObject]@{
            defaults = [PSCustomObject]@{ }
            list     = $UserConfig.profiles
        }
    }
    if (!(Get-Member Defaults -input $DefaultConfig.Profiles)) {
        $DefaultConfig.profiles = [PSCustomObject]@{
            defaults = [PSCustomObject]@{ }
            list     = $DefaultConfig.profiles
        }
    }

    # Update-Object doesn't handle arrays at all, so we need to deal with those manually
    for ($u = 0; $u -lt $UserConfig.profiles.list.Count; $u++) {
        if (($d = $DefaultConfig.profiles.list.guid.IndexOf($UserConfig.profiles.list[$u].guid)) -ge 0) {
            # Which is fine, because maybe we need to start with the default profile
            if ($FlattenDefaultProfile -and $UserConfig.profiles.defaults) {
                $UserConfig.profiles.list[$u] = $DefaultConfig.profiles.list[$d] |
                    Update-Object ($UserConfig.profiles.defaults | Select-Object *) |
                    Update-Object $UserConfig.profiles.list[$u]
            } else {
                $UserConfig.profiles.list[$u] = $DefaultConfig.profiles.list[$d] |
                    Update-Object $UserConfig.profiles.list[$u]
            }
        } elseif ($FlattenDefaultProfile -and $UserConfig.profiles.defaults) {
            $UserConfig.profiles.list[$u] = $UserConfig.profiles.defaults | Select-Object * |
                    Update-Object $UserConfig.profiles.list[$u]
        }
    }
    for ($u = 0; $u -lt $UserConfig.schemes.Count; $u++) {
        if (($d = $DefaultConfig.schemes.name.IndexOf($UserConfig.schemes[$u].name)) -ge 0) {
            $UserConfig.schemes[$u] = $DefaultConfig.schemes[$d] | Update-Object $UserConfig.schemes[$u]
        }
    }
    # Let's be honest, schemes that are in the default probably aren't in UserConfig, so copy them:
    $existing = $UserConfig.schemes.name
    $UserConfig.schemes += @($DefaultConfig.schemes).where{$_.name -notin $existing}

    # Finally, copy over everything else
    Update-Object -InputObject ($DefaultConfig | Select-Object *) -UpdateObject $UserConfig
}
#EndRegion '.\Private\GetLayeredConfig.ps1' 105
#Region '.\Private\GetSimpleConfig.ps1' 0
function GetSimpleConfig {
    [CmdletBinding()]
    param()

    # Hypothetically the file is in the first location, but if you're using a dev copy instead, it might be elsewhere:
    if (!$script:UserConfigFile) {
        $script:UserConfigFile = Get-ChildItem @(
            "$Env:LocalAppData/packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json"
            "$Env:LocalAppData/packages/WindowsTerminalDev_8wekyb3d8bbwe/LocalState/settings.json"
            "$Env:AppData/Microsoft/Windows Terminal/settings.json"
            "$Env:LocalAppData/packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/profile.json"
            "$Env:LocalAppData/packages/WindowsTerminalDev_8wekyb3d8bbwe/LocalState/profile.json"
            "$Env:AppData/Microsoft/Windows Terminal/profile.json"
            ) -ErrorAction Ignore | Select-Object -First 1
    }

    if (!$UserConfigFile) {
        Write-Warning "Unable to locate Windows Terminal's settings.json"
    } else {
        Write-Verbose $UserConfigFile
        $UserConfig = ConvertFrom-Json (Get-Content $UserConfigFile -Raw)
    }

    # The profiles exist two different ways, so we have to normalize that first ...
    # The only way to normalize it is to add the `default`:
    if (!(Get-Member Defaults -Input $UserConfig.Profiles)) {
        $UserConfig.profiles = [PSCustomObject]@{
            defaults = [PSCustomObject]@{ }
            list     = $UserConfig.profiles
        }
    }

    $UserConfig
}
#EndRegion '.\Private\GetSimpleConfig.ps1' 35
#Region '.\Public\Copy-WindowsTerminalProfile.ps1' 0
function Copy-WindowsTerminalProfile {
    <#
        .SYNOPSIS
            Creates a new WindowsTerminalProfile based on another
    #>

    [Alias("cptp")]
    [CmdletBinding(DefaultParameterSetName="SimpleCopy")]
    param(
        # The name of the profile to copy. By default, copies the current profile
        [Parameter()]
        [string]$SourceProfileName,

        # The name of the new profile. If it already exists, it will be overwritten
        [Parameter(Mandatory, Position = 0)]
        [string]$NewProfileName,

        # A command-line to override the one in the source profle
        [Parameter(Position = 1)]
        [string]$CommandLine,

        # If set, creates a new tab immediately from the profile
        [switch]$CreateTab,

        # If set, creates a new tab and immediately removes the profile
        [switch]$AutoDelete,

        # If set, this text is written to the bottom right corner of the background image
        [Parameter(Position = 2, ParameterSetName = "WithText")]
        [string]$TextForBackground,

        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.Size]$ImageSize,

        # Number of pixels of padding for the text on the left. By default, 20 pixels.
        [Parameter(ParameterSetName = "WithText")]
        [int]$LeftPadding = 20,

        # Number of pixels of padding for the text on the top. By default, 20 pixels.
        [Parameter(ParameterSetName = "WithText")]
        [int]$TopPadding = 20,

        # Number of pixels of padding for the text on the right. By default, 20 pixels.
        [Parameter(ParameterSetName = "WithText")]
        [int]$RightPadding = 20,

        # Number of pixels of padding for the text on the bottom. By default, 20 pixels.
        [Parameter(ParameterSetName = "WithText")]
        [int]$BottomPadding = 20,

        # The font name (defaults to "Cascadia Code")
        [Parameter(ParameterSetName = "WithText")]
        [string]$FontName = "Cascadia Code",

        # The font size (defaults to 28)
        [Parameter(ParameterSetName = "WithText")]
        [int]$FontSize = 28,

        # The font style defaults to bold
        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.FontStyle]$FontStyle = "Bold",

        # The alignment relative to the background + bounds.
        # In a left-to-right layout, the far position is bottom, and the near position is top.
        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.StringAlignment]$VerticalAlignment = "Far",

        # The alignment relative to the background + bounds.
        # In a left-to-right layout, the far position is right, and the near position is left.
        # However, in a right-to-left layout, the far position is left.
        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.StringAlignment]$HorizontalAlignment = "Far",

        # The stroke color defaults to [System.Drawing.Brushes]::Black
        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.Brush]$StrokeBrush = [System.Drawing.Brushes]::Black,

        # The fill color defaults to [System.Drawing.Brushes]::White
        [Parameter(ParameterSetName = "WithText")]
        [System.Drawing.Brush]$FillBrush = [System.Drawing.Brushes]::White
    )
    process {
        $LayeredConfig = GetLayeredConfig -FlattenDefaultProfile
        $Config = GetSimpleConfig

        if ($SourceProfileName) {
            $SourceProfile = @($LayeredConfig.profiles.list).Where({ $_.Name -eq $SourceProfileName }, "First", 1)[0] | Select-Object *
        }
        if (!$SourceProfile) {
            $SourceProfile = FindProfile $LayeredConfig.profiles.list
        }

        if (!($ActualProfile = @($Config.profiles.list).Where({ $_.Name -eq $SourceProfile.Name }, "First", 1)[0] | Select-Object * -ExcludeProperty "source", "hidden")) {
            $ActualProfile = $SourceProfile | Select-Object * -ExcludeProperty "source", "hidden"
        }

        $ActualProfile | Add-Member -NotePropertyName name $NewProfileName -force
        $ActualProfile | Add-Member -NotePropertyName guid ([guid]::NewGuid().ToString("b")) -force
        $ActualProfile | Add-Member -NotePropertyName commandline $CommandLine -force

        if ($TextForBackground) {
            # Make a copy of the PSBoundParameters that only has parameters from Write-TextOnImage
            $TextParameters = @{} + $PSBoundParameters
            $ParameterNames = (Get-Command Write-TextOnImage).Parameters.Keys
            @($PSBoundParameters.Keys.
                Where({ $_ -notin $ParameterNames })).
                ForEach({ $TextParameters.Remove($_) })

            try {
                $TerminalTempState = Join-Path $Env:LocalAppData "packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/TempState"
                if ($SourceProfile.backgroundImage) {
                    $TerminalRoamingState = Join-Path $Env:LocalAppData "packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/RoamingState/"
                    $TerminalLocalState = Join-Path $Env:LocalAppData "packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/"
                    $Path = $SourceProfile.backgroundImage -replace "ms-appdata:///roaming/", $TerminalRoamingState -replace "ms-appdata:///local/", $TerminalLocalState
                    Write-Verbose "Existing Background Image: $Path"
                    $BackgroundFile = Write-TextOnImage -Path $Path -Text $TextForBackground @TextParameters
                    $ActualProfile | Add-Member -NotePropertyName backgroundImage $BackgroundFile.FullName -Force
                } else {
                    $Path = Join-Path $TerminalTempState (Get-Date -f "yyyyMMddhhmmss.pn\g")
                    $BackgroundFile = Write-TextOnImage -NewPath $Path -ImageSize ([System.Drawing.Size]::new(256,256)) -Text $TextForBackground @TextParameters
                    $ActualProfile | Add-Member -NotePropertyName backgroundImage $BackgroundFile.FullName -Force
                }
            } catch {
                Write-Error "Couldn't update the background image. You may need to use a full file path or ms-appdata:/// path." -ErrorAction Continue
            }
        }

        $Config.profiles.list += $ActualProfile

        Set-Content $UserConfigFile ($Config | ConvertTo-Json -Depth 10)

        if ($CreateTab -or $AutoDelete) {
            $DefaultProfile = $Config.defaultProfile
            $DefaultKeyBindings = $Config.keybindings

            $Config.defaultProfile = $ActualProfile.guid

            # Force the newTab hotkey to something
            $NewTab = @($LayeredConfig.keybindings).where({ $_.command -eq "newTab" }, "First", 1)[0]
            $NewTab.keys = @( "ctrl+t" )
            $Config.keybindings = @($NewTab)
            Set-Content $UserConfigFile ($Config | ConvertTo-Json -Depth 10)
            Start-Sleep 1 # you have to wait for the terminal to _notice_ the config change

            # Create a new tab and then set the old default back
            Add-Type -AssemblyName System.Windows.Forms
            [System.Windows.Forms.SendKeys]::SendWait("^t")

            $Config.defaultProfile = $DefaultProfile
            $Config.keybindings = $DefaultKeyBindings
            if ($AutoDelete) {
                $Config.profiles.list = @($Config.profiles.list).Where{ $_.guid -ne $ActualProfile.guid }
            }
            Set-Content $UserConfigFile ($Config | ConvertTo-Json -Depth 10)
        }
    }
}
#EndRegion '.\Public\Copy-WindowsTerminalProfile.ps1' 157
#Region '.\Public\Get-WindowsTerminalTheme.ps1' 0
#using namespace PoshCode.Pansies

function Get-WindowsTerminalTheme {
    <#
        .SYNOPSIS
            Returns an object representing the color scheme from the Windows Terminal settings
        .DESCRIPTION
            The expectation is that you call Get-WindowsTerminalTheme, modify the result,
            then pipe the modified value into Set-WindowsTerminalTheme.
 
            By default (without parameters), this function detects the current profile via Env:WT_PROFILE_ID
            and returns the color scheme associated with that profile.
        .LINK
            Set-WindowsTerminalTheme
    #>

    [CmdletBinding()]
    param(
        # If specified, returns the named color scheme instead of the one associated with the current profile
        [Parameter(ValueFromPipeline)]
        [string]$ColorScheme
    )
    process {
        $Config = GetLayeredConfig -FlattenDefaultProfile

        if (!$ColorScheme) {
            $ActiveProfile = FindProfile $Config.profiles.list
            $ColorScheme = $ActiveProfile.ColorScheme
        }

        if ($ColorScheme) {
            $Result = @($Config.schemes).Where({ $_.name -eq $colorScheme })[0]
            $Result.PSTypeNames.Insert(0, "Terminal.ColorScheme")
            $Result.PSTypeNames.Insert(0, "WindowsTerminal.ColorScheme")
            if ($ActiveProfile) {
                # Update with overrides from the active profile
                if ($ActiveProfile.foreground) { # -and -not $Result.foreground
                    Add-Member -InputObject $Result -NotePropertyMembers @{ foreground = $ActiveProfile.foreground } -Force
                }
                if ($ActiveProfile.background) { # -and -not $Result.background
                    Add-Member -InputObject $Result -NotePropertyMembers @{ background = $ActiveProfile.background } -Force
                }
                if ($ActiveProfile.cursorColor) { # -and -not $Result.cursorColor
                    Add-Member -InputObject $Result -NotePropertyMembers @{ cursorColor = $ActiveProfile.cursorColor } -Force
                }
                if (!$Result.cursorColor) {
                    Add-Member -InputObject $Result -NotePropertyMembers @{ cursorColor = $Result.foreground } -Force
                }
            }

            # Since all the properties are colors, we cast them to RgbColor for display purposes
            foreach ($property in @(Get-Member -Input $Result -Type Properties).Where({$_.Name -ne "name"}).name) {
                $Result.$property = [RgbColor]$Result.$property
            }
            $Result
        }
    }
}
#EndRegion '.\Public\Get-WindowsTerminalTheme.ps1' 58
#Region '.\Public\Set-WindowsTerminalTheme.ps1' 0
#using namespace PoshCode.Pansies

function Set-WindowsTerminalTheme {
    <#
        .SYNOPSIS
            Set the theme for Windows Terminal
            Has parameters for each color, including foreground, background, and cursorColor
        .DESCRIPTION
            The expectation is that you call Get-WindowsTerminalTheme and modify the result,
            then pipe the modified value into Set-WindowsTerminalTheme
        .LINK
            Get-WindowsTerminalTheme
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Name           = "EzTheme",

        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$background   = "#0C0C0C",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$foreground   = "#CCCCCC",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$cursorColor  = "#FFFFFF",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$selectionBackground = "#FFFFFF",

        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$black        = "#0C0C0C",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$red          = "#C50F1F",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$green        = "#13A10E",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$yellow       = "#C19C00",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$blue         = "#0037DA",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$purple       = "#881798",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$cyan         = "#3A96DD",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$white        = "#CCCCCC",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightBlack  = "#767676",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightRed    = "#E74856",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightGreen  = "#16C60C",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightYellow = "#F9F1A5",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightBlue   = "#3B78FF",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightPurple = "#B4009E",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightCyan   = "#61D6D6",
        [Parameter(ValueFromPipelineByPropertyName)]
        [RgbColor]$brightWhite  = "#F2F2F2"
    )

    begin {
        $Config = GetLayeredConfig -FlattenDefaultProfile
        $CurrentProfile = FindProfile $Config.profiles.list
    }
    process {
        $UserConfig = ConvertFrom-Json (Get-Content $UserConfigFile -Raw)
        $SchemeIndex = $UserConfig.schemes.name.IndexOf($name)

        # Make sure we're using simple strings that match the json Windows Terminal needs
        $Scheme = [PSCustomObject]@{
            name         = "$Name"
            background   = "$background"
            foreground   = "$foreground"
            selectionBackground = "$selectionBackground"
            cursorColor         = "$cursorColor"

            black        = "$black"
            red          = "$red"
            green        = "$green"
            yellow       = "$yellow"
            blue         = "$blue"
            purple       = "$purple"
            cyan         = "$cyan"
            white        = "$white"
            brightBlack  = "$brightBlack"
            brightRed    = "$brightRed"
            brightGreen  = "$brightGreen"
            brightYellow = "$brightYellow"
            brightBlue   = "$brightBlue"
            brightPurple = "$brightPurple"
            brightCyan   = "$brightCyan"
            brightWhite  = "$brightWhite"
        }

        if ($SchemeIndex -lt 0) {
            $UserConfig.schemes += $Scheme
        } else {
            $UserConfig.schemes[$SchemeIndex] = $Scheme
        }

        if (!($CurrentUserProfile = @($UserConfig.profiles.list).where({ $_.guid -eq $CurrentProfile.guid })[0])) {
            $CurrentUserProfile = @($UserConfig.profiles).where({ $_.guid -eq $CurrentProfile.guid })[0]
        }

        if ($CurrentUserProfile.colorScheme) {
            $CurrentUserProfile.colorScheme = $Scheme.name
        } elseif($UserConfig.profiles.defaults) {
            $UserConfig.profiles.defaults | Add-Member -NotePropertyName colorScheme -NotePropertyValue $Scheme.name -Force
        } else {
            $UserConfig.profiles.defaults = [PSCustomObject]@{
                colorScheme = $Scheme.name
            }
        }

        Set-Content $UserConfigFile ($UserConfig | ConvertTo-Json -Depth 10)
    }
}
#EndRegion '.\Public\Set-WindowsTerminalTheme.ps1' 119
#Region '.\Public\Write-TextOnImage.ps1' 0
function Write-TextOnImage {
    <#
        .SYNOPSIS
            Writes text to an image.
        .DESCRIPTION
            Writes one or more lines of text onto a rectangle on an image.
 
            By default, it writes the computer name on the bottom right corner, with an outline around so you can read it regardless of the picture.
        .EXAMPLE
            Write-OutlineText "$Env:UserName`n$Env:ComputerName`n$Env:UserDnsDomain" -Path "~\Wallpaper.png"
    #>

    [CmdletBinding()]
    param(
        [Parameter(ParameterSetName = "OnExistingImageFile", Mandatory, ValueFromPipelineByPropertyName)]
        [ValidateScript({
                if (!(Test-Path "$_")) {
                    throw "The path must point to an existing image. Can't find '$_'"
                }
                $true
            })]
        [Alias("PSPath")]
        [string]$Path,

        [Parameter(ParameterSetName = "OnExistingImageFile")]
        [Parameter(ParameterSetName = "OnNewImageFile", Mandatory)]
        [Parameter(ParameterSetName = "OnExistingGraphics", ValueFromPipeline, Mandatory)]
        [string]$NewPath,

        [Parameter(ParameterSetName = "OnNewImageFile")]
        [System.Drawing.Size]$ImageSize,

        [Parameter(ParameterSetName = "OnExistingGraphics", ValueFromPipeline, Mandatory)]
        [System.Drawing.Graphics]$Graphics,

        # Number of pixels of padding on the left. By default, 20 pixels.
        [int]$LeftPadding = 20,
        # Number of pixels of padding on the top. By default, 20 pixels.
        [int]$TopPadding = 20,
        # Number of pixels of padding on the right. By default, 20 pixels.
        [int]$RightPadding = 20,
        # Number of pixels of padding on the bottom. By default, 60 pixels.
        [int]$BottomPadding = 60,

        [string]$Text = $Env:ComputerName,

        [string]$FontName = "Cascadia Code",

        [int]$FontSize = 38,

        [System.Drawing.FontStyle]$FontStyle = "Bold",

        # The alignment relative to the bounds. Note that in a left-to-right layout, the far position is bottom, and the near position is top.
        [System.Drawing.StringAlignment]$VerticalAlignment = "Far",

        # The alignment relative to the bounds. Note that in a left-to-right layout, the far position is right, and the near position is left. However, in a right-to-left layout, the far position is left.
        [System.Drawing.StringAlignment]$HorizontalAlignment = "Far",

        # The stroke color (defaults to [System.Drawing.Brushes]::Black)
        [System.Drawing.Brush]$StrokeBrush = [System.Drawing.Brushes]::Black,

        # The fill color (defaults to [System.Drawing.Brushes]::White)
        [System.Drawing.Brush]$FillBrush = [System.Drawing.Brushes]::White
    )

    process {
        if ($PSCmdlet.ParameterSetName -eq "OnExistingImageFile") {
            $Source = [System.Drawing.Image]::FromFile((Convert-Path $Path))
            $Graphics = [System.Drawing.Graphics]::FromImage($Source)
            Write-Verbose "Adding text to existing $($Graphics.VisibleClipBounds.Width) x $($Graphics.VisibleClipBounds.Height) image"
        } elseif($PSCmdlet.ParameterSetName -eq "OnNewImageFile") {
            $Source = [System.Drawing.Bitmap]::new($ImageSize.Width, $imageSize.Height)
            $Graphics = [System.Drawing.Graphics]::FromImage($Source)
            # $Graphics.Clear($StrokeBrush.Color)
            Write-Verbose "Adding text to blank $($ImageSize.Width) x $($ImageSize.Height) image"
        } else {
            Write-Verbose "Adding text to image from '$Path'"
        }

        # Save as png to avoid asking them, and dealing with image format as a parameter
        if ($Path -and -not $NewPath) {
            $NewPath = "{0}\{1}{2}" -f [IO.Path]::GetDirectoryName($Path),
            ([IO.Path]::GetFileNameWithoutExtension($Path) + '-' + ($Text -replace ("[" + [regex]::escape([io.path]::GetInvalidFileNameChars()) +"]"))),
            [IO.Path]::GetExtension($Path)
        } elseif ($Path -and -not (Split-Path $NewPath)) {
            $NewPath = Join-Path ([IO.Path]::GetDirectoryName($Path)) $NewPath
            Write-Verbose "Adding text to image: '$Path'"
        }

        try {
            $Bounds = [System.Drawing.RectangleF]::new(
                        $Graphics.VisibleClipBounds.Location + [System.Drawing.SizeF]::new($LeftPadding, $TopPadding),
                        $Graphics.VisibleClipBounds.Size - [System.Drawing.SizeF]::new($RightPadding + $LeftPadding, $TopPadding + $BottomPadding))
            Write-Verbose "Using Bounds: $($Bounds.Top), $($Bounds.Left), $($Bounds.Bottom, $Bounds.Right)"

            $Font = try {
                [System.Drawing.FontFamily]::new($FontName)
            } catch {
                [System.Drawing.FontFamily]::GenericMonospace
            }
            Write-Verbose "Using FontFamily $Font"

            $StringFormat = [System.Drawing.StringFormat]::GenericTypographic
            $StringFormat.Alignment = $HorizontalAlignment
            $StringFormat.LineAlignment = $VerticalAlignment

            $GraphicsPath = [System.Drawing.Drawing2D.GraphicsPath]::new()
            $GraphicsPath.AddString(
                $Text,
                $Font,
                $FontStyle,
                ($Graphics.DpiY * $FontSize / 72),
                $Bounds,
                $StringFormat);

            Write-Verbose "Adding '$Text' to the image in $($FillBrush.Color) on $($StrokeBrush.Color)"
            $Graphics.FillPath($FillBrush, $GraphicsPath);
            $Graphics.DrawPath($StrokeBrush, $GraphicsPath);
        } catch {
            Write-Warning "Unhandled Error: $_"
            Write-Warning "Unhandled Error: $($_.ScriptStackTrace)"
            throw
        } finally {
            if ($PSCmdlet.ParameterSetName -ne "OnExistingGraphics") {
                $Graphics.Dispose()
            }
            try {
                if ($NewPath -and $Source) {
                    $ImageFormat = switch ([IO.Path]::GetExtension($NewPath)) {
                        ".png" { [System.Drawing.Imaging.ImageFormat]::Png }
                        ".bmp" { [System.Drawing.Imaging.ImageFormat]::Bmp }
                        ".jpg" { [System.Drawing.Imaging.ImageFormat]::Jpeg }
                    }
                    Write-Verbose "Writing a $([IO.Path]::GetExtension($NewPath)) to $NewPath"
                    $Source.Save($NewPath, $ImageFormat)
                }
                Get-Item $NewPath
            } finally {
                if ($PSCmdlet.ParameterSetName -ne "OnExistingGraphics") {
                    $Source.Dispose()
                }
            }
        }
    }
}
#EndRegion '.\Public\Write-TextOnImage.ps1' 145
#Region '.\postfix.ps1' 0
if (Get-Module EzTheme -ErrorAction SilentlyContinue) {
    Get-ModuleTheme | Set-WindowsTerminalTheme
}
#EndRegion '.\postfix.ps1' 4