Skip to main content
Glama

IT-MCP

by acampkin95
windowsAdmin.ts20.6 kB
import { CommandRunner, type CommandOptions, type CommandResult } from "../utils/commandRunner.js"; export type WindowsAuthMethod = "Default" | "Negotiate" | "Kerberos" | "Basic" | "Credssp"; export interface WindowsConnectionOptions { readonly host: string; readonly username?: string; readonly password?: string; readonly passwordEnvVar?: string; readonly useSsl?: boolean; readonly port?: number; readonly authentication?: WindowsAuthMethod; readonly ignoreCertErrors?: boolean; } export interface WindowsCommandResult extends CommandResult { readonly json?: unknown; } export interface WindowsUpdateActionOptions { readonly mode: "list" | "install"; readonly includeOptional?: boolean; readonly categories?: string[]; } export interface WindowsRoleFeatureOptions { readonly action: "list" | "install" | "remove"; readonly featureNames?: string[]; readonly includeManagementTools?: boolean; } export interface WindowsPerformanceOptions { readonly sampleSeconds?: number; readonly includeDisks?: boolean; readonly includeNetwork?: boolean; } interface RemoteExecutionPlan { readonly command: string; readonly env?: NodeJS.ProcessEnv; } const DEFAULT_PASSWORD_ENV = "WINDOWS_REMOTE_PASSWORD"; export class WindowsAdminService { public constructor(private readonly runner: CommandRunner) {} public systemInfo(options: WindowsConnectionOptions): Promise<WindowsCommandResult> { const script = ` $os = Get-CimInstance -ClassName Win32_OperatingSystem $cs = Get-CimInstance -ClassName Win32_ComputerSystem $bios = Get-CimInstance -ClassName Win32_BIOS $disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" | Select-Object DeviceID, VolumeName, FileSystem, @{Name='SizeGB';Expression={[math]::Round($_.Size/1GB,2)}}, @{Name='FreeGB';Expression={[math]::Round($_.FreeSpace/1GB,2)}}, @{Name='FreePercent';Expression={ if ($_.Size -eq 0) { 0 } else { [math]::Round(($_.FreeSpace / $_.Size) * 100, 2) } }} $hotfix = @() try { $hotfix = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 HotFixID, Description, InstalledOn } catch { $hotfix = @() } $uptime = (Get-Date) - $os.LastBootUpTime [pscustomobject]@{ ComputerName = $env:COMPUTERNAME OS = $os | Select-Object Caption, Version, BuildNumber, LastBootUpTime ComputerSystem = $cs | Select-Object Manufacturer, Model, Domain, TotalPhysicalMemory, NumberOfProcessors, NumberOfLogicalProcessors BIOS = $bios | Select-Object Manufacturer, SMBIOSBIOSVersion, ReleaseDate MemoryGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2) Uptime = @{ Days = [int]$uptime.TotalDays Hours = $uptime.Hours Minutes = $uptime.Minutes } HotFixes = $hotfix Disks = $disks } `.trim(); return this.invokeRemote(script, options, true); } public serviceAction( options: WindowsConnectionOptions & { readonly service: string; readonly action: "status" | "start" | "stop" | "restart"; readonly force?: boolean; }, ): Promise<WindowsCommandResult> { const forceFlag = options.force ? "$true" : "$false"; const script = ` $serviceName = ${this.quotePs(options.service)} $action = ${this.quotePs(options.action)} $force = ${forceFlag} $service = Get-Service -Name $serviceName -ErrorAction Stop switch ($action.ToLowerInvariant()) { "start" { Start-Service -Name $serviceName -ErrorAction Stop } "stop" { if ($force) { Stop-Service -Name $serviceName -Force -ErrorAction Stop } else { Stop-Service -Name $serviceName -ErrorAction Stop } } "restart" { if ($force) { Restart-Service -Name $serviceName -Force -ErrorAction Stop } else { Restart-Service -Name $serviceName -ErrorAction Stop } } default { } } $service = Get-Service -Name $serviceName -ErrorAction Stop $details = Get-CimInstance -ClassName Win32_Service -Filter "Name='$serviceName'" | Select-Object Name, DisplayName, State, Status, StartMode, StartName [pscustomobject]@{ Name = $service.Name DisplayName = $service.DisplayName Status = $service.Status StartType = $details.StartMode RunAs = $details.StartName } `.trim(); return this.invokeRemote(script, options, true); } public processSummary( options: WindowsConnectionOptions & { readonly nameFilter?: string; readonly top?: number; readonly sortBy?: "cpu" | "memory"; }, ): Promise<WindowsCommandResult> { const filterClause = options.nameFilter ? `$processes = $processes | Where-Object { $_.Name -like ${this.quotePs(options.nameFilter)} }` : ""; const sortBy = options.sortBy === "memory" ? "WorkingSet64" : "CPU"; const top = typeof options.top === "number" ? Math.max(1, options.top) : 10; const script = ` $processes = Get-Process ${filterClause} $processes = $processes | Select-Object Name, Id, CPU, @{Name='MemoryMB';Expression={[math]::Round($_.WorkingSet64/1MB,2)}}, @{Name='StartTime';Expression={ try { $_.StartTime } catch { $null } }} $processes = $processes | Sort-Object -Property ${sortBy} -Descending $processes | Select-Object -First ${top} `.trim(); return this.invokeRemote(script, options, true); } public eventLog( options: WindowsConnectionOptions & { readonly logName: string; readonly maxEvents: number; readonly level?: 1 | 2 | 3 | 4 | 5; readonly eventId?: number; readonly provider?: string; }, ): Promise<WindowsCommandResult> { const filterParts = [`LogName = ${this.quotePs(options.logName)}`]; if (options.level) { filterParts.push(`Level = ${options.level}`); } if (typeof options.eventId === "number") { filterParts.push(`Id = ${options.eventId}`); } const providerFilter = options.provider ? `$events = $events | Where-Object { $_.ProviderName -like ${this.quotePs(options.provider)} }` : ""; const script = ` $filter = @{ ${filterParts.join("; ")} } $events = Get-WinEvent -FilterHashtable $filter -MaxEvents ${Math.max(1, options.maxEvents)} ${providerFilter} $events | Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message `.trim(); return this.invokeRemote(script, options, true); } public diskStatus(options: WindowsConnectionOptions): Promise<WindowsCommandResult> { const script = ` $volumes = Get-Volume | Where-Object { $_.DriveLetter } | Select-Object DriveLetter, FileSystemLabel, FileSystem, @{Name='SizeGB';Expression={[math]::Round($_.Size/1GB,2)}}, @{Name='FreeGB';Expression={[math]::Round($_.SizeRemaining/1GB,2)}}, @{Name='FreePercent';Expression={ if ($_.Size -eq 0) { 0 } else { [math]::Round(($_.SizeRemaining / $_.Size) * 100, 2) } }} try { $physical = Get-PhysicalDisk | Select-Object FriendlyName, SerialNumber, MediaType, Size, HealthStatus, OperationalStatus } catch { $physical = @() } try { $pools = Get-StoragePool | Select-Object FriendlyName, HealthStatus, OperationalStatus, Size, AllocatedSize } catch { $pools = @() } [pscustomobject]@{ Volumes = $volumes PhysicalDisks = $physical StoragePools = $pools } `.trim(); return this.invokeRemote(script, options, true); } public networkStatus( options: WindowsConnectionOptions & { readonly includeRoutes?: boolean; readonly testHost?: string; }, ): Promise<WindowsCommandResult> { const routeClause = options.includeRoutes ? ` try { $routes = Get-NetRoute -AddressFamily IPv4 | Select-Object -First 50 DestinationPrefix, InterfaceAlias, NextHop, RouteMetric } catch { $routes = @() } `.trim() : "$routes = @()"; const testClause = options.testHost ? ` try { $test = Test-NetConnection -ComputerName ${this.quotePs(options.testHost)} -InformationLevel Detailed } catch { $test = [pscustomobject]@{ ComputerName = ${this.quotePs(options.testHost)} Error = $_.Exception.Message } } `.trim() : "$test = $null"; const script = ` try { $adapters = Get-NetAdapter | Select-Object Name, InterfaceDescription, Status, MacAddress, LinkSpeed } catch { $adapters = @() } try { $addresses = Get-NetIPAddress | Where-Object { $_.AddressState -eq 'Preferred' } | Select-Object InterfaceAlias, IPAddress, PrefixLength, AddressFamily } catch { $addresses = @() } ${routeClause} ${testClause} [pscustomobject]@{ Adapters = $adapters Addresses = $addresses Routes = $routes ConnectivityTest = $test } `.trim(); return this.invokeRemote(script, options, true); } public scheduledTasks( options: WindowsConnectionOptions & { readonly taskNameFilter?: string; readonly stateFilter?: string; }, ): Promise<WindowsCommandResult> { const nameFilter = options.taskNameFilter ? `$tasks = $tasks | Where-Object { $_.TaskName -like ${this.quotePs(options.taskNameFilter)} }` : ""; const stateFilter = options.stateFilter ? `$tasks = $tasks | Where-Object { $_.State -eq ${this.quotePs(options.stateFilter)} }` : ""; const script = ` try { $tasks = Get-ScheduledTask } catch { $tasks = @() } ${nameFilter} ${stateFilter} $tasks | ForEach-Object { try { $info = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath } catch { $info = $null } [pscustomobject]@{ TaskName = $_.TaskName TaskPath = $_.TaskPath State = $_.State LastRunTime = if ($info) { $info.LastRunTime } else { $null } NextRunTime = if ($info) { $info.NextRunTime } else { $null } LastTaskResult = if ($info) { $info.LastTaskResult } else { $null } } } `.trim(); return this.invokeRemote(script, options, true); } public firewallStatus( options: WindowsConnectionOptions & { readonly includeRules?: boolean; readonly ruleNameFilter?: string; readonly profileFilter?: string; }, ): Promise<WindowsCommandResult> { const includeRules = options.includeRules ?? false; const ruleRetrieval = includeRules ? ` try { $rules = Get-NetFirewallRule -Enabled True ${options.profileFilter ? `$rules = $rules | Where-Object { $_.Profile -like ${this.quotePs(options.profileFilter)} }` : ""} ${options.ruleNameFilter ? `$rules = $rules | Where-Object { $_.DisplayName -like ${this.quotePs(options.ruleNameFilter)} }` : ""} $rules = $rules | Select-Object -First 75 DisplayName, Direction, Action, Profile, Enabled } catch { $rules = @() } ` : "$rules = @()"; const script = ` try { $profiles = Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction, AllowInboundRules, AllowLocalFirewallRules, AllowLocalIPsecRules } catch { $profiles = @() } ${ruleRetrieval} [pscustomobject]@{ Profiles = $profiles Rules = $rules } `.trim(); return this.invokeRemote(script, options, true); } public runScript( script: string, options: WindowsConnectionOptions, expectJson: boolean, ): Promise<WindowsCommandResult> { return this.invokeRemote(script, options, expectJson); } public windowsUpdateAction( options: WindowsConnectionOptions & WindowsUpdateActionOptions, ): Promise<WindowsCommandResult> { const queryParts = ["IsInstalled=0"]; if (!options.includeOptional) { queryParts.push("IsHidden=0"); } if (options.categories?.length) { const categoryClauses = options.categories.map((cat) => `CategoryIDs contains '{${cat}}'`); queryParts.push(`(${categoryClauses.join(" or ")})`); } const searchQuery = queryParts.join(" and "); const script = ` $session = New-Object -ComObject Microsoft.Update.Session $searcher = $session.CreateUpdateSearcher() $searchResult = $searcher.Search(${this.quotePs(searchQuery)}) $updates = @() for ($i = 0; $i -lt $searchResult.Updates.Count; $i++) { $u = $searchResult.Updates.Item($i) $updates += [pscustomobject]@{ Title = $u.Title KB = $u.KBArticleIDs -join ', ' Severity = $u.MsrcSeverity IsMandatory = $u.IsMandatory Downloaded = $u.IsDownloaded Categories = ($u.Categories | Select-Object -ExpandProperty Name) } } if (${options.mode === "install" ? "$true" : "$false"} -and $updates.Count -gt 0) { $updateCollection = New-Object -ComObject Microsoft.Update.UpdateColl foreach ($update in $searchResult.Updates) { [void]$updateCollection.Add($update) } $downloader = $session.CreateUpdateDownloader() $downloader.Updates = $updateCollection $downloadResult = $downloader.Download() $installer = $session.CreateUpdateInstaller() $installer.Updates = $updateCollection $installResult = $installer.Install() $installSummary = [pscustomobject]@{ HResult = $installResult.HResult RebootRequired = $installResult.RebootRequired ResultCode = $installResult.ResultCode UpdatesInstalled = $installResult.UpdatesInstalled } } else { $installSummary = $null } [pscustomobject]@{ Count = $updates.Count Updates = $updates InstallSummary = $installSummary } `.trim(); return this.invokeRemote(script, options, true); } public rolesAndFeatures( options: WindowsConnectionOptions & WindowsRoleFeatureOptions, ): Promise<WindowsCommandResult> { const featureList = options.featureNames?.length ? options.featureNames.map((name) => this.quotePs(name)).join(",") : ""; const script = (() => { switch (options.action) { case "list": return `Get-WindowsFeature | Select-Object Name, DisplayName, Installed | ConvertTo-Json -Depth 3`; case "install": { if (!featureList) { throw new Error("Feature installation requires 'featureNames'."); } const includeMgmt = options.includeManagementTools ? "-IncludeManagementTools" : ""; return ` $result = Install-WindowsFeature -Name ${featureList} ${includeMgmt} [pscustomobject]@{ Success = $result.Success RestartNeeded = $result.RestartNeeded FeatureResult = $result.FeatureResult | Select-Object Name, DisplayName, RestartNeeded, Success } `.trim(); } case "remove": { if (!featureList) { throw new Error("Feature removal requires 'featureNames'."); } return ` $result = Remove-WindowsFeature -Name ${featureList} [pscustomobject]@{ Success = $result.Success RestartNeeded = $result.RestartNeeded FeatureResult = $result.FeatureResult | Select-Object Name, DisplayName, RestartNeeded, Success } `.trim(); } default: { const exhaustive: never = options.action; throw new Error(`Unsupported roles/features action: ${exhaustive}`); } } })(); return this.invokeRemote(script, options, true); } public performanceSnapshot( options: WindowsConnectionOptions & WindowsPerformanceOptions, ): Promise<WindowsCommandResult> { const sampleSeconds = Math.max(1, Math.min(30, options.sampleSeconds ?? 5)); const includeDisks = options.includeDisks ? "$true" : "$false"; const includeNetwork = options.includeNetwork ? "$true" : "$false"; const script = ` $cpuSamples = Get-Counter -Counter "\\Processor(_Total)\\% Processor Time" -SampleInterval 1 -MaxSamples ${sampleSeconds} $cpuAvg = [math]::Round((($cpuSamples.CounterSamples | Measure-Object -Property CookedValue -Average).Average),2) $os = Get-CimInstance -ClassName Win32_OperatingSystem $memTotal = [math]::Round($os.TotalVisibleMemorySize/1KB,2) $memFree = [math]::Round($os.FreePhysicalMemory/1KB,2) $memUsedPct = if ($memTotal -eq 0) { 0 } else { [math]::Round((($memTotal - $memFree)/$memTotal)*100,2) } if (${includeDisks} ) { $diskCounters = Get-Counter -Counter "\\PhysicalDisk(*)\\Avg. Disk Queue Length" -SampleInterval 1 -MaxSamples ${sampleSeconds} $diskResults = $diskCounters.CounterSamples | Group-Object InstanceName | ForEach-Object { [pscustomobject]@{ Disk = $_.Name AvgQueue = [math]::Round((($_.Group | Measure-Object -Property CookedValue -Average).Average),3) } } } else { $diskResults = @() } if (${includeNetwork} ) { $netCounters = Get-Counter -Counter "\\Network Interface(*)\\Bytes Total/sec" -SampleInterval 1 -MaxSamples ${sampleSeconds} $netResults = $netCounters.CounterSamples | Group-Object InstanceName | ForEach-Object { [pscustomobject]@{ Interface = $_.Name AvgBytesPerSec = [math]::Round((($_.Group | Measure-Object -Property CookedValue -Average).Average),2) } } } else { $netResults = @() } [pscustomobject]@{ CpuPercent = $cpuAvg Memory = @{ TotalMB = $memTotal FreeMB = $memFree UsedPercent = $memUsedPct } Disk = $diskResults Network = $netResults } `.trim(); return this.invokeRemote(script, options, true); } private async invokeRemote( scriptBody: string, options: WindowsConnectionOptions, expectJson: boolean, ): Promise<WindowsCommandResult> { const plan = this.buildRemoteExecution(scriptBody, options, expectJson); const runnerOptions: CommandOptions = plan.env ? { env: plan.env } : {}; const result = await this.runner.run(plan.command, runnerOptions); if (!expectJson) { return result; } const trimmed = result.stdout.trim(); if (!trimmed.length) { return { ...result, json: undefined, }; } try { const parsed = JSON.parse(trimmed); return { ...result, json: parsed, }; } catch { return result; } } private buildRemoteExecution( scriptBody: string, options: WindowsConnectionOptions, expectJson: boolean, ): RemoteExecutionPlan { if (!options.host) { throw new Error("Windows host is required."); } const passwordEnvVar = options.passwordEnvVar ?? DEFAULT_PASSWORD_ENV; const sessionLines: string[] = [ "$ErrorActionPreference = 'Stop'", `$sessionArgs = @{ ComputerName = ${this.quotePs(options.host)} }`, `$sessionArgs.Authentication = ${this.quotePs(options.authentication ?? "Default")}`, ]; if (options.useSsl) { sessionLines.push("$sessionArgs.UseSSL = $true"); } if (typeof options.port === "number") { sessionLines.push(`$sessionArgs.Port = ${options.port}`); } if (options.ignoreCertErrors) { sessionLines.push( "$sessionArgs.SessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck", ); } if (options.username) { sessionLines.push(`$credentialUser = ${this.quotePs(options.username)}`); sessionLines.push(`$passwordEnvVar = ${this.quotePs(passwordEnvVar)}`); sessionLines.push(` $passwordValue = [Environment]::GetEnvironmentVariable($passwordEnvVar, [EnvironmentVariableTarget]::Process) if ([string]::IsNullOrEmpty($passwordValue)) { $passwordValue = [Environment]::GetEnvironmentVariable($passwordEnvVar, [EnvironmentVariableTarget]::User) } if ([string]::IsNullOrEmpty($passwordValue)) { $passwordValue = [Environment]::GetEnvironmentVariable($passwordEnvVar, [EnvironmentVariableTarget]::Machine) } if (-not [string]::IsNullOrEmpty($passwordValue)) { $securePassword = ConvertTo-SecureString $passwordValue -AsPlainText -Force $sessionArgs.Credential = [PSCredential]::new($credentialUser, $securePassword) [Environment]::SetEnvironmentVariable($passwordEnvVar, $null, [EnvironmentVariableTarget]::Process) } `.trim()); } const remoteScriptEncoded = Buffer.from(scriptBody, "utf8").toString("base64"); sessionLines.push( `$remoteScript = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(${this.quotePs(remoteScriptEncoded)}))`, ); sessionLines.push("$scriptBlock = [ScriptBlock]::Create($remoteScript)"); sessionLines.push("$result = Invoke-Command @sessionArgs -ScriptBlock $scriptBlock"); sessionLines.push(`$expectJson = ${expectJson ? "$true" : "$false"}`); sessionLines.push(` if ($null -eq $result) { if ($expectJson) { "" } else { "" } } elseif ($expectJson) { $json = $result | ConvertTo-Json -Depth 8 if ($null -eq $json) { "" } else { $json } } else { ($result | Out-String).TrimEnd() } `.trim()); const fullScript = sessionLines.join("\n"); const encodedCommand = Buffer.from(fullScript, "utf16le").toString("base64"); const command = `pwsh -NoLogo -NoProfile -EncodedCommand ${encodedCommand}`; const env = options.password ? { ...process.env, [passwordEnvVar]: options.password, } : undefined; return { command, env }; } private quotePs(value: string): string { return `'${value.replace(/'/g, "''")}'`; } }

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/acampkin95/MCP'

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