Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

### Features

- **scoop-forcekill:** Support killing running applications and services ([#6603](https://github.com/ScoopInstaller/Scoop/pull/6603))
- **autoupdate:** GitHub predefined hashes support ([#6416](https://github.com/ScoopInstaller/Scoop/issues/6416), [#6435](https://github.com/ScoopInstaller/Scoop/issues/6435))

### Bug Fixes
Expand Down
154 changes: 139 additions & 15 deletions lib/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -533,23 +533,147 @@ function persist_permission($manifest, $global) {
}
}

# test if there are running processes
function test_running_process($app, $global) {
$processdir = appdir $app $global | Convert-Path
$running_processes = Get-Process | Where-Object { $_.Path -like "$processdir\*" } | Out-String

if ($running_processes) {
if (get_config IGNORE_RUNNING_PROCESSES) {
warn "The following instances of `"$app`" are still running. Scoop is configured to ignore this condition."
Write-Host $running_processes
return $false
} else {
error "The following instances of `"$app`" are still running. Close them and try again."
Write-Host $running_processes
return $true
# check if there are running processes
function check_running_process($app, $global) {
if (get_config NO_JUNCTION) {
$version = Select-CurrentVersion -App $app -Global:$global
} else {
$version = 'current'
}

$json = install_info $app $version $global
$forcekill = $false
$forcekill_services = @()

if ($json) {
if ($json.forcekill) { $forcekill = $true }
if ($json.forcekill_services) { $forcekill_services = @($json.forcekill_services) }
}

$processdir = if (get_config NO_JUNCTION) { versiondir $app $version $global } else { versiondir $app 'current' $global }
$processdir = $processdir | Convert-Path

$servicesToStop = @()
if ($forcekill) {
$wmi_dir = $processdir -replace '\\', '\\' -replace '_', '[_]' -replace "'", "''"
$filter = "PathName LIKE '%$wmi_dir%'"
$foundServices = @(Get-CimInstance -ClassName Win32_Service -Filter $filter)
if ($foundServices) {
$servicesToStop += $foundServices.Name
}
if ($forcekill_services) {
$servicesToStop += $forcekill_services
}

if ($servicesToStop.Count -gt 0) {
$servicesToStop = @($servicesToStop | Select-Object -Unique)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

$running_processes = @(Get-Process | Where-Object { $_.Path -like "$processdir\*" })

$blocked = $false
if ($running_processes -and !$forcekill -and !(get_config IGNORE_RUNNING_PROCESSES)) {
$blocked = $true
}

return @{
Blocked = $blocked
Forcekill = $forcekill
App = $app
RunningProcesses = @($running_processes)
ServicesToStop = @($servicesToStop)
ProcessDir = $processdir
}
}

function stop_running_process($test_result) {
$forcekill = $test_result.Forcekill
$app = $test_result.App
$running_processes = $test_result.RunningProcesses
$servicesToStop = $test_result.ServicesToStop
$processdir = $test_result.ProcessDir

$stopped_services = @()
$stopped_processes = @()
$blocked = $test_result.Blocked

if ($forcekill -and $servicesToStop.Count -gt 0 -and -not (is_admin)) {
warn 'Administrative privileges are required to stop the associated services. Aborting forcekill.'
$blocked = $true
} else {
return $false
if ($running_processes.Count -gt 0) {
if ($forcekill) {
warn "The following instances of `"$app`" are still running. Scoop is configured to force kill them."
Write-Host ($running_processes | Out-String)

# Add all processes to the restart list. Warn admin if some can't be reliably restarted due to missing Paths.
$has_visible_window = @($running_processes | Where-Object MainWindowHandle -NE 0).Count -gt 0

if ($has_visible_window) {
warn "Some instances of `"$app`" had visible windows. They will NOT be automatically restarted."
} else {
foreach ($proc in $running_processes) {
if ($proc.Path) {
$stopped_processes += $proc.Path
} else {
warn "Process $($proc.Name) (ID $($proc.Id)) cannot be reliably restarted because its executable path is unreadable."
}
}
if ($stopped_processes.Count -gt 0) {
$stopped_processes = @($stopped_processes | Select-Object -Unique)
}
}

foreach ($proc in $running_processes) {
try {
Stop-Process -Id $proc.Id -Force -ErrorAction Stop
} catch {
warn "Failed to stop process $($proc.Name) (ID $($proc.Id)): $($_.Exception.Message)"
}
}

$still_running = @(Get-Process | Where-Object { $_.Path -like "$processdir\*" })
if ($still_running.Count -gt 0) {
warn "Some instances of `"$app`" could not be stopped."
$blocked = $true
}

} elseif (get_config IGNORE_RUNNING_PROCESSES) {
warn "The following instances of `"$app`" are still running. Scoop is configured to ignore this condition."
Write-Host ($running_processes | Out-String)
} else {
error "The following instances of `"$app`" are still running. Close them and try again."
Write-Host ($running_processes | Out-String)
$blocked = $true
}
}

if ($forcekill -and $servicesToStop.Count -gt 0) {
foreach ($svc in $servicesToStop) {
$status = (Get-Service -Name $svc -ErrorAction SilentlyContinue).Status
if ($status -eq 'Running') {
warn "Stopping service '$svc' associated with '$app'..."
try {
Stop-Service -Name $svc -Force -ErrorAction Stop
$stopped_services += $svc
} catch {
warn "Failed to stop service '$svc': $($_.Exception.Message)"
}

if ((Get-Service -Name $svc -ErrorAction SilentlyContinue).Status -eq 'Running') {
warn "Service '$svc' is still running."
$blocked = $true
}
}
}
}
}

return @{
Blocked = $blocked
ServicesToRestart = @($stopped_services)
ProcessesToRestart = @($stopped_processes)
}
}

Expand Down
88 changes: 88 additions & 0 deletions libexec/scoop-forcekill.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Usage: scoop forcekill <apps>
# Summary: Force close apps before updating
# Help: To mark a user-scoped app to force close:
# scoop forcekill <app>
#
# To mark a global app to force close:
# scoop forcekill -g <app>
#
# To explicitly define services to stop:
# scoop forcekill <app> --service <servicename>
#
# Options:
# -g, --global Force close globally installed apps
# -s, --service Services to stop (comma separated)

. "$PSScriptRoot\..\lib\getopt.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\versions.ps1"
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\install.ps1"

$opt, $apps, $err = getopt $args 'gs:' 'global', 'service='
if ($err) { "scoop forcekill: $err"; exit 1 }

$exitcode = 0

$global = $opt.g -or $opt.global

if (!$apps) {
my_usage
exit 1
}

if ($global -and !(is_admin)) {
error 'You need admin rights to mark a global app for forcekill.'
exit 1
}

foreach ($app in $apps) {
if (!(installed $app $global)) {
if ($global) {
error "'$app' is not installed globally."
} else {
error "'$app' is not installed."
}
$exitcode = 1
continue
}

if (get_config NO_JUNCTION) {
$version = Select-CurrentVersion -App $app -Global:$global
} else {
$version = 'current'
}
$dir = versiondir $app $version $global
$json = install_info $app $version $global
if (!$json) {
error "Failed to configure forcekill for '$app'."
$exitcode = 1
continue
}
$install = @{}
$json.PSObject.Properties | ForEach-Object { $install[$_.Name] = $_.Value }

$install.forcekill = $true

if ($opt.service) {
$services = $opt.service -split ',' | ForEach-Object { $_.Trim() }
$valid_services = @()
foreach ($svc in $services) {
if (Get-Service -Name $svc -ErrorAction SilentlyContinue) {
$valid_services += $svc
} else {
warn "Could not find service '$svc'. Skipping."
$exitcode = 1
}
}
if ($valid_services.Count -gt 0) {
$install.forcekill_services = @($valid_services | Select-Object -Unique)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

save_install_info $install $dir
success "$app is now marked to force close on update."
}

exit $exitcode
Comment thread
coderabbitai[bot] marked this conversation as resolved.
59 changes: 47 additions & 12 deletions libexec/scoop-reset.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
. "$PSScriptRoot\..\lib\shortcuts.ps1"

$opt, $apps, $err = getopt $args 'a' 'all'
if($err) { error "scoop reset: $err"; exit 1 }
if ($err) { error "scoop reset: $err"; exit 1 }
$all = $opt.a -or $opt.all

if(!$apps -and !$all) { error '<app> missing'; my_usage; exit 1 }
if (!$apps -and !$all) { error '<app> missing'; my_usage; exit 1 }

if($apps -eq '*' -or $all) {
$local = installed_apps $false | ForEach-Object { ,@($_, $false) }
$global = installed_apps $true | ForEach-Object { ,@($_, $true) }
if ($apps -eq '*' -or $all) {
$local = installed_apps $false | ForEach-Object { , @($_, $false) }
$global = installed_apps $true | ForEach-Object { , @($_, $true) }
$apps = @($local) + @($global)
}

Expand All @@ -30,17 +30,17 @@ $apps | ForEach-Object {

$app, $bucket, $version = parse_app $app

if(($global -eq $null) -and (installed $app $true)) {
if (($global -eq $null) -and (installed $app $true)) {
# set global flag when running reset command on specific app
$global = $true
}

if($app -eq 'scoop') {
if ($app -eq 'scoop') {
# skip scoop
return
}

if(!(installed $app)) {
if (!(installed $app)) {
error "'$app' isn't installed"
return
}
Expand All @@ -57,19 +57,21 @@ $apps | ForEach-Object {
return
}

if($global -and !(is_admin)) {
if ($global -and !(is_admin)) {
warn "'$app' ($version) is a global app. You need admin rights to reset it. Skipping."
return
}

write-host "Resetting $app ($version)."
Write-Host "Resetting $app ($version)."

$dir = Convert-Path (versiondir $app $version $global)
$original_dir = $dir
$persist_dir = persistdir $app $global

#region Workaround for #2952
if (test_running_process $app $global) {
$running_ret = check_running_process $app $global
$stop_ret = stop_running_process $running_ret
if ($stop_ret.Blocked) {
return
}
#endregion Workaround for #2952
Comment thread
MetalDevOps marked this conversation as resolved.
Expand All @@ -85,10 +87,43 @@ $apps | ForEach-Object {
env_rm $manifest $global $architecture
env_add_path $manifest $dir $global $architecture
env_set $manifest $global $architecture
# unlink all potential old link before re-persisting
unlink_persist_data $manifest $original_dir
persist_data $manifest $original_dir $persist_dir
persist_permission $manifest $global

$stopped_services = $stop_ret.ServicesToRestart
$stopped_processes = $stop_ret.ProcessesToRestart

if ($stopped_services.Count -gt 0) {
foreach ($svc in $stopped_services) {
warn "Restarting service '$svc' associated with '$app'..."
try {
Start-Service -Name $svc -ErrorAction Stop
} catch {
warn "Failed to restart service '$svc': $($_.Exception.Message)"
}
}
}

if ($stopped_processes.Count -gt 0) {
$new_version = current_version $app $global
$new_processdir = versiondir $app $new_version $global | Convert-Path
foreach ($proc in $stopped_processes) {
if ($proc.StartsWith($original_dir)) {
$proc = $proc.Replace($original_dir, $new_processdir)
}
warn "Restarting process '$(Split-Path $proc -Leaf)' associated with '$app'..."
if (Test-Path $proc) {
try {
Start-Process -FilePath $proc -ErrorAction Stop
} catch {
warn "Failed to restart process '$(Split-Path $proc -Leaf)': $($_.Exception.Message)"
}
} else {
warn "Process executable no longer exists at '$proc'."
}
}
}
}

exit 0
Loading