From f78514dbd86fe5c3003435899c6427fec067acb7 Mon Sep 17 00:00:00 2001 From: Andre Toerien Date: Sun, 22 Jun 2025 15:39:50 +0200 Subject: [PATCH] fix(powershell): execution policy check breaks when run in pwsh When topgrade is run from within pwsh, the execution policy check breaks for the Windows Update and Windows Store steps, because they use normal powershell and the inherited PSModulePath environment variable breaks the Microsoft.PowerShell.Security module import. So we unset that variable to fix the issue, but also allow for those steps to use pwsh as neither step actually requires PowerShell 5. Co-authored-by: nistee <52573120+niStee@users.noreply.github.com> --- src/execution_context.rs | 16 ++++++++++++---- src/step.rs | 2 +- src/steps/os/windows.rs | 5 ++--- src/steps/powershell.rs | 41 ++++++++++++++++++---------------------- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/execution_context.rs b/src/execution_context.rs index 86a08b14..cdd543e3 100644 --- a/src/execution_context.rs +++ b/src/execution_context.rs @@ -1,14 +1,16 @@ #![allow(dead_code)] +use color_eyre::eyre::Result; +use std::env::var; +use std::path::Path; +use std::sync::{LazyLock, Mutex}; + use crate::executor::RunType; +use crate::powershell::Powershell; #[cfg(target_os = "linux")] use crate::steps::linux::Distribution; use crate::sudo::Sudo; use crate::utils::{get_require_sudo_string, require_option}; use crate::{config::Config, executor::Executor}; -use color_eyre::eyre::Result; -use std::env::var; -use std::path::Path; -use std::sync::Mutex; pub struct ExecutionContext<'a> { run_type: RunType, @@ -22,6 +24,7 @@ pub struct ExecutionContext<'a> { under_ssh: bool, #[cfg(target_os = "linux")] distribution: &'a Result, + powershell: LazyLock, } impl<'a> ExecutionContext<'a> { @@ -40,6 +43,7 @@ impl<'a> ExecutionContext<'a> { under_ssh, #[cfg(target_os = "linux")] distribution, + powershell: LazyLock::new(Powershell::new), } } @@ -76,4 +80,8 @@ impl<'a> ExecutionContext<'a> { pub fn distribution(&self) -> &Result { self.distribution } + + pub fn powershell(&self) -> &Powershell { + &self.powershell + } } diff --git a/src/step.rs b/src/step.rs index da0d3622..9896bd79 100644 --- a/src/step.rs +++ b/src/step.rs @@ -464,7 +464,7 @@ impl Step { Pnpm => runner.execute(*self, "pnpm", || node::run_pnpm_upgrade(ctx))?, Poetry => runner.execute(*self, "Poetry", || generic::run_poetry(ctx))?, Powershell => { - let powershell = powershell::Powershell::new(); + let powershell = ctx.powershell(); if powershell.is_available() { runner.execute(Powershell, "Powershell Modules Update", || { powershell.update_modules(ctx) diff --git a/src/steps/os/windows.rs b/src/steps/os/windows.rs index 048234b4..b60be3e8 100644 --- a/src/steps/os/windows.rs +++ b/src/steps/os/windows.rs @@ -7,7 +7,6 @@ use tracing::debug; use crate::command::CommandExt; use crate::execution_context::ExecutionContext; -use crate::powershell; use crate::step::Step; use crate::terminal::{print_separator, print_warning}; use crate::utils::{require, which}; @@ -226,7 +225,7 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> { } pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { - let powershell = powershell::Powershell::windows_powershell(); + let powershell = ctx.powershell(); print_separator(t!("Windows Update")); @@ -244,7 +243,7 @@ pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { } pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> { - let powershell = powershell::Powershell::windows_powershell(); + let powershell = ctx.powershell(); print_separator(t!("Microsoft Store")); diff --git a/src/steps/powershell.rs b/src/steps/powershell.rs index e15876f3..15c97361 100644 --- a/src/steps/powershell.rs +++ b/src/steps/powershell.rs @@ -34,7 +34,7 @@ impl Powershell { .or_else(|| which("powershell").map(|p| (Some(p), false))) .unwrap_or((None, false)); - let profile = path.as_ref().and_then(Self::get_profile); + let profile = path.as_ref().and_then(|path| Self::get_profile(path, is_pwsh)); Self { path, profile, is_pwsh } } @@ -43,29 +43,12 @@ impl Powershell { self.path.is_some() } - #[cfg(windows)] - pub fn windows_powershell() -> Self { - if terminal::is_dumb() { - return Self { - path: None, - profile: None, - is_pwsh: false, - }; - } - - Self { - path: which("powershell"), - profile: None, - is_pwsh: false, - } - } - pub fn profile(&self) -> Option<&PathBuf> { self.profile.as_ref() } - fn get_profile(path: &PathBuf) -> Option { - let profile = Self::build_command_internal(path, "Split-Path $PROFILE") + fn get_profile(path: &PathBuf, is_pwsh: bool) -> Option { + let profile = Self::build_command_internal(path, is_pwsh, "Split-Path $PROFILE") .output_checked_utf8() .map(|output| output.stdout.trim().to_string()) .and_then(|s| PathBuf::from(s).require()) @@ -75,12 +58,18 @@ impl Powershell { } /// Builds an "internal" powershell command - fn build_command_internal(path: &PathBuf, cmd: &str) -> Command { + fn build_command_internal(path: &PathBuf, is_pwsh: bool, cmd: &str) -> Command { let mut command = Command::new(path); command.args(["-NoProfile", "-Command"]); command.arg(cmd); + // If topgrade was run from pwsh, but we are trying to run powershell, then + // the inherited PSModulePath breaks module imports + if !is_pwsh { + command.env_remove("PSModulePath"); + } + command } @@ -106,6 +95,12 @@ impl Powershell { command.args(["-NoProfile", "-Command"]); command.arg(cmd); + // If topgrade was run from pwsh, but we are trying to run powershell, then + // the inherited PSModulePath breaks module imports + if !self.is_pwsh { + command.env_remove("PSModulePath"); + } + Ok(command) } @@ -158,7 +153,7 @@ impl Powershell { // Find the index of our target policy let target_idx = valid_policies.iter().position(|&p| p == policy); - let mut command = Self::build_command_internal(powershell, "Get-ExecutionPolicy"); + let mut command = Self::build_command_internal(powershell, self.is_pwsh, "Get-ExecutionPolicy"); let current_policy = command .output_checked_utf8() @@ -187,7 +182,7 @@ impl Powershell { if let Some(powershell) = &self.path { let cmd = format!("Get-Module -ListAvailable {}", module_name); - return Self::build_command_internal(powershell, &cmd) + return Self::build_command_internal(powershell, self.is_pwsh, &cmd) .output_checked() .map(|output| !output.stdout.trim_ascii().is_empty()) .unwrap_or(false);