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>
This commit is contained in:
Andre Toerien
2025-06-22 15:39:50 +02:00
committed by Gideon
parent 9fc5fe9798
commit f78514dbd8
4 changed files with 33 additions and 31 deletions

View File

@@ -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<Distribution>,
powershell: LazyLock<Powershell>,
}
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<Distribution> {
self.distribution
}
pub fn powershell(&self) -> &Powershell {
&self.powershell
}
}

View File

@@ -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)

View File

@@ -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"));

View File

@@ -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<PathBuf> {
let profile = Self::build_command_internal(path, "Split-Path $PROFILE")
fn get_profile(path: &PathBuf, is_pwsh: bool) -> Option<PathBuf> {
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);