Public/Install-TerminalColors.ps1
|
# One command to set everything up, so that installing from the PowerShell Gallery # is a two-liner: # # Install-Module TerminalColors -Scope CurrentUser # Install-TerminalColors # # Install-Module cannot do this part itself: the colouring needs the Windows # Terminal settings and your PowerShell profile to be edited, which no package # manager is allowed to do on your behalf. # # install.ps1 copies the module from a clone and then calls this very function, so # both installation paths behave identically by construction. function Write-TcStep { param([string] $Message, [switch] $Quiet) if ($Quiet) { return } Write-Host ' -> ' -ForegroundColor Cyan -NoNewline Write-Host $Message } function Write-TcNote { param([string] $Message, [switch] $Quiet) if ($Quiet) { return } Write-Host ' ! ' -ForegroundColor Yellow -NoNewline Write-Host $Message -ForegroundColor Yellow } function Install-TerminalColors { <# .SYNOPSIS Sets up everything TerminalColors needs, in one command. .DESCRIPTION Chains the three one-off steps and activates the colouring in the current session: 1. the Windows Terminal theme, which binds the tab and the window border to the pane background; 2. the opaque backdrop, which keeps the pane readable while the tab takes the full project colour; 3. the block in your PowerShell profile, so every new session is coloured. Each step is reported, and a step that fails is reported without stopping the others - a missing Windows Terminal must not prevent the profile from being set up. Nothing is installed outside your user profile, and no administrator rights are needed. Every file touched is backed up first. .PARAMETER SkipTheme Does not install the Windows Terminal theme. Without it the tab and the border do not change colour at all. .PARAMETER SkipBackdrop Does not install the opaque backdrop. The project colour is then diluted onto the pane background and the tab stays subtle. .PARAMETER SystemTitleBar Also moves the tabs out of the title bar, so the window gets a real system title bar that DWM colours. Requires Windows Terminal to be closed completely to take effect. .PARAMETER SkipProfile Does not touch your PowerShell profile. The colouring then has to be enabled by hand with Enable-TerminalColors in each session. .PARAMETER EnableArguments Arguments written on the Enable-TerminalColors line of your profile. Example: '-TitleFormat "{icon} {name}" -NoWindowBorder' -PureColor is added automatically when the backdrop is in place, unless you specify -Tint or -PureColor yourself. .PARAMETER Force Reinstalls the theme even when it is already present. .PARAMETER NoBackup Does not write backup copies of settings.json and of the profile. .PARAMETER Quiet Prints nothing. Combine with -PassThru to get the result as an object. .PARAMETER PassThru Returns an object describing what each step did. .EXAMPLE Install-TerminalColors .EXAMPLE Install-TerminalColors -SystemTitleBar Adds a coloured system title bar on top of the coloured tab. .EXAMPLE Install-TerminalColors -SkipBackdrop -EnableArguments '-Tint 0.45' Keeps the diluted rendering: the colour lands on the pane background rather than on the tab. .EXAMPLE Install-TerminalColors -WhatIf Shows every change that would be made, and writes nothing. #> # SupportsShouldProcess without a ShouldProcess call of its own: every write is # performed by a sub-command that implements it, and -WhatIf is forwarded to each # of them explicitly. Announcing the orchestration on top of that would report # the same change twice. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Delegated to the sub-commands, which each call ShouldProcess; -WhatIf is forwarded to all of them.')] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param( [switch] $SkipTheme, [switch] $SkipBackdrop, [switch] $SystemTitleBar, [switch] $SkipProfile, [string] $EnableArguments, [switch] $Force, [switch] $NoBackup, [switch] $Quiet, [switch] $PassThru ) # Prefix so that -WhatIf never announces an action as done. $simulated = '' if ($WhatIfPreference) { $simulated = '(simulation) ' } $result = [ordered]@{ Theme = 'skipped' TabRowColor = $null Backdrop = 'skipped' BackdropColor = $null SystemTitleBar = 'not requested' Profiles = @() EnableArguments = $null Enabled = $false RestartNeeded = $false } if (-not $Quiet) { $version = (Get-Module TerminalColors).Version Write-Host '' Write-Host " TerminalColors $version" -ForegroundColor White Write-Host ' ============================' -ForegroundColor DarkGray Write-Host '' } # --- 1. Windows Terminal theme ------------------------------------------ # -WhatIf is forwarded explicitly rather than relying on the preference # variable, so the intent is visible at every call site. if (-not $SkipTheme) { try { $step = Install-TerminalColorsTheme -Force:$Force -NoBackup:$NoBackup -WhatIf:$WhatIfPreference $result.TabRowColor = $step.TabRowColor if ($step.Changed) { $result.Theme = 'installed' Write-TcStep -Quiet:$Quiet -Message "$($simulated)Windows Terminal theme installed ($($step.Changes -join ', '))" if ($step.Backup) { Write-TcStep -Quiet:$Quiet -Message "settings.json backup: $($step.Backup)" } } else { $result.Theme = 'already in place' Write-TcStep -Quiet:$Quiet -Message 'Windows Terminal theme already in place' } } catch { $result.Theme = "failed: $($_.Exception.Message)" Write-TcNote -Quiet:$Quiet -Message "Theme not installed: $($_.Exception.Message)" Write-TcNote -Quiet:$Quiet -Message 'Without the theme, the tab and the window border will not change colour.' } } else { Write-TcNote -Quiet:$Quiet -Message 'Windows Terminal theme skipped (-SkipTheme).' } # --- 2. Opaque backdrop -------------------------------------------------- # This is what allows a full-strength colour on the tab without colouring the # pane: the background colour carries the pure project colour (the tab copies # it), and a solid image of your usual background colour covers the pane. $backdropReady = $false if (-not $SkipBackdrop) { try { $step = Install-TerminalColorsBackdrop -NoBackup:$NoBackup -WhatIf:$WhatIfPreference $backdropReady = $true $result.BackdropColor = $step.Color if ($step.Changed) { $result.Backdrop = 'installed' Write-TcStep -Quiet:$Quiet -Message "$($simulated)Opaque backdrop installed: the pane stays $($step.Color)" if ($step.Backup) { Write-TcStep -Quiet:$Quiet -Message "settings.json backup: $($step.Backup)" } } else { $result.Backdrop = 'already in place' Write-TcStep -Quiet:$Quiet -Message "Opaque backdrop already in place (pane $($step.Color))" } } catch { $result.Backdrop = "failed: $($_.Exception.Message)" Write-TcNote -Quiet:$Quiet -Message "Opaque backdrop not installed: $($_.Exception.Message)" Write-TcNote -Quiet:$Quiet -Message 'The colour will stay diluted on the pane background.' } } else { Write-TcNote -Quiet:$Quiet -Message 'Opaque backdrop skipped (-SkipBackdrop): the colour will be diluted on the pane background.' } # --- 3. System title bar (optional) ------------------------------------- if ($SystemTitleBar) { try { $step = Install-TerminalColorsTitleBar -NoBackup:$NoBackup -WhatIf:$WhatIfPreference if ($step -and $step.Changed) { $result.SystemTitleBar = 'enabled' $result.RestartNeeded = $true Write-TcStep -Quiet:$Quiet -Message "$($simulated)System title bar enabled (showTabsInTitlebar: false)" Write-TcNote -Quiet:$Quiet -Message 'Close Windows Terminal completely for this setting to take effect.' } else { $result.SystemTitleBar = 'already enabled' Write-TcStep -Quiet:$Quiet -Message 'System title bar already enabled' } } catch { $result.SystemTitleBar = "failed: $($_.Exception.Message)" Write-TcNote -Quiet:$Quiet -Message "System title bar not enabled: $($_.Exception.Message)" } } # --- 4. PowerShell profile ---------------------------------------------- # The backdrop is only worth having with -PureColor, so add it - unless the # caller has already expressed a dilution preference of their own. $effectiveArguments = $EnableArguments if ($backdropReady -and $EnableArguments -notmatch '-(PureColor|Tint)\b') { $effectiveArguments = ('-PureColor ' + $EnableArguments).Trim() } $result.EnableArguments = $effectiveArguments if (-not $SkipProfile) { # Both editions' profiles when both are installed, so the colouring works # whichever one a Windows Terminal profile happens to launch. # Uninstall-TerminalColors reads the same list, so it can never clean less # than what was written. $touched = @() foreach ($profilePath in (Get-TcProfilePath)) { try { $step = Install-TerminalColorsProfile -ProfilePath $profilePath -EnableArguments $effectiveArguments -NoBackup:$NoBackup -WhatIf:$WhatIfPreference $touched += $step.ProfilePath if ($step.Changed) { Write-TcStep -Quiet:$Quiet -Message "$($simulated)Profile updated: $($step.ProfilePath) ($($step.Action))" } else { Write-TcStep -Quiet:$Quiet -Message "Profile already up to date: $($step.ProfilePath)" } } catch { Write-TcNote -Quiet:$Quiet -Message "Profile [$profilePath] not modified: $($_.Exception.Message)" } } $result.Profiles = $touched } else { Write-TcNote -Quiet:$Quiet -Message 'PowerShell profile skipped (-SkipProfile).' } # --- 5. Immediate activation -------------------------------------------- if ($WhatIfPreference) { Write-TcNote -Quiet:$Quiet -Message '-WhatIf mode: nothing was written, and this session was not modified.' } elseif (Test-TcWindowsTerminal) { if ($effectiveArguments) { Write-TcStep -Quiet:$Quiet -Message "Enabling with: $effectiveArguments" # The arguments come from the caller, who is already running this # command: there is no trust boundary to cross here. Invoke-Expression "Enable-TerminalColors $effectiveArguments" } else { Enable-TerminalColors } $result.Enabled = $true Write-TcStep -Quiet:$Quiet -Message 'Colouring active in this session' } else { Write-TcNote -Quiet:$Quiet -Message 'Not a Windows Terminal session: open Windows Terminal to see the result.' } if (-not $Quiet) { Write-Host '' Write-Host ' Done.' -ForegroundColor Green Write-Host '' Write-Host ' To check: ' -NoNewline; Write-Host 'Invoke-TerminalColorsDoctor' -ForegroundColor White Write-Host ' To try it: ' -NoNewline; Write-Host 'cd <a Git repository> and look at the tab' -ForegroundColor White Write-Host ' To pick a colour: ' -NoNewline; Write-Host 'Set-FolderColor ''#215732''' -ForegroundColor White Write-Host '' } if ($PassThru) { return [pscustomobject]$result } } function Uninstall-TerminalColors { <# .SYNOPSIS Undoes everything Install-TerminalColors set up. .DESCRIPTION Removes the profile block, the opaque backdrop and the theme, restores what was there before, and returns the current session to its normal appearance. The module itself is left in place - remove it with Uninstall-Module TerminalColors, or by deleting its folder. .PARAMETER KeepTheme Leaves the Windows Terminal theme installed and selected. .PARAMETER NoBackup Does not write backup copies of settings.json. .PARAMETER Quiet Prints nothing. .EXAMPLE Uninstall-TerminalColors #> # Same as Install-TerminalColors: the removals are all performed by # sub-commands that implement ShouldProcess themselves. [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Delegated to the sub-commands, which each call ShouldProcess; -WhatIf is forwarded to all of them.')] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [switch] $KeepTheme, [switch] $NoBackup, [switch] $Quiet ) # Order matters: the profile first, so a new session started midway does not # reapply what is being removed. # # Every profile the install writes to, not just the current edition's, read from # the same helper it uses. A profile that carries no block is left alone rather # than warned about: it was never touched. $removedProfiles = 0 foreach ($profilePath in (Get-TcProfilePath)) { if (-not (Test-TerminalColorsProfile -ProfilePath $profilePath)) { continue } try { Uninstall-TerminalColorsProfile -ProfilePath $profilePath -WhatIf:$WhatIfPreference Write-TcStep -Quiet:$Quiet -Message "Removed: the block in $profilePath" $removedProfiles++ } catch { Write-TcNote -Quiet:$Quiet -Message "PowerShell profile [$profilePath]: $($_.Exception.Message)" } } if ($removedProfiles -eq 0) { Write-TcStep -Quiet:$Quiet -Message 'Nothing to remove: the PowerShell profile block' } # Every step states what it found before saying what it did. An unconditional # "Removed" - printed even right after the sub-command had warned there was # nothing there - makes the whole report worthless: an uninstall on a machine # where nothing was installed claimed to have removed all of it. $steps = @( @{ Name = 'the opaque backdrop' Installed = { (Test-TerminalColorsBackdrop).Installed } Remove = { Uninstall-TerminalColorsBackdrop -NoBackup:$NoBackup -WhatIf:$WhatIfPreference } } @{ Name = 'the system title bar' Installed = { Test-TerminalColorsTitleBar } Remove = { Uninstall-TerminalColorsTitleBar -NoBackup:$NoBackup -WhatIf:$WhatIfPreference } } ) if (-not $KeepTheme) { $steps += @{ Name = 'the Windows Terminal theme' Installed = { Test-TcThemeInSettings } Remove = { Uninstall-TerminalColorsTheme -NoBackup:$NoBackup -WhatIf:$WhatIfPreference } } } foreach ($step in $steps) { $present = $false try { $present = [bool](& $step.Installed) } catch { Write-TcNote -Quiet:$Quiet -Message "$($step.Name): $($_.Exception.Message)" continue } if (-not $present) { Write-TcStep -Quiet:$Quiet -Message "Nothing to remove: $($step.Name)" continue } try { & $step.Remove | Out-Null Write-TcStep -Quiet:$Quiet -Message "Removed: $($step.Name)" } catch { Write-TcNote -Quiet:$Quiet -Message "$($step.Name): $($_.Exception.Message)" } } if (-not $WhatIfPreference) { try { Disable-TerminalColors } catch { Write-Verbose "TerminalColors: could not restore this session ($($_.Exception.Message))" } Write-TcStep -Quiet:$Quiet -Message 'This session restored to its normal appearance' } } |