From 899bd7b553b1a17d0dddf614986e7190f046915d Mon Sep 17 00:00:00 2001 From: Uarz <149165079+Uarz@users.noreply.github.com> Date: Thu, 27 Aug 2026 02:46:32 +0800 Subject: [PATCH 1/2] Skip signed PowerShell profiles instead of breaking them Installing coreutils injects a managed section into the user's PowerShell profile. If that profile is Authenticode-signed (e.g. under an AllSigned execution policy), rewriting the file invalidates the signature and the profile no longer loads, breaking the user's shell. Detect signed profiles with Get-AuthenticodeSignature before writing: when a signature is present (Status != NotSigned), skip the injection for that profile and emit a warning instead of silently corrupting it. Fixes #161 --- src/pwsh-install.ps1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pwsh-install.ps1 b/src/pwsh-install.ps1 index aa04834..3ae5025 100644 --- a/src/pwsh-install.ps1 +++ b/src/pwsh-install.ps1 @@ -91,6 +91,18 @@ function Update-PowerShellProfile([string]$Path, [bool] $Install, [bool] $UseBom $Path = $profile.ResolvedTarget } + # A signed PowerShell profile cannot be modified without invalidating its + # Authenticode signature, which breaks profile loading under signed + # execution policies (AllSigned/etc.). Detect such profiles and skip them + # instead of silently breaking them. See microsoft/coreutils#161. + if (Test-Path -LiteralPath $Path) { + $signature = Get-AuthenticodeSignature -LiteralPath $Path -ErrorAction Ignore + if ($null -ne $signature -and $signature.Status -ne 'NotSigned') { + Write-Warning "Skipping signed PowerShell profile '$Path': modifying it would break its Authenticode signature. coreutils commands will not be available in this profile." + return + } + } + # Get-Content uses .NET's StreamReader, so it auto-detects UTF-8/UTF-16 with BOM. $text = Get-Content -LiteralPath $Path -Raw -ErrorAction Ignore if (!$text) { From c2b9ebf2e428cba5868af25e9713d97f2fdc8f04 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Mon, 31 Aug 2026 13:54:45 +0200 Subject: [PATCH 2/2] Skip updating skipped profiles --- src/pwsh-install.ps1 | 48 ++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/pwsh-install.ps1 b/src/pwsh-install.ps1 index 3ae5025..34f1f36 100644 --- a/src/pwsh-install.ps1 +++ b/src/pwsh-install.ps1 @@ -40,6 +40,14 @@ function Remove-FileIfExists([string]$Path) { } } +function Resolve-ProfilePath([string]$Path) { + $profileFile = Get-Item -LiteralPath $Path -Force -ErrorAction Ignore + if ($profileFile) { + return $profileFile.ResolvedTarget + } + return $Path +} + function Get-EnabledCoreutilsAliases([string]$CmdDir) { [string[]]$disabled = @() if ($props = Get-ItemProperty -LiteralPath $CoreutilsRegPath -Name $DisabledUtilitiesRegName -ErrorAction Ignore) { @@ -78,29 +86,15 @@ function Get-InjectedSection([string]$CmdDir) { } function Update-PowerShellProfile([string]$Path, [bool] $Install, [bool] $UseBom, [string]$Section, [bool]$RefreshOnly) { - $parent = Split-Path -LiteralPath $Path if ($Install -and !$RefreshOnly) { + $parent = Split-Path -LiteralPath $Path [void](New-Item -Path $parent -ItemType Directory -Force) } - elseif (!(Test-Path -LiteralPath $Path)) { - return - } - $profile = Get-Item -LiteralPath $Path -Force -ErrorAction Ignore - if ($profile) { - $Path = $profile.ResolvedTarget - } - - # A signed PowerShell profile cannot be modified without invalidating its - # Authenticode signature, which breaks profile loading under signed - # execution policies (AllSigned/etc.). Detect such profiles and skip them - # instead of silently breaking them. See microsoft/coreutils#161. - if (Test-Path -LiteralPath $Path) { - $signature = Get-AuthenticodeSignature -LiteralPath $Path -ErrorAction Ignore - if ($null -ne $signature -and $signature.Status -ne 'NotSigned') { - Write-Warning "Skipping signed PowerShell profile '$Path': modifying it would break its Authenticode signature. coreutils commands will not be available in this profile." - return - } + # We cannot re-sign the profile, so skip it. + $signature = Get-AuthenticodeSignature -LiteralPath $Path -ErrorAction SilentlyContinue + if ($signature -and ($signature.Status -ne [System.Management.Automation.SignatureStatus]::NotSigned)) { + return $false } # Get-Content uses .NET's StreamReader, so it auto-detects UTF-8/UTF-16 with BOM. @@ -116,7 +110,7 @@ function Update-PowerShellProfile([string]$Path, [bool] $Install, [bool] $UseBom throw "Invalid coreutils section markers in PowerShell profile: $Path" } if ($RefreshOnly -and $markerCount -eq 0) { - return + return $true } # Strip the existing section (markers + content + any surrounding blank lines) in one shot. @@ -135,7 +129,7 @@ function Update-PowerShellProfile([string]$Path, [bool] $Install, [bool] $UseBom if (!$text) { Remove-FileIfExists $Path - return + return $true } $text += "`r`n" @@ -151,6 +145,8 @@ function Update-PowerShellProfile([string]$Path, [bool] $Install, [bool] $UseBom Remove-Item -LiteralPath $newPath -Force -ErrorAction Ignore throw } + + return $true } function Get-MsiPwshProfilePaths { @@ -243,6 +239,8 @@ function Get-ProfilePlan([bool] $Install, [string]$Scope) { return $null } + $Path = Resolve-ProfilePath $Path + $existing = $plan[$Path] if ($existing) { return $existing @@ -298,6 +296,9 @@ function Get-RefreshProfilePlan { if (!$Path) { return } + + $Path = Resolve-ProfilePath $Path + if ($plan[$Path]) { return } @@ -337,7 +338,10 @@ else { } foreach ($entry in $plan) { - Update-PowerShellProfile -Path $entry.Path -Install $entry.Install -UseBom $false -Section $section -RefreshOnly $refresh + if (!Update-PowerShellProfile -Path $entry.Path -Install $entry.Install -UseBom $false -Section $section -RefreshOnly $refresh) { + # Update failed: Skip the record update. + $entry.RecordSid = $null + } } # Only adjust records once every Update succeeded. A failure mid-loop leaves