UNPKG

portfree

Version:

A cross-platform CLI tool for managing processes running on specific ports

276 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WindowsAdapter = void 0; const base_adapter_1 = require("./base-adapter"); const child_process_1 = require("child_process"); const util_1 = require("util"); const process_1 = require("../models/process"); const errors_1 = require("../errors"); const execAsync = (0, util_1.promisify)(child_process_1.exec); /** * Windows platform adapter * Implements platform-specific commands for Windows systems */ class WindowsAdapter extends base_adapter_1.BaseAdapter { /** * Finds processes running on the specified port using netstat * @param port - Port number to check * @returns Array of process objects */ async findProcessByPort(port) { try { // Use netstat to find processes listening on the port const command = `netstat -ano | findstr :${port}`; const { stdout } = await execAsync(command); if (!stdout.trim()) { return []; } const processes = []; const lines = stdout.trim().split('\n'); const pidSet = new Set(); // Track unique PIDs to avoid duplicates for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine) continue; // Parse netstat output: Proto Local Address Foreign Address [State] PID // Note: UDP doesn't have a State column const parts = trimmedLine.split(/\s+/); if (parts.length < 4) continue; const protocol = parts[0].toUpperCase(); // TCP or UDP const localAddress = parts[1]; // Check if this line is for our port (more precise matching) const portPattern = new RegExp(`:${port}(\\s|$)`); const portMatch = portPattern.test(localAddress); if (!portMatch) continue; let pid; let state = null; if (protocol === 'TCP') { // TCP format: Proto Local Foreign State PID if (parts.length < 5) continue; state = parts[3]; pid = parseInt(parts[4]); // For TCP, we're interested in LISTENING state primarily, but also ESTABLISHED if (state !== 'LISTENING' && state !== 'ESTABLISHED') continue; } else if (protocol === 'UDP') { // UDP format: Proto Local Foreign PID (no State column) if (parts.length < 4) continue; pid = parseInt(parts[3]); } else { continue; // Skip unknown protocols } // Skip if not a valid PID or if we've already processed this PID if (isNaN(pid) || pid <= 0 || pidSet.has(pid)) continue; pidSet.add(pid); try { // Get additional process details const processDetails = await this.getProcessDetails(pid); const process = new process_1.Process({ pid, name: processDetails.name || 'Unknown', user: processDetails.user || 'Unknown', protocol, port, command: processDetails.command || processDetails.name || 'Unknown' }); processes.push(process); } catch { // If we can't get process details, create a basic process object try { const process = new process_1.Process({ pid, name: 'Unknown', user: 'Unknown', protocol, port, command: 'Unknown' }); processes.push(process); } catch { // Skip invalid process data but continue processing others continue; } } } return processes; } catch (error) { const err = error; if (err.code === 1 && !err.stdout) { // netstat/findstr returns exit code 1 when no matches found, which is normal return []; } // Handle permission denied errors if (err.stderr && err.stderr.includes('Access is denied')) { throw new errors_1.PermissionError('Permission denied when checking port. Some processes may not be visible without elevated privileges.'); } // Check for network-related errors if (err.message.includes('network') || err.message.includes('connection') || err.message.includes('timeout') || err.stderr && err.stderr.includes('network')) { throw new errors_1.NetworkError(`Network error while checking port ${port}: ${err.message}`, port, 'netstat lookup'); } // Check for missing commands if (err.stderr && (err.stderr.includes('not recognized') || err.stderr.includes('not found'))) { throw new errors_1.SystemError('Required system command not found. Ensure netstat and findstr are available.', `netstat -ano | findstr :${port}`, err.code); } // Handle invalid port errors if (err.stderr && err.stderr.includes('invalid')) { throw new errors_1.ValidationError(`Invalid port number ${port} for netstat command`); } throw new errors_1.SystemError(`Failed to find processes on port ${port}: ${err.message}`, `netstat -ano | findstr :${port}`, err.code); } } /** * Gets detailed information about a process by PID using tasklist * @param pid - Process ID * @returns Process details object */ async getProcessDetails(pid) { try { // Use tasklist to get detailed process information const command = `tasklist /FI "PID eq ${pid}" /FO CSV /NH`; const { stdout } = await execAsync(command); if (!stdout.trim()) { throw new errors_1.SystemError(`Process with PID ${pid} not found`, command, 1); } // Parse CSV output: "Image Name","PID","Session Name","Session#","Mem Usage" const line = stdout.trim(); // Remove quotes and split by comma const csvMatch = line.match(/"([^"]+)","([^"]+)","([^"]+)","([^"]+)","([^"]+)"/); if (!csvMatch || csvMatch.length < 6) { throw new errors_1.SystemError(`Invalid tasklist output format for PID ${pid}`, command); } const imageName = csvMatch[1]; const processPid = parseInt(csvMatch[2]); const sessionName = csvMatch[3]; // For Windows, we don't have easy access to the user without additional commands // We'll use the session name as a proxy, or 'System' for system processes const user = sessionName === 'Services' ? 'SYSTEM' : sessionName; return { pid: processPid, user, name: imageName, command: imageName }; } catch (error) { if (error instanceof errors_1.SystemError) { throw error; } const err = error; if (err.code === 1) { throw new errors_1.SystemError(`Process with PID ${pid} not found`, `tasklist /FI "PID eq ${pid}"`, err.code); } throw new errors_1.SystemError(`Failed to get process details for PID ${pid}: ${err.message}`, `tasklist /FI "PID eq ${pid}"`, err.code); } } /** * Terminates a process by PID using taskkill command * @param pid - Process ID to terminate * @returns True if process was successfully terminated */ async killProcess(pid) { try { // First try graceful termination await this._killWithForce(pid, false); // Wait a moment to see if process terminates gracefully await this._sleep(1000); // Check if process is still running const isRunning = await this._isProcessRunning(pid); if (!isRunning) { return true; } // If still running, force kill await this._killWithForce(pid, true); // Wait a moment and check again await this._sleep(500); const isStillRunning = await this._isProcessRunning(pid); return !isStillRunning; } catch (error) { if (error instanceof errors_1.PermissionError) { throw error; } const err = error; throw new errors_1.SystemError(`Failed to kill process ${pid}: ${err.message}`, `taskkill /PID ${pid}`, err.code); } } /** * Kills a process with or without force * @param pid - Process ID * @param force - Whether to use force (/F flag) * @private */ async _killWithForce(pid, force) { try { const command = force ? `taskkill /PID ${pid} /F` : `taskkill /PID ${pid}`; await execAsync(command); } catch (error) { const err = error; if (err.code === 128 && err.stderr && err.stderr.includes('Access is denied')) { throw new errors_1.PermissionError(`Permission denied when trying to kill process ${pid}. The process may be a system process or owned by another user.`, pid); } if (err.code === 128 && err.stderr && err.stderr.includes('not found')) { // Process already terminated, which is fine return; } // Check for missing taskkill command if (err.stderr && (err.stderr.includes('not recognized') || err.stderr.includes('not found'))) { const command = force ? `taskkill /PID ${pid} /F` : `taskkill /PID ${pid}`; throw new errors_1.SystemError('taskkill command not found. Ensure Windows system tools are available.', command, err.code); } // Provide more specific error messages if (err.stderr && err.stderr.includes('Invalid argument')) { const command = force ? `taskkill /PID ${pid} /F` : `taskkill /PID ${pid}`; throw new errors_1.SystemError(`Invalid process ID ${pid} or taskkill parameters`, command, err.code); } throw error; } } /** * Checks if a process is still running * @param pid - Process ID * @returns True if process is running * @private */ async _isProcessRunning(pid) { try { const command = `tasklist /FI "PID eq ${pid}" /FO CSV /NH`; const { stdout } = await execAsync(command); return stdout.trim().length > 0 && !stdout.includes('INFO: No tasks are running'); } catch { // If tasklist fails, assume process is not running return false; } } /** * Sleep for specified milliseconds * @param ms - Milliseconds to sleep * @returns Promise that resolves after the specified time * @private */ _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Validates if the adapter can run on Windows systems * @returns True if adapter is compatible */ async isCompatible() { const platform = process.platform; return platform === 'win32'; } } exports.WindowsAdapter = WindowsAdapter; //# sourceMappingURL=windows-adapter.js.map