1212param (
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'
4245function Write-Section ([string ] $Text ){ Write-Host " `n === $Text ===" - ForegroundColor Magenta }
4346function 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+
4585function 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
251292combine-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
275318Exit 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+
335423Handle- MiscCommand $Command
0 commit comments