Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions locales/app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ _version: 2
zh_CN: "未安装 Powershell"
zh_TW: "未安裝 Powershell"
de: "Powershell ist nicht installiert"
"PowerShellGet Update-Module is unavailable or could not be loaded. Skipping PowerShell module updates.":
en: "PowerShellGet Update-Module is unavailable or could not be loaded. Skipping PowerShell module updates."
lt: "PowerShellGet Update-Module nepasiekiama arba jos nepavyko įkelti. Praleidžiamas PowerShell modulių atnaujinimas."
es: "PowerShellGet Update-Module no está disponible o no se pudo cargar. Se omite la actualización de módulos de PowerShell."
fr: "PowerShellGet Update-Module n'est pas disponible ou n'a pas pu être chargé. Mise à jour des modules PowerShell ignorée."
zh_CN: "PowerShellGet Update-Module 不可用或无法加载。正在跳过 PowerShell 模块更新。"
zh_TW: "PowerShellGet Update-Module 無法使用或無法載入。正在略過 PowerShell 模組更新。"
de: "PowerShellGet Update-Module ist nicht verfügbar oder konnte nicht geladen werden. PowerShell-Modulaktualisierung wird übersprungen."
"Error detecting current distribution: {error}":
en: "Error detecting current distribution: %{error}"
lt: "Klaida nustatant dabartinę distribuciją: %{error}"
Expand Down
74 changes: 60 additions & 14 deletions src/steps/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1206,25 +1206,71 @@ pub fn run_powershell(ctx: &ExecutionContext) -> Result<()> {

print_separator(t!("Powershell Modules Update"));

let mut cmd = "Update-Module".to_string();
// For PowerShell Core, run without sudo (defaults to CurrentUser scope).
// For Windows PowerShell, use sudo (defaults to AllUsers scope).
let use_sudo = !powershell.is_pwsh();

let check_use_sudo = use_sudo && !ctx.run_type().dry();
Comment thread
niStee marked this conversation as resolved.
Outdated
if !powershell.has_command(ctx, "Update-Module", check_use_sudo)? {
let message = t!(
"PowerShellGet Update-Module is unavailable or could not be loaded. Skipping PowerShell module updates."
)
.to_string();
print_warning(&message);
return Err(SkipStep(message).into());
}

let cmd = powershell_update_modules_command(ctx.config().verbose(), ctx.config().yes(Step::Powershell));

println!("{}", t!("Updating modules..."));

powershell.build_command(ctx, &cmd, use_sudo)?.status_checked()
}

fn powershell_update_modules_command(verbose: bool, assume_yes: bool) -> String {
let mut cmd = "$params = @{};".to_string();
cmd.push_str(" $updateModule = Get-Command Update-Module -ErrorAction Stop;");

if ctx.config().verbose() {
cmd.push_str(" -Verbose");
if verbose {
cmd.push_str(" $params['Verbose'] = $true;");
}
if ctx.config().yes(Step::Powershell) {
cmd.push_str(" -Force");
if assume_yes {
// Avoid -Force here: PowerShellGet uses it to reinstall already-current modules,
// which can lock PackageManagement in the running Windows PowerShell session.
cmd.push_str(" $params['Confirm'] = $false;");
cmd.push_str(
" if ($updateModule.Parameters.ContainsKey('AcceptLicense')) { $params['AcceptLicense'] = $true; }",
);
}

println!("{}", t!("Updating modules..."));
cmd.push_str(" & $updateModule @params");
cmd
}
Comment thread
niStee marked this conversation as resolved.

if powershell.is_pwsh() {
// For PowerShell Core, run Update-Module without sudo since it defaults to CurrentUser scope
// and Update-Module updates all modules regardless of their original installation scope
powershell.build_command(ctx, &cmd, false)?.status_checked()
} else {
// For (Windows) PowerShell, use sudo since it defaults to AllUsers scope
// and may need administrator privileges
powershell.build_command(ctx, &cmd, true)?.status_checked()
#[cfg(test)]
mod powershell_tests {
use super::powershell_update_modules_command;

#[test]
fn assume_yes_suppresses_confirmation_without_force() {
let cmd = powershell_update_modules_command(false, true);

assert!(cmd.contains("$params['Confirm'] = $false"));
assert!(cmd.contains("$updateModule.Parameters.ContainsKey('AcceptLicense')"));
assert!(cmd.contains("$params['AcceptLicense'] = $true"));
assert!(cmd.contains("Get-Command Update-Module -ErrorAction Stop"));
assert!(cmd.contains("& $updateModule @params"));
assert!(!cmd.contains("-Force"));
assert!(!cmd.contains("exit 0"));
assert!(!cmd.contains("topgradeUpdateModule"));
}

#[test]
fn verbose_sets_verbose_parameter() {
let cmd = powershell_update_modules_command(true, false);

assert!(cmd.contains("$params['Verbose'] = $true"));
assert!(!cmd.contains("AcceptLicense"));
}
}

Expand Down
49 changes: 49 additions & 0 deletions src/steps/powershell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ impl Powershell {
self.is_pwsh
}

pub fn has_command(&self, ctx: &ExecutionContext, command_name: &str, use_sudo: bool) -> Result<bool> {
let cmd = has_command_script(command_name);

let output = if use_sudo {
self.build_command(ctx, &cmd, true)?.output_checked_utf8()?
} else {
self.build_command_internal(ctx, &cmd).output_checked_utf8()?
};

Ok(output.stdout.trim().eq_ignore_ascii_case("true"))
}

/// Builds an "internal" powershell command
pub fn build_command_internal(&self, ctx: &ExecutionContext, cmd: &str) -> Executor {
let mut command = ctx.execute(&self.path).always();
Expand Down Expand Up @@ -157,3 +169,40 @@ impl Powershell {
.unwrap_or(false)
}
}

fn has_command_script(command_name: &str) -> String {
format!(
"if (Get-Command -Name {} -ErrorAction SilentlyContinue) {{ Write-Output 'true' }}",
powershell_single_quote(command_name)
)
}

fn powershell_single_quote(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}
Comment thread
niStee marked this conversation as resolved.
Outdated

#[cfg(test)]
mod tests {
use super::{has_command_script, powershell_single_quote};

#[test]
fn has_command_script_checks_quoted_command_name() {
assert_eq!(
has_command_script("Update-Module"),
"if (Get-Command -Name 'Update-Module' -ErrorAction SilentlyContinue) { Write-Output 'true' }"
);
}

#[test]
fn has_command_script_escapes_command_name_as_data() {
assert_eq!(
has_command_script("Bob's-Command"),
"if (Get-Command -Name 'Bob''s-Command' -ErrorAction SilentlyContinue) { Write-Output 'true' }"
);
}

#[test]
fn powershell_single_quote_escapes_single_quotes() {
assert_eq!(powershell_single_quote("Bob's-Command"), "'Bob''s-Command'");
}
}
Loading