portmaster-cli.ps1ā¢18.7 kB
#!/usr/bin/env powershell
<#
.SYNOPSIS
PortMaster CLI - Direct command-line interface for port and process management
.DESCRIPTION
This script provides direct CLI access to PortMaster MCP functions without needing Claude Code.
You can check ports, find processes, get service info, and perform management operations.
.PARAMETER Command
The command to execute (check-ports, find-service, process-info, kill-port, etc.)
.PARAMETER Target
The target for the command (port numbers, service name, PID, etc.)
.PARAMETER Force
Skip confirmation prompts for destructive operations
.EXAMPLE
.\portmaster-cli.ps1 check-ports 3000,3001,3002
.\portmaster-cli.ps1 find-service "*SQL*"
.\portmaster-cli.ps1 process-info 1234
.\portmaster-cli.ps1 kill-port 8080 -Force
#>
param(
[Parameter(Mandatory=$false)]
[ValidateSet("check-ports", "all-ports", "find-service", "service-info", "process-info", "kill-process", "kill-port", "kill-multiple", "stop-service", "start-service", "restart-service", "help")]
[string]$Command = "help",
[Parameter(Mandatory=$false, ValueFromRemainingArguments=$true)]
[string[]]$Target,
[Parameter(Mandatory=$false)]
[switch]$Force
)
# Colors for output
$colors = @{
Info = "Cyan"
Success = "Green"
Warning = "Yellow"
Error = "Red"
Header = "Magenta"
}
function Show-Help {
Write-Host ""
Write-Host "=== PortMaster CLI ===" -ForegroundColor $colors.Header
Write-Host "Direct command-line interface for port and process management" -ForegroundColor $colors.Info
Write-Host ""
Write-Host "DISCOVERY COMMANDS (No admin required):" -ForegroundColor $colors.Header
Write-Host " check-ports <ports> - Check what's listening on specific ports"
Write-Host " Example: check-ports 3000,3001,3002"
Write-Host " all-ports - Show all listening ports on the system"
Write-Host " find-service <pattern> - Find services by name (supports wildcards)"
Write-Host " Example: find-service '*SQL*'"
Write-Host " service-info <name> - Get detailed info about a service"
Write-Host " process-info <PID> - Get detailed info about a process"
Write-Host ""
Write-Host "MANAGEMENT COMMANDS (Require admin/UAC):" -ForegroundColor $colors.Header
Write-Host " kill-process <PID> - Kill a process by PID"
Write-Host " kill-port <ports> - Kill all processes using specific ports"
Write-Host " kill-multiple <PIDs> - Kill multiple processes"
Write-Host " stop-service <name> - Stop a Windows service"
Write-Host " start-service <name> - Start a Windows service"
Write-Host " restart-service <name> - Restart a Windows service"
Write-Host ""
Write-Host "OPTIONS:" -ForegroundColor $colors.Header
Write-Host " -Force - Skip confirmation prompts"
Write-Host ""
Write-Host "EXAMPLES:" -ForegroundColor $colors.Header
Write-Host " .\portmaster-cli.ps1 check-ports 3000-3005"
Write-Host " .\portmaster-cli.ps1 find-service 'Docker*'"
Write-Host " .\portmaster-cli.ps1 kill-port 8080 -Force"
Write-Host " .\portmaster-cli.ps1 all-ports | findstr :80"
Write-Host ""
}
function Check-Ports {
param([string]$PortList)
if (-not $PortList -or $PortList -eq "") {
Write-Host "Error: Port list required" -ForegroundColor $colors.Error
Write-Host "Example: check-ports 3000,3001,3002 or check-ports 3000-3005"
return
}
# Parse port ranges and lists
$ports = @()
$PortList -split ',' | ForEach-Object {
$range = $_.Trim()
if ($range -match '^(\d+)-(\d+)$') {
$start = [int]$matches[1]
$end = [int]$matches[2]
$ports += $start..$end
} else {
$ports += [int]$range
}
}
Write-Host "Checking ports: $($ports -join ', ')" -ForegroundColor $colors.Info
Write-Host ""
$found = $false
foreach ($port in $ports) {
$connections = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
if ($connections) {
$found = $true
foreach ($conn in $connections) {
$process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
Write-Host "Port $port - " -NoNewline -ForegroundColor $colors.Warning
Write-Host "$($process.ProcessName) " -NoNewline -ForegroundColor $colors.Success
Write-Host "(PID: $($conn.OwningProcess)) " -NoNewline
Write-Host "[$($conn.LocalAddress)]" -ForegroundColor $colors.Info
}
}
}
if (-not $found) {
Write-Host "No processes found listening on the specified ports" -ForegroundColor $colors.Warning
}
}
function Get-AllPorts {
Write-Host "All listening ports:" -ForegroundColor $colors.Info
Write-Host ""
$connections = Get-NetTCPConnection -State Listen | Sort-Object LocalPort
foreach ($conn in $connections) {
$process = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
Write-Host "Port $($conn.LocalPort) - " -NoNewline -ForegroundColor $colors.Warning
Write-Host "$($process.ProcessName) " -NoNewline -ForegroundColor $colors.Success
Write-Host "(PID: $($conn.OwningProcess)) " -NoNewline
Write-Host "[$($conn.LocalAddress)]" -ForegroundColor $colors.Info
}
}
function Find-ServiceByPattern {
param([string]$Pattern)
if (-not $Pattern) {
Write-Host "Error: Service pattern required" -ForegroundColor $colors.Error
Write-Host "Example: find-service '*SQL*' or find-service 'Docker'"
return
}
Write-Host "Finding services matching: $Pattern" -ForegroundColor $colors.Info
Write-Host ""
$services = Get-Service | Where-Object {
$_.Name -like $Pattern -or $_.DisplayName -like $Pattern
}
if ($services) {
foreach ($service in $services) {
Write-Host "$($service.Name) " -NoNewline -ForegroundColor $colors.Success
Write-Host "($($service.DisplayName)) " -NoNewline
Write-Host "- $($service.Status)" -ForegroundColor $(
if ($service.Status -eq 'Running') { $colors.Success } else { $colors.Warning }
)
# Try to find associated processes
$processes = Get-Process | Where-Object { $_.ProcessName -like "*$($service.Name)*" }
if ($processes) {
foreach ($proc in $processes) {
Write-Host " āā Process: $($proc.ProcessName) (PID: $($proc.Id))" -ForegroundColor $colors.Info
}
}
}
} else {
Write-Host "No services found matching pattern: $Pattern" -ForegroundColor $colors.Warning
}
}
function Get-ProcessChainInfo {
param([string]$ProcessId)
try {
$targetProcess = Get-Process -Id $ProcessId -ErrorAction Stop
} catch {
Write-Host "Process with PID '$ProcessId' not found" -ForegroundColor $colors.Error
return
}
# Get WMI process info for parent details
$wmiProcess = Get-WmiObject -Class Win32_Process -Filter "ProcessId = $ProcessId" -ErrorAction SilentlyContinue
Write-Host ""
Write-Host "=== Process Information ===" -ForegroundColor $colors.Header
Write-Host "Process Name: $($targetProcess.ProcessName)" -ForegroundColor $colors.Success
Write-Host "PID: $($targetProcess.Id)"
Write-Host "Memory Usage: $([math]::Round($targetProcess.WorkingSet64 / 1MB, 1)) MB"
# Format CPU Time properly
$cpuTime = if ($targetProcess.TotalProcessorTime) {
$targetProcess.TotalProcessorTime.ToString("hh\:mm\:ss\.fff")
} else {
"Not available"
}
Write-Host "CPU Time: $cpuTime"
# Format Start Time properly
$startTime = if ($targetProcess.StartTime) {
$targetProcess.StartTime.ToString("yyyy-MM-dd HH:mm:ss")
} else {
"Not available"
}
Write-Host "Start Time: $startTime"
Write-Host "Priority: $($targetProcess.BasePriority)"
Write-Host "Thread Count: $($targetProcess.Threads.Count)"
# Get command line and path info
if ($wmiProcess) {
$cmdLine = if ($wmiProcess.CommandLine) { $wmiProcess.CommandLine } else { "Not available" }
$exePath = if ($wmiProcess.ExecutablePath) { $wmiProcess.ExecutablePath } else { "Not available" }
Write-Host "Command Line: $cmdLine" -ForegroundColor $colors.Info
Write-Host "Executable Path: $exePath"
} else {
Write-Host "Command Line: Not available (WMI access denied)" -ForegroundColor $colors.Warning
Write-Host "Executable Path: Not available (WMI access denied)" -ForegroundColor $colors.Warning
}
# Get parent process info
Write-Host ""
Write-Host "=== Process Chain ===" -ForegroundColor $colors.Header
if ($wmiProcess -and $wmiProcess.ParentProcessId) {
$parentProcess = Get-Process -Id $wmiProcess.ParentProcessId -ErrorAction SilentlyContinue
if ($parentProcess) {
Write-Host "Parent Process: $($parentProcess.ProcessName) (PID: $($parentProcess.Id))" -ForegroundColor $colors.Warning
# Get grandparent if available
$parentWmi = Get-WmiObject -Class Win32_Process -Filter "ProcessId = $($parentProcess.Id)" -ErrorAction SilentlyContinue
if ($parentWmi -and $parentWmi.ParentProcessId) {
$grandparentProcess = Get-Process -Id $parentWmi.ParentProcessId -ErrorAction SilentlyContinue
if ($grandparentProcess) {
Write-Host " āā Grandparent: $($grandparentProcess.ProcessName) (PID: $($grandparentProcess.Id))" -ForegroundColor $colors.Info
}
}
} else {
Write-Host "Parent Process: (PID: $($wmiProcess.ParentProcessId)) - Process no longer exists" -ForegroundColor $colors.Warning
}
} else {
Write-Host "Parent Process: Not available (may be system process)" -ForegroundColor $colors.Info
}
# Get child processes
$childProcesses = Get-WmiObject -Class Win32_Process -Filter "ParentProcessId = $ProcessId" -ErrorAction SilentlyContinue
if ($childProcesses) {
Write-Host ""
Write-Host "Child Processes:" -ForegroundColor $colors.Warning
foreach ($child in $childProcesses) {
$childProcess = Get-Process -Id $child.ProcessId -ErrorAction SilentlyContinue
if ($childProcess) {
Write-Host " āā $($childProcess.ProcessName) (PID: $($child.ProcessId)) - Memory: $([math]::Round($childProcess.WorkingSet64 / 1MB, 1)) MB" -ForegroundColor $colors.Success
# Show grandchildren (one level deeper)
$grandchildren = Get-WmiObject -Class Win32_Process -Filter "ParentProcessId = $($child.ProcessId)" -ErrorAction SilentlyContinue
foreach ($grandchild in $grandchildren) {
$grandchildProcess = Get-Process -Id $grandchild.ProcessId -ErrorAction SilentlyContinue
if ($grandchildProcess) {
Write-Host " āā $($grandchildProcess.ProcessName) (PID: $($grandchild.ProcessId))" -ForegroundColor $colors.Info
}
}
}
}
} else {
Write-Host "Child Processes: None" -ForegroundColor $colors.Info
}
# Check if process is listening on any ports
Write-Host ""
Write-Host "=== Network Information ===" -ForegroundColor $colors.Header
$tcpConnections = Get-NetTCPConnection | Where-Object { $_.OwningProcess -eq $ProcessId }
if ($tcpConnections) {
Write-Host "Listening Ports:" -ForegroundColor $colors.Warning
$listeningPorts = $tcpConnections | Where-Object { $_.State -eq "Listen" } | Sort-Object LocalPort -Unique
foreach ($conn in $listeningPorts) {
Write-Host " āā Port $($conn.LocalPort) [$($conn.LocalAddress)]" -ForegroundColor $colors.Success
}
$activePorts = $tcpConnections | Where-Object { $_.State -ne "Listen" } | Sort-Object LocalPort -Unique
if ($activePorts) {
Write-Host "Active Connections:" -ForegroundColor $colors.Warning
foreach ($conn in $activePorts) {
Write-Host " āā $($conn.LocalAddress):$($conn.LocalPort) -> $($conn.RemoteAddress):$($conn.RemotePort) [$($conn.State)]" -ForegroundColor $colors.Info
}
}
} else {
Write-Host "Network Connections: None" -ForegroundColor $colors.Info
}
# Show loaded modules (DLLs) - top 10 by size
Write-Host ""
Write-Host "=== Loaded Modules (Top 10 by Size) ===" -ForegroundColor $colors.Header
try {
$modules = $targetProcess.Modules | Sort-Object ModuleMemorySize -Descending | Select-Object -First 10
if ($modules -and $modules.Count -gt 0) {
foreach ($module in $modules) {
$sizeKB = [math]::Round($module.ModuleMemorySize / 1KB, 1)
Write-Host " āā $($module.ModuleName) - $sizeKB KB" -ForegroundColor $colors.Info
}
} else {
Write-Host " No modules accessible (may be system process or access denied)" -ForegroundColor $colors.Warning
}
} catch {
Write-Host " Modules: Access denied (likely system process or insufficient privileges)" -ForegroundColor $colors.Warning
}
# Add process uptime calculation
Write-Host ""
Write-Host "=== Additional Information ===" -ForegroundColor $colors.Header
if ($targetProcess.StartTime) {
$uptime = (Get-Date) - $targetProcess.StartTime
$uptimeString = "{0:dd} days, {0:hh} hours, {0:mm} minutes, {0:ss} seconds" -f $uptime
Write-Host "Uptime: $uptimeString" -ForegroundColor $colors.Info
}
# Show process working set details
Write-Host "Working Set: $([math]::Round($targetProcess.WorkingSet64 / 1MB, 1)) MB"
Write-Host "Peak Working Set: $([math]::Round($targetProcess.PeakWorkingSet64 / 1MB, 1)) MB"
Write-Host "Virtual Memory: $([math]::Round($targetProcess.VirtualMemorySize64 / 1MB, 1)) MB"
Write-Host "Peak Virtual Memory: $([math]::Round($targetProcess.PeakVirtualMemorySize64 / 1MB, 1)) MB"
Write-Host ""
}
function Call-AdminHelper {
param([string]$Operation, [string]$TargetValue, [bool]$ForceOperation)
$scriptPath = Join-Path $PSScriptRoot "admin_helper.ps1"
if (-not (Test-Path $scriptPath)) {
Write-Host "Error: admin_helper.ps1 not found at $scriptPath" -ForegroundColor $colors.Error
return $false
}
try {
$forceArg = if ($ForceOperation) { " -Force" } else { "" }
$cmd = "Start-Process powershell -ArgumentList '-ExecutionPolicy Bypass -File `"$scriptPath`" -Operation $Operation -Target `"$TargetValue`"$forceArg' -Verb RunAs -Wait"
Write-Host "Requesting admin privileges for operation..." -ForegroundColor $colors.Warning
Invoke-Expression $cmd
return $true
} catch {
Write-Host "Error executing admin operation: $_" -ForegroundColor $colors.Error
return $false
}
}
# Main execution
$TargetString = if ($Target) { $Target -join "," } else { "" }
switch ($Command) {
"check-ports" {
Check-Ports -PortList $TargetString
}
"all-ports" {
Get-AllPorts
}
"find-service" {
Find-ServiceByPattern -Pattern $TargetString
}
"service-info" {
if (-not $TargetString) {
Write-Host "Error: Service name required" -ForegroundColor $colors.Error
return
}
$service = Get-Service -Name $TargetString -ErrorAction SilentlyContinue
if ($service) {
Write-Host "Service: $($service.Name)" -ForegroundColor $colors.Header
Write-Host "Display Name: $($service.DisplayName)"
Write-Host "Status: $($service.Status)" -ForegroundColor $(
if ($service.Status -eq 'Running') { $colors.Success } else { $colors.Warning }
)
Write-Host "Start Type: $($service.StartType)"
} else {
Write-Host "Service '$TargetString' not found" -ForegroundColor $colors.Error
}
}
"process-info" {
if (-not $TargetString) {
Write-Host "Error: Process ID required" -ForegroundColor $colors.Error
return
}
Get-ProcessChainInfo -ProcessId $TargetString
}
"kill-process" {
if (-not $TargetString) {
Write-Host "Error: Process ID required" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "kill-process" -TargetValue $TargetString -ForceOperation $Force
}
"kill-port" {
if (-not $TargetString) {
Write-Host "Error: Port number(s) required" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "kill-by-port" -TargetValue $TargetString -ForceOperation $Force
}
"kill-multiple" {
if (-not $TargetString) {
Write-Host "Error: Process IDs required (comma-separated)" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "kill-multiple" -TargetValue $TargetString -ForceOperation $Force
}
"stop-service" {
if (-not $TargetString) {
Write-Host "Error: Service name required" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "stop-service" -TargetValue $TargetString -ForceOperation $Force
}
"start-service" {
if (-not $TargetString) {
Write-Host "Error: Service name required" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "start-service" -TargetValue $TargetString -ForceOperation $Force
}
"restart-service" {
if (-not $TargetString) {
Write-Host "Error: Service name required" -ForegroundColor $colors.Error
return
}
Call-AdminHelper -Operation "restart-service" -TargetValue $TargetString -ForceOperation $Force
}
"help" {
Show-Help
}
default {
Write-Host "Unknown command: $Command" -ForegroundColor $colors.Error
Show-Help
}
}