Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions Microsoft.PowerShell_profile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,14 @@ function Get-Utf8FileText {
[System.IO.File]::ReadAllText($Path)
}

# Write UTF-8 without BOM through a sibling temp file before replacing the target.
# Write UTF-8 without BOM through a sibling temp file before replacing the target; a symlink is written in place so the link survives.
function Write-Utf8FileAtomic {
param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][AllowEmptyString()][string]$Content)
$existing = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue
if ($existing -and $existing.LinkType) {
[System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($false))
return
}
$dir = Split-Path -Parent $Path
if (-not $dir) { $dir = '.' }
$tmp = Join-Path $dir ('.psp-tmp-' + [System.IO.Path]::GetRandomFileName())
Expand Down Expand Up @@ -951,12 +956,15 @@ function Update-Profile {
}

# Apply user-settings.json overrides (never downloaded, never overwritten)
$wtManaged = $true
if (Test-Path $userSettingsPath) {
try {
$userSettings = Get-Utf8FileText $userSettingsPath | ConvertFrom-Json
$userSettingsParsed = $true
$userThemeOverridePresent = $null -ne $userSettings.theme
$userWindowsTerminalOverridePresent = $null -ne $userSettings.windowsTerminal
# windowsTerminal.manage = false hands settings.json to another tool, such as a dotfiles repo.
if ($userSettings.windowsTerminal -and $userSettings.windowsTerminal.PSObject.Properties['manage'] -and $userSettings.windowsTerminal.manage -eq $false) { $wtManaged = $false }
$userTerminalDefaultsOverridePresent = $null -ne $userSettings.defaults
$userKeybindingsOverridePresent = $null -ne $userSettings.keybindings
if ($config -and $userSettings.theme) {
Expand Down Expand Up @@ -996,7 +1004,8 @@ function Update-Profile {
"_comment": "User overrides for terminal, theme, and profile behavior. Only add keys you want to override.",
"_examples": {
"theme": { "name": "catppuccin", "url": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/catppuccin.omp.json" },
"windowsTerminal": { "colorScheme": "One Half Dark", "cursorColor": "#ffffff" },
"windowsTerminal": { "manage": true, "colorScheme": "One Half Dark", "cursorColor": "#ffffff" },
"_manage_note": "set windowsTerminal.manage to false when another tool (for example a dotfiles repo) owns Windows Terminal settings.json; the profile then leaves that file alone",
"defaults": {
"opacity": 90,
"font": { "size": 14 },
Expand Down Expand Up @@ -1123,7 +1132,10 @@ function Update-Profile {

# Phase 6: Synchronize every installed Windows Terminal variant.
$terminalOverridesChanged = $userSettingsChanged -and ($userWindowsTerminalOverridePresent -or $userTerminalDefaultsOverridePresent -or $userKeybindingsOverridePresent)
if (($Force -or $profileChanged -or $configChanged -or $terminalConfigChanged -or $terminalOverridesChanged) -and (($config -and $config.windowsTerminal) -or $terminalConfig)) {
if (-not $wtManaged) {
Write-Host "Windows Terminal settings left alone (windowsTerminal.manage = false in user-settings.json)." -ForegroundColor DarkGray
}
elseif (($Force -or $profileChanged -or $configChanged -or $terminalConfigChanged -or $terminalOverridesChanged) -and (($config -and $config.windowsTerminal) -or $terminalConfig)) {
$wtSettingsPaths = Get-WindowsTerminalSettingsPaths
foreach ($wtSettingsPath in $wtSettingsPaths) {
if ($PSCmdlet.ShouldProcess($wtSettingsPath, "Update Windows Terminal settings")) {
Expand Down Expand Up @@ -2895,6 +2907,36 @@ function Test-ProfileHealth {
$results += [pscustomobject]@{ Category = 'Config'; Check = 'user-settings.json'; Status = 'WARN'; Detail = 'missing (no overrides applied)' }
}

# Windows Terminal settings.json ownership: a symlink or windowsTerminal.manage = false means another tool owns it.
$wtManagedHere = $true
if (Test-Path $us) {
try {
$usCfg = Get-Content $us -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
if ($usCfg.windowsTerminal -and $usCfg.windowsTerminal.PSObject.Properties['manage'] -and $usCfg.windowsTerminal.manage -eq $false) { $wtManagedHere = $false }
}
catch { $null = $_ }
}
foreach ($wtPath in @(Get-WindowsTerminalSettingsPaths)) {
$wtItem = Get-Item -LiteralPath $wtPath -Force -ErrorAction SilentlyContinue
if (-not $wtItem) { continue }
$wtVariant = if ($wtPath -match 'WindowsTerminalPreview') { 'Preview' } elseif ($wtPath -match 'WindowsTerminalCanary') { 'Canary' } elseif ($wtPath -match 'Packages') { 'Stable' } else { 'Unpackaged' }
if ($wtItem.LinkType) {
$wtTarget = [string](@($wtItem.Target)[0])
if ($wtManagedHere) {
$results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'WARN'; Detail = "symlink to $wtTarget; set windowsTerminal.manage = false in user-settings.json if that tool owns it" }
}
else {
$results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = "symlink to $wtTarget, managed elsewhere" }
}
}
elseif ($wtManagedHere) {
$results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = 'managed by this profile' }
}
else {
$results += [pscustomobject]@{ Category = 'Terminal'; Check = "settings.json ($wtVariant)"; Status = 'OK'; Detail = 'not managed (windowsTerminal.manage = false)' }
}
}

# Check the font this install actually uses: a wizard choice in user-settings.json
# overrides the shipped terminal-config.json default.
$fontName = $null
Expand Down Expand Up @@ -3160,7 +3202,11 @@ function Uninstall-Profile {
$backups = Get-ChildItem -Path $wtLocalState -Filter 'settings.json.*.bak' -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending

if ($HardResetWindowsTerminal) {
$wtItem = Get-Item -LiteralPath $wtSettingsPath -Force -ErrorAction SilentlyContinue
if ($wtItem -and $wtItem.LinkType) {
Write-Host " $wtSettingsPath is a symlink managed elsewhere; Windows Terminal settings left in place." -ForegroundColor DarkGray
}
elseif ($HardResetWindowsTerminal) {
if (Test-Path $wtSettingsPath) {
if ($PSCmdlet.ShouldProcess($wtSettingsPath, 'Delete WT settings for hard reset')) {
Remove-Item $wtSettingsPath -Force -ErrorAction SilentlyContinue
Expand Down Expand Up @@ -6089,6 +6135,10 @@ function Set-TerminalBackground {
}

# 2. Apply the background to every installed Windows Terminal variant.
if ($settings.PSObject.Properties['windowsTerminal'] -and $settings.windowsTerminal.PSObject.Properties['manage'] -and $settings.windowsTerminal.manage -eq $false) {
Write-Host 'Windows Terminal settings.json is managed elsewhere (windowsTerminal.manage = false). Background persisted to user-settings.json only.' -ForegroundColor DarkGray
return
}
$wtSettingsPaths = Get-WindowsTerminalSettingsPaths
if (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) {
Write-Host 'Windows Terminal settings.json not found (Store/Preview/Canary/unpackaged). Change persisted; will apply after next Update-Profile.' -ForegroundColor DarkGray
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Reconfigure-Profile
4. **Nerd Font** — Caskaydia, JetBrainsMono, FiraCode, Meslo, Hack, or Iosevka. Fetches latest release tag from [ryanoasis/nerd-fonts](https://github.com/ryanoasis/nerd-fonts/releases) automatically.
5. **Tab bar color + window chrome** — presets (scheme-match, pure black, custom hex) + `applicationTheme` dark/light.
6. **Terminal appearance** — opacity, `useAcrylic`, font size, cursor shape, padding, scrollbar state, history size. Each prompt keeps the current default on Enter.
7. **PSReadLine colors** — default / derive from chosen scheme / skip.
7. **PSReadLine colors** — derive from chosen scheme (default) / shipped palette / skip.
8. **Background image** — optional path + opacity (0.05–0.50). Skipped by default.
9. **Editor preference** — VS Code, Notepad++, Neovim, Vim, Notepad, or a custom exe. Used by `edit`, `ep`, `hosts`, etc.
10. **Telemetry opt-out + feature toggles** — `psfzf`, `predictions`, `startupMessage`, `perDirProfiles`, `commandOverrides` — y/n per item with sensible defaults.
Expand Down Expand Up @@ -160,6 +160,7 @@ Four extension points survive updates. From simplest to most powerful:

- **`user-settings.json`** (`%LOCALAPPDATA%\PowerShellProfile\`) - JSON overrides. Keys:
- `theme`, `windowsTerminal`, `defaults`, `keybindings` - terminal and OMP theme
- `windowsTerminal.manage` - set to `false` when another tool owns Windows Terminal's `settings.json` (a dotfiles repo, for example). `setup.ps1`, `Update-Profile`, `Set-TerminalBackground` and `Uninstall-Profile` then leave that file alone, and `psp-doctor` reports who owns it. A symlinked `settings.json` is always written in place, so the link survives updates.
- `defaults.backgroundImage` / `backgroundImageOpacity` / `backgroundImageStretchMode` / `backgroundImageAlignment` - Windows Terminal background image
- `features` - toggle heavy/optional behavior: `psfzf`, `predictions`, `startupMessage`, `perDirProfiles` (all `true` by default), `transientPrompt` (collapses previous prompt on Enter; default `false`, customize via `$script:PSP.TransientPrompt = { ... }` in `profile_user.ps1`), `updateCheck` (notifies once a week when main has advanced past the applied commit; default `false` so `irm | iex` in scripts does not trigger a surprise network call)
- `commandOverrides` - redefine any command without editing source: `{ "gs": "git status --short" }`. Opt-in: set `features.commandOverrides = true` in the same file. Default off because it compiles JSON strings into executable scriptblocks.
Expand Down Expand Up @@ -453,6 +454,7 @@ PowerShellPerfect bundles a **prompt** (via Oh My Posh), a **command suite**, an
| `running scripts is disabled on this system` | Run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. (The recommended one-liner runs an in-memory scriptblock, which isn't subject to the script-file execution policy.) |
| Prompt shows boxes / missing glyphs | The terminal font isn't a Nerd Font. Set your Windows Terminal profile font to the one setup installed (e.g. *CaskaydiaCove Nerd Font*) and restart WT. |
| `oh-my-posh` not found right after install | Reopen the terminal (PATH refresh) or run `Update-SessionPathFromRegistry`; confirm with `psp-doctor`. |
| Windows Terminal settings keep changing back | Another tool also writes `settings.json`. Pick one owner: set `windowsTerminal.manage` to `false` in `user-settings.json` to let the other tool have it, or stop the other tool. `psp-doctor` shows whether the file is a symlink and who manages it. |
| Turned a feature off in the wizard but it still loads | Check the `features` block in `user-settings.json`, then restart PowerShell. |
| Something feels off | Run `psp-doctor` (alias for `Test-ProfileHealth`): OK/WARN/FAIL per check across tools, caches, fonts, PATH, and modules. |

Expand Down
14 changes: 11 additions & 3 deletions setup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ function Start-InstallWizard {
@{ Name = 'Derive from color scheme'; Desc = 'Map scheme ANSI roles to syntax (Command, String, ...)'; Value = 'scheme' }
@{ Name = 'Skip'; Desc = 'No override; edit user-settings.json later'; Value = $null }
)
$pick = Select-WizardItem -Title 'PSReadLine syntax colors' -Items $rlOptions -DefaultName 'theme.json default'
$pick = Select-WizardItem -Title 'PSReadLine syntax colors' -Items $rlOptions -DefaultName 'Derive from color scheme'
if ($pick -and $pick.Value) { $choices.PSReadLine = $pick.Value }
$choices.CompletedSteps += 'PSReadLine'
Save-State
Expand Down Expand Up @@ -1149,10 +1149,13 @@ function Merge-JsonObject {
}

# Apply user-settings.json overrides (never downloaded, never overwritten)
$script:WtManaged = $true
$userSettingsPath = Join-Path $configCachePath "user-settings.json"
if (Test-Path $userSettingsPath) {
try {
$userSettings = Get-Content $userSettingsPath -Raw | ConvertFrom-Json
# windowsTerminal.manage = false hands settings.json to another tool, such as a dotfiles repo.
if ($userSettings.windowsTerminal -and $userSettings.windowsTerminal.PSObject.Properties['manage'] -and $userSettings.windowsTerminal.manage -eq $false) { $script:WtManaged = $false }
if ($profileConfig -and $userSettings.theme) {
if (-not $profileConfig.theme) {
$profileConfig | Add-Member -NotePropertyName "theme" -NotePropertyValue ([PSCustomObject]@{}) -Force
Expand Down Expand Up @@ -1272,7 +1275,8 @@ if (-not (Test-Path $userSettingsTemplate)) {
"_comment": "User overrides for terminal, theme, and profile behavior. Only add keys you want to override.",
"_examples": {
"theme": { "name": "catppuccin", "url": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/catppuccin.omp.json" },
"windowsTerminal": { "colorScheme": "One Half Dark", "cursorColor": "#ffffff" },
"windowsTerminal": { "manage": true, "colorScheme": "One Half Dark", "cursorColor": "#ffffff" },
"_manage_note": "set windowsTerminal.manage to false when another tool (for example a dotfiles repo) owns Windows Terminal settings.json; the profile then leaves that file alone",
"defaults": {
"opacity": 90,
"font": { "size": 14 },
Expand Down Expand Up @@ -1575,7 +1579,11 @@ else {
# Merge font, theme, and appearance into every Windows Terminal variant.
Write-Host "[10/10] Windows Terminal" -ForegroundColor Cyan
$wtSettingsPaths = Get-WindowsTerminalSettingsPaths
if (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) {
if (-not $script:WtManaged) {
Write-Host " Skipped: user-settings.json sets windowsTerminal.manage = false, so settings.json is left to the tool that owns it." -ForegroundColor DarkGray
$wtSettingsPaths = @()
}
elseif (-not $wtSettingsPaths -or $wtSettingsPaths.Count -eq 0) {
Write-Host " Windows Terminal settings not found (skipped)." -ForegroundColor Yellow
}
foreach ($wtSettingsPath in $wtSettingsPaths) {
Expand Down
Loading