Skip to content

Commit a708def

Browse files
author
vp
committed
Add kill-dotnet command for interactive and non-interactive process termination
1 parent a1c976d commit a708def

3 files changed

Lines changed: 98 additions & 1 deletion

File tree

.vscode/tasks-misc.ps1

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
param(
1313
# Command routing (added wrapper around original script logic)
1414
[Parameter(Position=0)] [string] $Command = 'help',
15+
# Optional process id for non-interactive kill-dotnet
16+
[int] $ProcessId,
17+
[switch] $ForceKill,
1518

1619
# === Original CombineSources.ps1 parameters (kept defaults) ===
1720
[string]$OutputDirectory = './.tmp',
@@ -42,6 +45,43 @@ $ErrorActionPreference = 'Stop'
4245
function Write-Section([string] $Text){ Write-Host "`n=== $Text ===" -ForegroundColor Magenta }
4346
function Fail([string] $Msg, [int] $Code=1){ Write-Error $Msg; exit $Code }
4447

48+
# Reused from diagnostics script (with minor fallback enhancements):
49+
${script:DotNetOnly} = $false
50+
function Select-Pid($title){
51+
try {
52+
Import-Module PwshSpectreConsole -ErrorAction Stop
53+
$procs = Get-Process | Where-Object { $_.Id -gt 0 }
54+
if($script:DotNetOnly){ $procs = $procs | Where-Object { $_.ProcessName -match 'dotnet|Presentation.Web.Server' } }
55+
$procs = $procs | Sort-Object ProcessName,Id
56+
$rows = @()
57+
foreach($p in $procs){
58+
$label = "$($p.ProcessName) (#$($p.Id))"
59+
if($rows -notcontains $label){ $rows += $label }
60+
}
61+
if(-not $rows){ Write-Host 'No matching processes found.' -ForegroundColor Yellow; return $null }
62+
$choices = $rows + 'Cancel'
63+
$sel = Read-SpectreSelection -Title $title -Choices $choices -EnableSearch -PageSize 25
64+
Write-Host "Raw selection: '$sel'" -ForegroundColor DarkGray
65+
if([string]::IsNullOrWhiteSpace($sel) -or $sel -eq 'Cancel'){ return $null }
66+
if($sel -match '\(#(\d+)\)$'){ Write-Host "Selected PID: $($Matches[1])" -ForegroundColor DarkGray; return [int]$Matches[1] }
67+
Write-Host "Could not parse PID from selection: $sel" -ForegroundColor Yellow
68+
return $null
69+
} catch {
70+
# Fallback to manual numeric selection if Spectre unavailable
71+
Write-Host ('Spectre selection unavailable; falling back to manual input. Reason: {0}' -f $_.Exception.Message) -ForegroundColor Yellow
72+
$procs = Get-Process | Where-Object { $_.Id -gt 0 }
73+
if($script:DotNetOnly){ $procs = $procs | Where-Object { $_.ProcessName -match 'dotnet|Presentation.Web.Server' } }
74+
$procs = $procs | Sort-Object ProcessName,Id
75+
if(-not $procs){ Write-Host 'No matching processes found.' -ForegroundColor Yellow; return $null }
76+
Write-Host 'Index | ProcessName | PID' -ForegroundColor Cyan
77+
for($i=0;$i -lt $procs.Count;$i++){ Write-Host ("{0,5} | {1,-25} | {2}" -f $i,$procs[$i].ProcessName,$procs[$i].Id) -ForegroundColor DarkGray }
78+
$raw = Read-Host 'Enter index to select (or blank to cancel)'
79+
if([string]::IsNullOrWhiteSpace($raw)){ return $null }
80+
if(-not ([int]::TryParse($raw,[ref]$idx)) -or $idx -lt 0 -or $idx -ge $procs.Count){ Write-Host 'Invalid index.' -ForegroundColor Red; return $null }
81+
return $procs[$idx].Id
82+
}
83+
}
84+
4585
function Run-CSharpRepl() {
4686
Write-Section 'Starting C# REPL'
4787
Write-Host 'Restoring dotnet tools...' -ForegroundColor Cyan
@@ -246,6 +286,7 @@ Commands:
246286
digest|combine-sources|combine|docs Generate consolidated markdown documentation per project (.g.md).
247287
clean|cleanup Remove build/output artifact directories (bin/obj/node_modules/etc.).
248288
repl|shell Run C# REPL (dotnet tool csharprepl) after tool restore.
289+
kill-dotnet Interactive (or non-interactive with -ProcessId) termination of a dotnet process.
249290
help|? Show this help.
250291
251292
combine-sources Parameters (defaults shown):
@@ -271,6 +312,8 @@ Examples:
271312
pwsh -File .vscode/tasks-misc.ps1 digest -OutputDirectory ./.tmp/docs -StripComments true -StripEmptyLines true
272313
pwsh -File .vscode/tasks-misc.ps1 clean
273314
pwsh -File .vscode/tasks-misc.ps1 repl
315+
pwsh -File .vscode/tasks-misc.ps1 kill-dotnet # interactive selection
316+
pwsh -File .vscode/tasks-misc.ps1 kill-dotnet -ProcessId 1234 -ForceKill # non-interactive
274317
275318
Exit Codes:
276319
0 success, non-zero on failure.
@@ -326,10 +369,55 @@ function Handle-MiscCommand([string]$cmd){
326369
'cleanup' { Clean-Workspace; return }
327370
'repl' { Run-CSharpRepl; return }
328371
'shell' { Run-CSharpRepl; return }
372+
'kill-dotnet' { Kill-DotNetProcess; return }
329373
'help' { Help; return }
330374
'?' { Help; return }
331375
default { Write-Host "Unknown misc command '$cmd'" -ForegroundColor Red; Help; exit 10 }
332376
}
333377
}
334378

379+
function Kill-DotNetProcess() {
380+
Write-Section 'Kill .NET Process'
381+
# Non-interactive direct path if provided
382+
# If ProcessId provided non-interactively, skip selection & confirmation when -ForceKill used.
383+
$selectedPid = $null
384+
if($ProcessId -gt 0){
385+
Write-Host ("Non-interactive target PID specified: {0}" -f $ProcessId) -ForegroundColor Cyan
386+
$selectedPid = $ProcessId
387+
}
388+
if(-not $selectedPid){
389+
# Use shared Select-Pid logic scoped to dotnet processes
390+
$script:DotNetOnly = $true
391+
$selectedPid = Select-Pid 'Select .NET process to KILL'
392+
if(-not $selectedPid){ Write-Host 'Kill operation cancelled or no process selected.' -ForegroundColor Yellow; $global:LASTEXITCODE=0; return }
393+
}
394+
$proc = Get-Process -Id $selectedPid -ErrorAction SilentlyContinue
395+
if(-not $proc){ Write-Host ("Process with PID {0} no longer exists." -f $selectedPid) -ForegroundColor Yellow; $global:LASTEXITCODE=0; return }
396+
Write-Host ("Target: {0} (PID {1})" -f $proc.ProcessName,$selectedPid) -ForegroundColor Cyan
397+
if(-not $ForceKill){
398+
if($ProcessId -gt 0){
399+
$confirmRaw = Read-Host ("Type YES to confirm kill of PID {0} ({1}) or press Enter to cancel" -f $selectedPid,$proc.ProcessName)
400+
if($confirmRaw -ne 'YES'){ Write-Host 'Kill aborted.' -ForegroundColor Yellow; $global:LASTEXITCODE=0; return }
401+
}
402+
elseif(-not $spectreUnavailable){
403+
$confirm = Read-SpectreSelection -Title ("Confirm kill of PID {0} ({1})" -f $selectedPid,$proc.ProcessName) -Choices @('No','Yes') -PageSize 5
404+
if($confirm -ne 'Yes'){ Write-Host 'Kill aborted.' -ForegroundColor Yellow; $global:LASTEXITCODE=0; return }
405+
} else {
406+
$confirmRaw2 = Read-Host ("Type YES to confirm kill of PID {0} ({1})" -f $selectedPid,$proc.ProcessName)
407+
if($confirmRaw2 -ne 'YES'){ Write-Host 'Kill aborted.' -ForegroundColor Yellow; $global:LASTEXITCODE=0; return }
408+
}
409+
} else {
410+
Write-Host 'ForceKill specified; skipping confirmation.' -ForegroundColor DarkYellow
411+
}
412+
try {
413+
Stop-Process -Id $selectedPid -Force -ErrorAction Stop
414+
Write-Host ("Process {0} terminated." -f $selectedPid) -ForegroundColor Green
415+
$global:LASTEXITCODE=0
416+
} catch {
417+
$errMsg = $_.Exception.Message
418+
Write-Host ("Failed to terminate PID {0}: {1}" -f $selectedPid, $errMsg) -ForegroundColor Red
419+
$global:LASTEXITCODE=5
420+
}
421+
}
422+
335423
Handle-MiscCommand $Command

.vscode/tasks.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@
240240
"presentation": {"echo": true,"reveal": "always","focus": true,"panel": "dedicated"},
241241
"problemMatcher": []
242242
},
243+
{
244+
"label": "Misc [kill dotnet process]",
245+
"type": "shell",
246+
"command": "pwsh",
247+
"args": ["-NoProfile","-File","${workspaceFolder}/tasks.ps1","-Task","misc-kill-dotnet"],
248+
"presentation": {"echo": true,"reveal": "always","focus": true,"panel": "shared"},
249+
"problemMatcher": []
250+
},
243251
{
244252
"label": "Security [vulnerable packages]",
245253
"type": "shell",

tasks.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ $tasks = [ordered]@{
126126
'misc-clean' = @{ Label='Misc Clean Workspace'; Script={ Invoke-Misc 'clean' } }
127127
'misc-digest' = @{ Label='Misc Digest Sources'; Script={ Invoke-Misc 'digest' } }
128128
'misc-repl' = @{ Label='Misc C# REPL'; Script={ Invoke-Misc 'repl' } }
129+
'misc-kill-dotnet' = @{ Label='Misc Kill .NET Process'; Script={ Invoke-Misc 'kill-dotnet' } }
129130
'bench' = @{ Label='Diagnostics Benchmarks'; Script={ Invoke-Diagnostics 'bench' } }
130131
'bench-select' = @{ Label='Diagnostics Benchmarks (Project)'; Script={ Invoke-Diagnostics 'bench-select' } }
131132
'trace-flame' = @{ Label='Diagnostics Trace (Flame)'; Script={ Invoke-Diagnostics 'trace-flame' } }
@@ -148,7 +149,7 @@ $categories = [ordered]@{
148149
'Docker & Containers' = @('docker-build-run','docker-build-debug','docker-build-release','docker-run','docker-stop','docker-remove','compose-up','compose-up-pull','compose-down','compose-down-clean')
149150
'Security & Compliance' = @('vulnerabilities','vulnerabilities-deep','outdated','outdated-json','licenses')
150151
'API & Spec' = @('openapi-lint')
151-
'Utilities' = @('misc-clean','misc-digest','misc-repl')
152+
'Utilities' = @('misc-clean','misc-digest','misc-repl','misc-kill-dotnet')
152153
'Performance & Diagnostics' = @('bench','bench-select','trace-flame','trace-cpu','trace-gc','dump-heap','gc-stats','aspnet-metrics','diag-quick','speedscope-view')
153154
}
154155

0 commit comments

Comments
 (0)