Skip to main content
Glama
ooples

MCP Console Automation Server

WMIProtocol.ts27.9 kB
import { spawn, ChildProcess } from 'child_process'; import * as os from 'os'; import { BaseProtocol } from '../core/BaseProtocol.js'; import { ConsoleSession, SessionOptions, ConsoleType, ConsoleOutput, } from '../types/index.js'; import { SessionState, ProtocolCapabilities, ProtocolHealthStatus, } from '../core/IProtocol.js'; import { Logger } from '../utils/logger.js'; import { v4 as uuidv4 } from 'uuid'; /** * WMI connection options */ export interface WMIConnectionOptions { host?: string; namespace?: string; username?: string; password?: string; domain?: string; authLevel?: | 'default' | 'none' | 'connect' | 'call' | 'pkt' | 'pktIntegrity' | 'pktPrivacy'; impersonationLevel?: 'anonymous' | 'identify' | 'impersonate' | 'delegate'; timeout?: number; locale?: string; authority?: string; enablePrivileges?: boolean; useKerberos?: boolean; useNTLMv2?: boolean; } /** * WMI query options */ export interface WMIQueryOptions { query: string; namespace?: string; timeout?: number; format?: 'list' | 'table' | 'mof' | 'xml' | 'csv' | 'json'; } /** * WMI method invocation options */ export interface WMIMethodOptions { className: string; methodName: string; parameters?: Record<string, any>; instance?: string; namespace?: string; } /** * WMI session state */ interface WMISession { id: string; connectionOptions: WMIConnectionOptions; process?: ChildProcess; isConnected: boolean; lastActivity: Date; queryHistory: Array<{ query: string; timestamp: Date; result?: any; error?: string; }>; monitoringIntervals: Map<string, NodeJS.Timeout>; } /** * WMI Protocol implementation extending BaseProtocol * Provides Windows Management Instrumentation capabilities */ export class WMIProtocol extends BaseProtocol { public readonly type: ConsoleType = 'wmi'; public readonly capabilities: ProtocolCapabilities; private wmiSessions: Map<string, WMISession> = new Map(); private wmiProcesses: Map<string, ChildProcess> = new Map(); private healthStatus: ProtocolHealthStatus; private isWindows: boolean; constructor() { super('WMIProtocol'); this.isWindows = os.platform() === 'win32'; this.capabilities = { supportsStreaming: true, supportsFileTransfer: false, supportsX11Forwarding: false, supportsPortForwarding: false, supportsAuthentication: true, supportsEncryption: true, supportsCompression: false, supportsMultiplexing: true, supportsKeepAlive: true, supportsReconnection: true, supportsBinaryData: false, supportsCustomEnvironment: false, supportsWorkingDirectory: false, supportsSignals: false, supportsResizing: false, supportsPTY: false, maxConcurrentSessions: 10, defaultTimeout: 30000, supportedEncodings: ['utf-8', 'utf-16'], supportedAuthMethods: ['ntlm', 'kerberos', 'basic'], platformSupport: { windows: true, linux: true, // via wmic or PowerShell Core macos: false, freebsd: false, }, }; this.healthStatus = { isHealthy: true, lastChecked: new Date(), errors: [], warnings: [], metrics: { activeSessions: 0, totalSessions: 0, averageLatency: 0, successRate: 1.0, uptime: 0, }, dependencies: { wmic: { available: false, version: 'checking...', }, powershell: { available: false, version: 'checking...', }, }, }; this.isInitialized = true; } /** * Initialize WMI Protocol */ async initialize(): Promise<void> { if (this.isInitialized) return; this.logger.info('Initializing WMI Protocol'); // Check for WMIC availability if (this.isWindows) { try { const wmicCheck = spawn('wmic', ['/?'], { shell: true }); await new Promise((resolve, reject) => { wmicCheck.on('exit', (code) => { if (code === 0) { this.healthStatus.dependencies.wmic = { available: true, version: 'system', }; resolve(undefined); } else { reject(new Error('WMIC not found')); } }); wmicCheck.on('error', reject); }); } catch { this.logger.warn('WMIC not available'); this.healthStatus.dependencies.wmic = { available: false, version: 'not installed', }; } } // Check for PowerShell with WMI cmdlets try { const psCheck = spawn( 'powershell', ['-Command', 'Get-WmiObject -List | Select-Object -First 1'], { shell: true } ); await new Promise((resolve, reject) => { psCheck.on('exit', (code) => { if (code === 0) { this.healthStatus.dependencies.powershell = { available: true, version: 'system', }; resolve(undefined); } else { reject(new Error('PowerShell WMI not available')); } }); psCheck.on('error', reject); }); } catch { this.healthStatus.dependencies.powershell = { available: false, version: 'not installed', }; } this.isInitialized = true; } /** * Create session with BaseProtocol integration */ async createSession(options: SessionOptions): Promise<ConsoleSession> { const sessionId = `wmi-${Date.now()}-${uuidv4().substring(0, 8)}`; const sessionState = await this.createSessionWithTypeDetection( sessionId, options ); return sessionState; } /** * Implementation of BaseProtocol's doCreateSession */ protected async doCreateSession( sessionId: string, options: SessionOptions, sessionState: SessionState ): Promise<ConsoleSession> { const wmiOptions: WMIConnectionOptions = { host: options.wmiHost || 'localhost', namespace: options.wmiNamespace || 'root\\CIMV2', username: options.wmiUsername, password: options.wmiPassword, domain: options.wmiDomain, authLevel: (options.wmiAuthLevel as any) || 'pktPrivacy', impersonationLevel: (options.wmiImpersonationLevel as any) || 'impersonate', timeout: options.wmiTimeout || 30000, locale: options.wmiLocale || 'MS_409', enablePrivileges: options.wmiEnablePrivileges !== false, useKerberos: options.wmiUseKerberos || false, useNTLMv2: options.wmiUseNTLMv2 !== false, }; const wmiSession: WMISession = { id: sessionId, connectionOptions: wmiOptions, isConnected: false, lastActivity: new Date(), queryHistory: [], monitoringIntervals: new Map(), }; this.wmiSessions.set(sessionId, wmiSession); try { // Test WMI connection await this.testConnection(wmiSession); wmiSession.isConnected = true; const session: ConsoleSession = { id: sessionId, type: 'wmi', status: 'running', createdAt: new Date(), lastActivity: new Date(), executionState: 'idle', command: options.command || 'wmi', args: options.args || [], cwd: process.cwd(), env: options.environment || {}, environment: options.environment || {}, activeCommands: new Map(), streaming: options.streaming ?? true, metadata: { wmiOptions, namespace: wmiOptions.namespace, host: wmiOptions.host, }, }; this.sessions.set(sessionId, session); this.emit('sessionCreated', session); // Add initial output this.addToOutputBuffer(sessionId, { sessionId, type: 'stdout', data: `WMI session established to ${wmiOptions.host}\\${wmiOptions.namespace}\n`, timestamp: new Date(), raw: '', }); return session; } catch (error) { this.wmiSessions.delete(sessionId); throw error; } } /** * Test WMI connection */ private async testConnection(session: WMISession): Promise<void> { try { // Simple query to test connection await this.executeWMIQuery(session, { query: 'SELECT Name FROM Win32_ComputerSystem', namespace: session.connectionOptions.namespace, }); } catch (error) { throw new Error(`Failed to connect to WMI: ${error}`); } } /** * Execute WMI query */ async executeWMIQuery( session: WMISession, options: WMIQueryOptions ): Promise<any> { const { query, namespace, format = 'json', timeout } = options; // Record query in history const historyEntry = { query, timestamp: new Date(), result: undefined as any, error: undefined as string | undefined, }; session.queryHistory.push(historyEntry); try { let result: string; if (this.healthStatus.dependencies.wmic?.available && this.isWindows) { // Use WMIC result = await this.executeWMIC(session, query, namespace, format); } else if (this.healthStatus.dependencies.powershell?.available) { // Use PowerShell result = await this.executePowerShellWMI( session, query, namespace, format ); } else { throw new Error('No WMI provider available'); } const parsed = this.parseWMIResult(result, format); historyEntry.result = parsed; session.lastActivity = new Date(); return parsed; } catch (error) { historyEntry.error = error instanceof Error ? error.message : String(error); throw error; } } /** * Execute WMIC command */ private async executeWMIC( session: WMISession, query: string, namespace?: string, format?: string ): Promise<string> { return new Promise((resolve, reject) => { const args: string[] = []; // Add connection parameters if ( session.connectionOptions.host && session.connectionOptions.host !== 'localhost' ) { args.push('/node:' + session.connectionOptions.host); } if (session.connectionOptions.username) { args.push('/user:' + session.connectionOptions.username); } if (session.connectionOptions.password) { args.push('/password:' + session.connectionOptions.password); } if (namespace && namespace !== 'root\\CIMV2') { args.push('/namespace:' + namespace); } // Add query if (query.toUpperCase().startsWith('SELECT')) { // WQL query args.push( 'path', 'Win32_ComputerSystem', 'where', this.extractWhereClause(query), 'get', this.extractSelectFields(query) ); } else { // Direct class/method call args.push(...query.split(' ')); } // Add format if (format === 'csv') { args.push('/format:csv'); } else if (format === 'xml') { args.push('/format:rawxml'); } else { args.push('/format:list'); } const wmicProcess = spawn('wmic', args, { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env }, }); let stdout = ''; let stderr = ''; wmicProcess.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); }); wmicProcess.stderr?.on('data', (data: Buffer) => { stderr += data.toString(); }); wmicProcess.on('close', (code) => { if (code === 0) { resolve(stdout); } else { reject(new Error(`WMIC failed with code ${code}: ${stderr}`)); } }); wmicProcess.on('error', reject); }); } /** * Execute PowerShell WMI command */ private async executePowerShellWMI( session: WMISession, query: string, namespace?: string, format?: string ): Promise<string> { return new Promise((resolve, reject) => { let psCommand: string; if (query.toUpperCase().startsWith('SELECT')) { // WQL query via Get-WmiObject psCommand = `Get-WmiObject -Query "${query}"`; if (namespace && namespace !== 'root\\CIMV2') { psCommand += ` -Namespace "${namespace}"`; } if ( session.connectionOptions.host && session.connectionOptions.host !== 'localhost' ) { psCommand += ` -ComputerName "${session.connectionOptions.host}"`; } if (session.connectionOptions.username) { // Create credential object psCommand = `$cred = New-Object System.Management.Automation.PSCredential("${session.connectionOptions.username}", (ConvertTo-SecureString "${session.connectionOptions.password}" -AsPlainText -Force)); ` + psCommand + ' -Credential $cred'; } // Format output if (format === 'json') { psCommand += ' | ConvertTo-Json -Depth 10'; } else if (format === 'csv') { psCommand += ' | ConvertTo-Csv -NoTypeInformation'; } else if (format === 'xml') { psCommand += ' | ConvertTo-Xml -As String'; } else { psCommand += ' | Format-List *'; } } else { // Direct command psCommand = query; } const psProcess = spawn('powershell', ['-Command', psCommand], { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env }, }); let stdout = ''; let stderr = ''; psProcess.stdout?.on('data', (data: Buffer) => { stdout += data.toString(); }); psProcess.stderr?.on('data', (data: Buffer) => { stderr += data.toString(); }); psProcess.on('close', (code) => { if (code === 0) { resolve(stdout); } else { reject( new Error(`PowerShell WMI failed with code ${code}: ${stderr}`) ); } }); psProcess.on('error', reject); }); } /** * Parse WMI result based on format */ private parseWMIResult(result: string, format?: string): any { switch (format) { case 'json': try { return JSON.parse(result); } catch { return { raw: result }; } case 'csv': return this.parseCSV(result); case 'xml': return { xml: result }; default: return this.parseListFormat(result); } } /** * Parse CSV format */ private parseCSV(csv: string): any[] { const lines = csv.split('\n').filter((line) => line.trim()); if (lines.length < 2) return []; const headers = lines[0].split(',').map((h) => h.trim().replace(/"/g, '')); const results: any[] = []; for (let i = 1; i < lines.length; i++) { const values = lines[i].split(',').map((v) => v.trim().replace(/"/g, '')); const obj: any = {}; headers.forEach((header, index) => { obj[header] = values[index]; }); results.push(obj); } return results; } /** * Parse list format */ private parseListFormat(text: string): any { const result: any = {}; const lines = text.split('\n'); for (const line of lines) { const match = line.match(/^([^=]+)=(.*)$/); if (match) { const key = match[1].trim(); const value = match[2].trim(); result[key] = value; } } return result; } /** * Extract WHERE clause from WQL */ private extractWhereClause(query: string): string { const whereMatch = query.match(/WHERE\s+(.+)$/i); return whereMatch ? whereMatch[1] : '*'; } /** * Extract SELECT fields from WQL */ private extractSelectFields(query: string): string { const selectMatch = query.match(/SELECT\s+(.+?)\s+FROM/i); if (selectMatch) { const fields = selectMatch[1]; if (fields === '*') { return '/all'; } return fields.replace(/,/g, ','); } return '/all'; } /** * Execute command in session */ async executeCommand( sessionId: string, command: string, args?: string[] ): Promise<void> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } // Parse command as WMI query or method call let result: any; if ( command.toUpperCase().startsWith('SELECT') || command.toUpperCase().startsWith('ASSOCIATORS') || command.toUpperCase().startsWith('REFERENCES') ) { // WQL query result = await this.executeWMIQuery(session, { query: command, format: 'json', }); } else if (command.includes('.')) { // Method invocation (e.g., Win32_Process.Create) const [className, methodName] = command.split('.'); result = await this.invokeWMIMethod(session, { className, methodName, parameters: args ? this.parseMethodArgs(args) : undefined, }); } else { // Class enumeration result = await this.executeWMIQuery(session, { query: `SELECT * FROM ${command}`, format: 'json', }); } // Add output this.addToOutputBuffer(sessionId, { sessionId, type: 'stdout', data: typeof result === 'string' ? result : JSON.stringify(result, null, 2), timestamp: new Date(), raw: result, }); } /** * Parse method arguments */ private parseMethodArgs(args: string[]): Record<string, any> { const params: Record<string, any> = {}; for (const arg of args) { const match = arg.match(/^([^=]+)=(.*)$/); if (match) { const key = match[1]; const value = match[2]; // Try to parse as JSON try { params[key] = JSON.parse(value); } catch { params[key] = value; } } } return params; } /** * Invoke WMI method */ async invokeWMIMethod( session: WMISession, options: WMIMethodOptions ): Promise<any> { const { className, methodName, parameters, instance, namespace } = options; let command: string; if (this.healthStatus.dependencies.powershell?.available) { // Use PowerShell command = `Invoke-WmiMethod -Class ${className} -Name ${methodName}`; if (namespace) { command += ` -Namespace "${namespace}"`; } if (parameters && Object.keys(parameters).length > 0) { const paramStr = Object.entries(parameters) .map(([key, value]) => `${key}="${value}"`) .join(','); command += ` -ArgumentList @{${paramStr}}`; } if (instance) { command += ` -InputObject (Get-WmiObject -Query "SELECT * FROM ${className} WHERE ${instance}")`; } command += ' | ConvertTo-Json'; return this.executePowerShellWMI(session, command, namespace, 'json'); } else if ( this.healthStatus.dependencies.wmic?.available && this.isWindows ) { // Use WMIC const args = [className]; if (instance) { args.push('where', instance); } args.push('call', methodName); if (parameters) { Object.entries(parameters).forEach(([key, value]) => { args.push(`${key}="${value}"`); }); } return this.executeWMIC(session, args.join(' '), namespace, 'list'); } else { throw new Error('No WMI provider available for method invocation'); } } /** * Send input to session */ async sendInput(sessionId: string, input: string): Promise<void> { // WMI doesn't have traditional input, treat as command await this.executeCommand(sessionId, input); } /** * Close session */ async closeSession(sessionId: string): Promise<void> { const session = this.wmiSessions.get(sessionId); if (!session) return; this.logger.info(`Closing WMI session ${sessionId}`); // Stop all monitoring intervals session.monitoringIntervals.forEach((interval) => clearInterval(interval)); session.monitoringIntervals.clear(); // Kill any running processes const process = this.wmiProcesses.get(sessionId); if (process) { process.kill('SIGTERM'); this.wmiProcesses.delete(sessionId); } // Clean up session this.wmiSessions.delete(sessionId); this.sessions.delete(sessionId); this.markSessionComplete(sessionId); this.emit('sessionClosed', sessionId); } /** * Get health status */ async getHealthStatus(): Promise<ProtocolHealthStatus> { const baseHealth = await super.getHealthStatus(); this.healthStatus.metrics.activeSessions = this.wmiSessions.size; this.healthStatus.lastChecked = new Date(); return { ...baseHealth, ...this.healthStatus, metrics: { ...baseHealth.metrics, activeSessions: this.wmiSessions.size, }, }; } /** * Dispose of protocol resources */ async dispose(): Promise<void> { this.logger.info('Disposing WMI Protocol'); // Close all sessions const sessionIds = Array.from(this.wmiSessions.keys()); await Promise.all(sessionIds.map((id) => this.closeSession(id))); // Clean up await super.cleanup(); } // WMI-specific helper methods /** * Query system information */ async querySystemInfo(sessionId: string): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const queries = { computer: 'SELECT * FROM Win32_ComputerSystem', os: 'SELECT * FROM Win32_OperatingSystem', cpu: 'SELECT * FROM Win32_Processor', memory: 'SELECT * FROM Win32_PhysicalMemory', disk: 'SELECT * FROM Win32_DiskDrive', network: 'SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=TRUE', }; const results: any = {}; for (const [key, query] of Object.entries(queries)) { try { results[key] = await this.executeWMIQuery(session, { query, format: 'json', }); } catch (error) { results[key] = { error: error instanceof Error ? error.message : String(error), }; } } return results; } /** * Monitor performance counters */ async startPerformanceMonitoring( sessionId: string, counters: string[], intervalMs: number = 5000 ): Promise<string> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const monitorId = uuidv4(); const interval = setInterval(async () => { try { const results: any = {}; for (const counter of counters) { const query = this.buildPerformanceQuery(counter); results[counter] = await this.executeWMIQuery(session, { query, format: 'json', }); } this.addToOutputBuffer(sessionId, { sessionId, type: 'stdout', data: JSON.stringify( { monitorId, timestamp: new Date(), counters: results }, null, 2 ), timestamp: new Date(), raw: results, }); } catch (error) { this.logger.error(`Performance monitoring error:`, error); } }, intervalMs); session.monitoringIntervals.set(monitorId, interval); return monitorId; } /** * Stop performance monitoring */ async stopPerformanceMonitoring( sessionId: string, monitorId: string ): Promise<void> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const interval = session.monitoringIntervals.get(monitorId); if (interval) { clearInterval(interval); session.monitoringIntervals.delete(monitorId); } } /** * Build performance counter query */ private buildPerformanceQuery(counter: string): string { const counterMap: Record<string, string> = { cpu: 'SELECT PercentProcessorTime FROM Win32_PerfFormattedData_PerfOS_Processor WHERE Name="_Total"', memory: 'SELECT AvailableMBytes, PercentCommittedBytesInUse FROM Win32_PerfFormattedData_PerfOS_Memory', disk: 'SELECT DiskReadBytesPerSec, DiskWriteBytesPerSec, PercentDiskTime FROM Win32_PerfFormattedData_PerfDisk_PhysicalDisk WHERE Name="_Total"', network: 'SELECT BytesReceivedPerSec, BytesSentPerSec FROM Win32_PerfFormattedData_Tcpip_NetworkInterface', }; return ( counterMap[counter] || `SELECT * FROM Win32_PerfFormattedData_${counter}` ); } /** * Query installed software */ async queryInstalledSoftware(sessionId: string): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } return this.executeWMIQuery(session, { query: 'SELECT Name, Version, Vendor, InstallDate FROM Win32_Product', format: 'json', }); } /** * Query running processes */ async queryProcesses(sessionId: string, filter?: string): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } let query = 'SELECT ProcessId, Name, ExecutablePath, CommandLine, WorkingSetSize, PageFileUsage, ThreadCount FROM Win32_Process'; if (filter) { query += ` WHERE ${filter}`; } return this.executeWMIQuery(session, { query, format: 'json' }); } /** * Query services */ async queryServices(sessionId: string, filter?: string): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } let query = 'SELECT Name, DisplayName, State, Status, StartMode, PathName FROM Win32_Service'; if (filter) { query += ` WHERE ${filter}`; } return this.executeWMIQuery(session, { query, format: 'json' }); } /** * Query event logs */ async queryEventLogs( sessionId: string, logName: string, maxRecords: number = 100 ): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } const query = `SELECT TOP ${maxRecords} TimeGenerated, EventCode, SourceName, EventType, Message FROM Win32_NTLogEvent WHERE Logfile="${logName}" ORDER BY TimeGenerated DESC`; return this.executeWMIQuery(session, { query, format: 'json' }); } /** * Start process via WMI */ async startProcess( sessionId: string, commandLine: string, workingDirectory?: string ): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } return this.invokeWMIMethod(session, { className: 'Win32_Process', methodName: 'Create', parameters: { CommandLine: commandLine, CurrentDirectory: workingDirectory || 'C:\\', }, }); } /** * Terminate process via WMI */ async terminateProcess(sessionId: string, processId: number): Promise<any> { const session = this.wmiSessions.get(sessionId); if (!session) { throw new Error(`Session ${sessionId} not found`); } return this.invokeWMIMethod(session, { className: 'Win32_Process', methodName: 'Terminate', instance: `ProcessId=${processId}`, }); } } export default WMIProtocol;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ooples/mcp-console-automation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server