Files
dotfiles/chezmoi/.chezmoiscripts/run_20-configure-windows-terminal.ps1
T
Dmitry DementievandClaude Opus 4.6 c06ad1ecbb fix(windows): WT-скрипт использует source вместо commandline для PS7
- Зачем:
  - на машине без pwsh в стандартном PATH профиль с `commandline = 'pwsh.exe'` ломался, потому что WT игнорировал динамический `source` в пользу явного `commandline`.
- Что:
  - PS7 профиль теперь управляется через `source = Windows.Terminal.PowershellCore`, `commandline` удаляется.
  - добавлена проверка `Test-PowerShell7Available`: если pwsh не найден, профиль не создаётся.
  - добавлена `Remove-JsonPropertyValue` для идемпотентного удаления свойств.
  - magic string вынесен в константу `$PowerShellSource`.
- Проверка:
  - `chezmoi -S "$HOME\dotfiles\chezmoi" apply --dry-run --verbose`.
  - `pwsh -NoProfile -Command "(Get-Content (Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json') -Raw | ConvertFrom-Json).profiles.list | Where-Object {$_.source -eq 'Windows.Terminal.PowershellCore'} | ConvertTo-Json"`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 17:41:37 +03:00

298 lines
8.8 KiB
PowerShell

if (-not ($IsWindows -or $PSVersionTable.PSEdition -eq 'Desktop')) {
return
}
$settingsPath = Join-Path $env:LOCALAPPDATA 'Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json'
if (-not (Test-Path -LiteralPath $settingsPath)) {
Write-Warning "Windows Terminal settings not found: $settingsPath"
return
}
$PowerShellGuid = '{574e775e-4f2a-5b96-ac1e-a2962a402336}'
$PowerShellSource = 'Windows.Terminal.PowershellCore'
$LegacyPowerShellGuid = '{61c54bbd-c2c6-5271-96e7-009a87ff44bf}'
$changed = $false
function Get-JsonPropertyValue {
param(
[Parameter(Mandatory = $true)]
$Object,
[Parameter(Mandatory = $true)]
[string]$Name
)
$Property = $Object.PSObject.Properties[$Name]
if ($null -eq $Property) {
return $null
}
return $Property.Value
}
function Test-JsonValueEqual {
param(
$Left,
$Right
)
if ($null -eq $Left -and $null -eq $Right) {
return $true
}
if ($null -eq $Left -or $null -eq $Right) {
return $false
}
if (
$Left -is [System.Collections.IEnumerable] -and
-not ($Left -is [string])
) {
return (ConvertTo-Json $Left -Depth 100 -Compress) -eq (ConvertTo-Json $Right -Depth 100 -Compress)
}
return $Left -eq $Right
}
function Set-JsonPropertyValue {
param(
[Parameter(Mandatory = $true)]
$Object,
[Parameter(Mandatory = $true)]
[string]$Name,
$Value
)
$Property = $Object.PSObject.Properties[$Name]
if ($null -eq $Property) {
$Object | Add-Member -NotePropertyName $Name -NotePropertyValue $Value
$script:changed = $true
return
}
if (-not (Test-JsonValueEqual -Left $Property.Value -Right $Value)) {
$Property.Value = $Value
$script:changed = $true
}
}
function Remove-JsonPropertyValue {
param(
[Parameter(Mandatory = $true)]
$Object,
[Parameter(Mandatory = $true)]
[string]$Name
)
$Property = $Object.PSObject.Properties[$Name]
if ($null -ne $Property) {
[void]$Object.PSObject.Properties.Remove($Name)
$script:changed = $true
}
}
function Test-PowerShell7Available {
try {
return $null -ne (Get-Command pwsh -ErrorAction SilentlyContinue | Select-Object -First 1)
}
catch {
return $false
}
}
function Test-IsPowerShell7Profile {
param(
[Parameter(Mandatory = $true)]
$Profile
)
$Source = [string](Get-JsonPropertyValue -Object $Profile -Name source)
$Name = [string](Get-JsonPropertyValue -Object $Profile -Name name)
$CommandLine = [string](Get-JsonPropertyValue -Object $Profile -Name commandline)
if ($Source -eq $PowerShellSource) {
return $true
}
if ($CommandLine -match '(?i)(^|[\\/ ])pwsh(?:\.exe)?(?:\s|$)') {
return $true
}
return $Name -match '^(PowerShell|PowerShell 7)$'
}
function Test-IsDisposablePowerShell7Profile {
param(
[Parameter(Mandatory = $true)]
$Profile
)
$Source = [string](Get-JsonPropertyValue -Object $Profile -Name source)
$Name = [string](Get-JsonPropertyValue -Object $Profile -Name name)
$CommandLine = [string](Get-JsonPropertyValue -Object $Profile -Name commandline)
return (
$Name -match '^(PowerShell|PowerShell 7)$' -and
(
$Source -eq $PowerShellSource -or
$CommandLine -match '(?i)(^|[\\/ ])pwsh(?:\.exe)?(?:\s|$)'
)
)
}
try {
$originalSettings = Get-Content -LiteralPath $settingsPath -Raw
$settings = $originalSettings | ConvertFrom-Json
}
catch {
Write-Warning "Failed to read Windows Terminal settings: $settingsPath"
Write-Warning $_
return
}
if ($null -eq (Get-JsonPropertyValue -Object $settings -Name profiles)) {
$settings | Add-Member -NotePropertyName profiles -NotePropertyValue ([pscustomobject]@{
defaults = [pscustomobject]@{}
list = @()
})
$changed = $true
}
if ($null -eq (Get-JsonPropertyValue -Object $settings.profiles -Name list)) {
$settings.profiles | Add-Member -NotePropertyName list -NotePropertyValue @()
$changed = $true
}
$profiles = @($settings.profiles.list)
$powerShell7CandidateIndices = New-Object System.Collections.Generic.List[int]
$legacyPowerShellIndex = $null
for ($i = 0; $i -lt $profiles.Count; $i++) {
$profile = $profiles[$i]
$guid = [string](Get-JsonPropertyValue -Object $profile -Name guid)
$source = [string](Get-JsonPropertyValue -Object $profile -Name source)
$commandLine = [string](Get-JsonPropertyValue -Object $profile -Name commandline)
if (Test-IsPowerShell7Profile -Profile $profile) {
[void]$powerShell7CandidateIndices.Add($i)
}
if (
$guid -eq $LegacyPowerShellGuid -or
$commandLine -match '(?i)WindowsPowerShell\\v1\.0\\powershell\.exe'
) {
$legacyPowerShellIndex = $i
}
if ($source -eq 'Windows.Terminal.Azure') {
Set-JsonPropertyValue -Object $profile -Name hidden -Value $true
}
}
$powerShell7Index = $null
foreach ($candidateIndex in $powerShell7CandidateIndices) {
$candidate = $profiles[$candidateIndex]
if ([string](Get-JsonPropertyValue -Object $candidate -Name source) -eq $PowerShellSource) {
$powerShell7Index = $candidateIndex
break
}
}
if ($null -eq $powerShell7Index -and $powerShell7CandidateIndices.Count -gt 0) {
$powerShell7Index = $powerShell7CandidateIndices[0]
}
if ($null -eq $powerShell7Index) {
if (Test-PowerShell7Available) {
# Create a dynamic-profile override instead of a manual pwsh.exe launcher.
$profiles += [pscustomobject]@{
guid = $PowerShellGuid
hidden = $false
name = 'PowerShell 7'
source = $PowerShellSource
startingDirectory = '%USERPROFILE%'
}
$powerShell7Index = $profiles.Count - 1
$changed = $true
}
else {
Write-Warning 'PowerShell 7 was not detected; leaving Windows Terminal default profile unchanged.'
}
}
if ($null -ne $powerShell7Index) {
$powerShell7Profile = $profiles[$powerShell7Index]
Set-JsonPropertyValue -Object $powerShell7Profile -Name guid -Value $PowerShellGuid
Set-JsonPropertyValue -Object $powerShell7Profile -Name source -Value $PowerShellSource
Remove-JsonPropertyValue -Object $powerShell7Profile -Name commandline
Set-JsonPropertyValue -Object $powerShell7Profile -Name name -Value 'PowerShell 7'
Set-JsonPropertyValue -Object $powerShell7Profile -Name hidden -Value $false
Set-JsonPropertyValue -Object $powerShell7Profile -Name startingDirectory -Value '%USERPROFILE%'
Set-JsonPropertyValue -Object $settings -Name defaultProfile -Value ([string](Get-JsonPropertyValue -Object $powerShell7Profile -Name guid))
}
if ($null -ne $legacyPowerShellIndex) {
$legacyPowerShellProfile = $profiles[$legacyPowerShellIndex]
Set-JsonPropertyValue -Object $legacyPowerShellProfile -Name name -Value 'Windows PowerShell 5.1 (Legacy)'
Set-JsonPropertyValue -Object $legacyPowerShellProfile -Name hidden -Value $false
}
$removedIndices = New-Object System.Collections.Generic.HashSet[int]
foreach ($candidateIndex in $powerShell7CandidateIndices) {
if ($candidateIndex -eq $powerShell7Index) {
continue
}
if (Test-IsDisposablePowerShell7Profile -Profile $profiles[$candidateIndex]) {
if ($removedIndices.Add($candidateIndex)) {
$changed = $true
}
}
}
$orderedProfiles = New-Object System.Collections.Generic.List[object]
$addedIndices = New-Object System.Collections.Generic.HashSet[int]
function Add-ProfileByIndex {
param(
[int]$Index
)
if ($Index -lt 0) {
return
}
if ($removedIndices.Contains($Index)) {
return
}
if ($addedIndices.Add($Index)) {
$orderedProfiles.Add($profiles[$Index])
}
}
Add-ProfileByIndex -Index $powerShell7Index
if ($null -ne $legacyPowerShellIndex) {
Add-ProfileByIndex -Index $legacyPowerShellIndex
}
for ($i = 0; $i -lt $profiles.Count; $i++) {
Add-ProfileByIndex -Index $i
}
$orderedProfilesArray = @($orderedProfiles.ToArray())
if ((ConvertTo-Json $profiles -Depth 100 -Compress) -ne (ConvertTo-Json $orderedProfilesArray -Depth 100 -Compress)) {
$changed = $true
}
$settings.profiles.list = $orderedProfilesArray
if (-not $changed) {
Write-Host 'Windows Terminal settings already match the managed baseline.'
return
}
$backupPath = '{0}.bak-{1}' -f $settingsPath, (Get-Date -Format 'yyyyMMdd-HHmmss')
Copy-Item -LiteralPath $settingsPath -Destination $backupPath -Force
$settings | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath $settingsPath -Encoding utf8BOM
Write-Host "Updated Windows Terminal settings: $settingsPath"
Write-Host "Backup saved to: $backupPath"