diff --git a/src/pwsh-install-template.ps1 b/src/pwsh-install-template.ps1 index 19ec19b..91756ad 100644 --- a/src/pwsh-install-template.ps1 +++ b/src/pwsh-install-template.ps1 @@ -34,6 +34,15 @@ $script:__COREUTILS_CMD_PREDICATE__ = [System.Func[System.Management.Automation. param($n) $n -is [System.Management.Automation.Language.CommandAst] } +$script:__COREUTILS_READLINE_HAS_LAST_STATUS__ = [Microsoft.PowerShell.PSConsoleReadLine].GetMethod( + 'ReadLine', + [type[]]@( + [System.Management.Automation.Runspaces.Runspace], + [System.Management.Automation.EngineIntrinsics], + [System.Nullable[bool]] + ) +) -ne $null + $script:__COREUTILS_ARG_SPECIAL__ = [char[]] @("'", '"', '`', '$') # Wrap arguments into quotes. By being a function we can properly handle $variables. @@ -100,7 +109,12 @@ function PSConsoleHostReadLine { $lastRunStatus = $? Microsoft.PowerShell.Core\Set-StrictMode -Off - $line = [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus) + $line = if ($script:__COREUTILS_READLINE_HAS_LAST_STATUS__) { + [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext, $lastRunStatus) + } + else { + [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext) + } # If the line contains no coreutils name, we don't need to parse the AST at all. if (-not $script:__COREUTILS_FAST_SKIP__.IsMatch($line)) { diff --git a/src/pwsh-install.ps1 b/src/pwsh-install.ps1 index 2c70c32..5bd4859 100644 --- a/src/pwsh-install.ps1 +++ b/src/pwsh-install.ps1 @@ -21,6 +21,9 @@ $SectionMarker = '60b36fc6-2d59-49df-be51-28dd2f4c3c9a' $MarkerLine = "# DO NOT MODIFY -- coreutils -- $SectionMarker" # Earliest PowerShell that supports PSNativeCommandPreserveBytePipe. $MinPwshVersion = [version]'7.4.0' +# PowerShell 7.4 ships PSReadLine 2.3.4, which has the +# ReadLine(Runspace, EngineIntrinsics, bool?) overload used by the profile hook. +$MinPSReadLineVersion = [version]'2.3.4' # Contains SID --> Microsoft.PowerShell_profile.ps1 mappins, # such that we can clean them up on uninstall. $ProfilesRegPath = 'HKLM:\SOFTWARE\Microsoft\coreutils\PowerShellProfiles' @@ -34,6 +37,24 @@ function Remove-FileIfExists([string]$Path) { } } +function Assert-PSReadLineRequirement { + try { + $module = Import-Module PSReadLine -PassThru -ErrorAction Stop | Select-Object -First 1 + } + catch { + throw "PowerShell integration requires PSReadLine $MinPSReadLineVersion or newer, but PSReadLine could not be loaded: $($_.Exception.Message)" + } + + if (!$module -or !$module.Version) { + throw "PowerShell integration requires PSReadLine $MinPSReadLineVersion or newer, but the resolved PSReadLine module did not report a version" + } + + if ($module.Version -lt $MinPSReadLineVersion) { + $path = if ($module.Path) { " from '$($module.Path)'" } else { '' } + throw "PowerShell integration requires PSReadLine $MinPSReadLineVersion or newer, but PowerShell resolved PSReadLine $($module.Version)$path. Update or remove the older PSReadLine module and re-run the installer." + } +} + function Get-InjectedSection([string]$CmdDir) { $templatePath = Join-Path $PSScriptRoot 'pwsh-install-template.ps1' $template = Get-Content -LiteralPath $templatePath -Raw @@ -243,6 +264,7 @@ function Get-ProfilePlan([bool] $Install, [string]$Scope) { $install = $Action -eq 'Install' $plan = @(Get-ProfilePlan $install $Scope) $section = if ($install) { + Assert-PSReadLineRequirement Get-InjectedSection $CmdDir } else {