diff --git a/windows_configs/powertoys_setup.ps1 b/windows_configs/powertoys_setup.ps1 new file mode 100644 index 0000000..382c2ed --- /dev/null +++ b/windows_configs/powertoys_setup.ps1 @@ -0,0 +1,147 @@ +[CmdletBinding()] +param() + +$ErrorActionPreference = "Stop" + +$powerToysDscDocument = Join-Path -Path $PSScriptRoot -ChildPath "powertoys.dsc.yaml" + +function Test-IsAdministrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +if (-not (Test-IsAdministrator)) { + $powershellName = if ($PSVersionTable.PSEdition -eq "Core") { + "pwsh.exe" + } else { + "powershell.exe" + } + + $powershellPath = Join-Path -Path $PSHOME -ChildPath $powershellName + $elevatedArguments = @( + "-NoProfile" + "-ExecutionPolicy" + "Bypass" + "-File" + "`"$PSCommandPath`"" + ) + + $elevatedProcess = Start-Process ` + -FilePath $powershellPath ` + -Verb RunAs ` + -ArgumentList $elevatedArguments ` + -Wait ` + -PassThru + exit $elevatedProcess.ExitCode +} + +function Invoke-WinGetCommand { + param( + [Parameter(Mandatory = $true)] + [string[]]$ArgumentList + ) + + $process = Start-Process ` + -FilePath "winget.exe" ` + -ArgumentList $ArgumentList ` + -NoNewWindow ` + -PassThru ` + -Wait + + if ($process.ExitCode -ne 0) { + throw "winget.exe exited with code $($process.ExitCode)" + } +} + +# Stopgap: PowerToys DSC does not yet support AltWindowCycle. Move this +# configuration into powertoys.dsc.yaml once native DSC support is available. +function Set-PowerToysWindowHopper { + $powerToysDirectory = Join-Path -Path $env:LOCALAPPDATA -ChildPath "Microsoft\PowerToys" + $generalSettingsPath = Join-Path -Path $powerToysDirectory -ChildPath "settings.json" + $windowHopperDirectory = Join-Path -Path $powerToysDirectory -ChildPath "AltWindowCycle" + $windowHopperSettingsPath = Join-Path -Path $windowHopperDirectory -ChildPath "settings.json" + + if (-not (Test-Path -LiteralPath $generalSettingsPath -PathType Leaf)) { + throw "PowerToys settings were not found: $generalSettingsPath" + } + + $generalSettings = Get-Content -LiteralPath $generalSettingsPath -Raw | ConvertFrom-Json + if ($null -eq $generalSettings.enabled) { + $generalSettings | Add-Member -MemberType NoteProperty -Name "enabled" -Value ([pscustomobject]@{}) + } + + $generalSettings.enabled | Add-Member ` + -MemberType NoteProperty ` + -Name "AltWindowCycle" ` + -Value $true ` + -Force + $generalSettings | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath $generalSettingsPath -Encoding utf8 + + New-Item -ItemType Directory -Path $windowHopperDirectory -Force | Out-Null + + $windowHopperSettings = [pscustomobject]@{ + name = "AltWindowCycle" + version = "1.0" + properties = [pscustomobject]@{ + next_window_shortcut = [pscustomobject]@{ + win = $false + ctrl = $false + alt = $true + shift = $false + code = 0xC0 + key = "" + } + previous_window_shortcut = [pscustomobject]@{ + win = $false + ctrl = $false + alt = $true + shift = $true + code = 0xC0 + key = "" + } + } + } + + $windowHopperSettings | ConvertTo-Json -Depth 100 | Set-Content -LiteralPath $windowHopperSettingsPath -Encoding utf8 +} + +function Restart-PowerToys { + $powerToys = Get-Process -Name "PowerToys" -ErrorAction SilentlyContinue + if ($null -eq $powerToys) { + return + } + + $powerToysPath = Join-Path -Path $env:ProgramFiles -ChildPath "PowerToys\PowerToys.exe" + if (-not (Test-Path -LiteralPath $powerToysPath -PathType Leaf)) { + throw "PowerToys executable was not found: $powerToysPath" + } + + $powerToys | Stop-Process -Force + Start-Process -FilePath "explorer.exe" -ArgumentList "`"$powerToysPath`"" +} + +if (-not (Test-Path -LiteralPath $powerToysDscDocument -PathType Leaf)) { + throw "PowerToys DSC document was not found: $powerToysDscDocument" +} + +Invoke-WinGetCommand -ArgumentList @( + "configure" + "--file" + $powerToysDscDocument +) + +Invoke-WinGetCommand -ArgumentList @( + "upgrade" + "--id" + "Microsoft.PowerToys" + "--exact" + "--source" + "winget" + "--silent" + "--accept-package-agreements" + "--accept-source-agreements" +) + +Set-PowerToysWindowHopper +Restart-PowerToys diff --git a/windows_configs/windows_setup.ps1 b/windows_configs/windows_setup.ps1 index 088786c..36e1fa8 100644 --- a/windows_configs/windows_setup.ps1 +++ b/windows_configs/windows_setup.ps1 @@ -6,7 +6,7 @@ $ErrorActionPreference = "Stop" $wingetUpdateScript = Join-Path -Path $PSScriptRoot -ChildPath "winget_update.ps1" $wslSetupScript = Join-Path -Path $PSScriptRoot -ChildPath "wsl_setup.ps1" $profileSource = Join-Path -Path $PSScriptRoot -ChildPath "Microsoft.PowerShell_profile.ps1" -$powerToysDscDocument = Join-Path -Path $PSScriptRoot -ChildPath "powertoys.dsc.yaml" +$powerToysSetupScript = Join-Path -Path $PSScriptRoot -ChildPath "powertoys_setup.ps1" $dotfilesRoot = Split-Path -Path $PSScriptRoot -Parent $failures = [System.Collections.Generic.List[string]]::new() @@ -151,7 +151,7 @@ function Set-ScancodeMap { } } -foreach ($path in @($wingetUpdateScript, $wslSetupScript, $profileSource, $powerToysDscDocument)) { +foreach ($path in @($wingetUpdateScript, $wslSetupScript, $profileSource, $powerToysSetupScript)) { if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Windows setup source was not found: $path" } @@ -197,15 +197,17 @@ $env:Path = @( ) -join ";" $powerToysArguments = @( - "configure" - "--file" - $powerToysDscDocument + "-NoProfile" + "-ExecutionPolicy" + "Bypass" + "-File" + $powerToysSetupScript ) Invoke-NativeCommand ` - -FilePath "winget.exe" ` + -FilePath $powershellPath ` -ArgumentList $powerToysArguments ` - -Description "Applying PowerToys configuration (Caps Lock -> Left Ctrl)" + -Description "Applying PowerToys configuration" Invoke-NativeCommand ` -FilePath "npm.cmd" `