@mokoconsulting/mcp-windows 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogitea/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogitea/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogitea/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogitea/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_connection_issue.md +67 -0
- package/.mokogitea/ISSUE_TEMPLATE/mcp_tool_request.md +49 -0
- package/.mokogitea/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogitea/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogitea/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogitea/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogitea/branch-protection.yml +251 -0
- package/.mokogitea/workflows/auto-assign.yml +76 -0
- package/.mokogitea/workflows/auto-bump.yml +66 -0
- package/.mokogitea/workflows/auto-dev-issue.yml +207 -0
- package/.mokogitea/workflows/auto-release.yml +421 -0
- package/.mokogitea/workflows/branch-cleanup.yml +48 -0
- package/.mokogitea/workflows/cascade-dev.yml +10 -0
- package/.mokogitea/workflows/changelog-validation.yml +101 -0
- package/.mokogitea/workflows/ci-generic.yml +191 -0
- package/.mokogitea/workflows/cleanup.yml +87 -0
- package/.mokogitea/workflows/codeql-analysis.yml +115 -0
- package/.mokogitea/workflows/copilot-agent.yml +44 -0
- package/.mokogitea/workflows/deploy-manual.yml +126 -0
- package/.mokogitea/workflows/enterprise-firewall-setup.yml +758 -0
- package/.mokogitea/workflows/gitleaks.yml +92 -0
- package/.mokogitea/workflows/issue-branch.yml +73 -0
- package/.mokogitea/workflows/mcp-auto-release.yml +278 -0
- package/.mokogitea/workflows/mcp-build-test.yml +65 -0
- package/.mokogitea/workflows/mcp-sdk-check.yml +109 -0
- package/.mokogitea/workflows/mcp-tool-inventory.yml +61 -0
- package/.mokogitea/workflows/notify.yml +70 -0
- package/.mokogitea/workflows/npm-publish.yml +113 -0
- package/.mokogitea/workflows/pr-check.yml +534 -0
- package/.mokogitea/workflows/pre-release.yml +252 -0
- package/.mokogitea/workflows/rc-revert.yml +66 -0
- package/.mokogitea/workflows/repo-health.yml +712 -0
- package/.mokogitea/workflows/repository-cleanup.yml +525 -0
- package/.mokogitea/workflows/security-audit.yml +82 -0
- package/.mokogitea/workflows/standards-compliance.yml +2614 -0
- package/.mokogitea/workflows/sync-version-on-merge.yml +133 -0
- package/.mokogitea/workflows/update-server.yml +312 -0
- package/.mokogitea/workflows/workflow-sync-trigger.yml +73 -0
- package/CHANGELOG.md +130 -0
- package/CLAUDE.md +49 -0
- package/CONTRIBUTING.md +161 -0
- package/ISSUES.md +601 -0
- package/Makefile +70 -0
- package/README.md +80 -0
- package/automation/ci-issue-reporter.sh +237 -0
- package/config.example.json +18 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +111 -0
- package/dist/shell.d.ts +50 -0
- package/dist/shell.js +209 -0
- package/dist/tools/apps.d.ts +3 -0
- package/dist/tools/apps.js +63 -0
- package/dist/tools/audio.d.ts +3 -0
- package/dist/tools/audio.js +142 -0
- package/dist/tools/audio_apps.d.ts +3 -0
- package/dist/tools/audio_apps.js +86 -0
- package/dist/tools/automation.d.ts +3 -0
- package/dist/tools/automation.js +261 -0
- package/dist/tools/bluetooth.d.ts +3 -0
- package/dist/tools/bluetooth.js +96 -0
- package/dist/tools/clipboard.d.ts +3 -0
- package/dist/tools/clipboard.js +118 -0
- package/dist/tools/config.d.ts +3 -0
- package/dist/tools/config.js +85 -0
- package/dist/tools/dialog.d.ts +3 -0
- package/dist/tools/dialog.js +72 -0
- package/dist/tools/display.d.ts +3 -0
- package/dist/tools/display.js +256 -0
- package/dist/tools/drives.d.ts +3 -0
- package/dist/tools/drives.js +98 -0
- package/dist/tools/environment.d.ts +3 -0
- package/dist/tools/environment.js +129 -0
- package/dist/tools/execute.d.ts +3 -0
- package/dist/tools/execute.js +28 -0
- package/dist/tools/filesystem.d.ts +3 -0
- package/dist/tools/filesystem.js +230 -0
- package/dist/tools/firewall.d.ts +3 -0
- package/dist/tools/firewall.js +108 -0
- package/dist/tools/hosts.d.ts +3 -0
- package/dist/tools/hosts.js +119 -0
- package/dist/tools/maintenance.d.ts +3 -0
- package/dist/tools/maintenance.js +236 -0
- package/dist/tools/netstat.d.ts +3 -0
- package/dist/tools/netstat.js +56 -0
- package/dist/tools/network.d.ts +3 -0
- package/dist/tools/network.js +70 -0
- package/dist/tools/notification.d.ts +3 -0
- package/dist/tools/notification.js +41 -0
- package/dist/tools/power.d.ts +3 -0
- package/dist/tools/power.js +104 -0
- package/dist/tools/printer.d.ts +3 -0
- package/dist/tools/printer.js +97 -0
- package/dist/tools/process.d.ts +3 -0
- package/dist/tools/process.js +54 -0
- package/dist/tools/process_kill.d.ts +3 -0
- package/dist/tools/process_kill.js +48 -0
- package/dist/tools/recycle_bin.d.ts +3 -0
- package/dist/tools/recycle_bin.js +108 -0
- package/dist/tools/registry.d.ts +3 -0
- package/dist/tools/registry.js +136 -0
- package/dist/tools/scheduler.d.ts +3 -0
- package/dist/tools/scheduler.js +116 -0
- package/dist/tools/service.d.ts +3 -0
- package/dist/tools/service.js +79 -0
- package/dist/tools/startup.d.ts +3 -0
- package/dist/tools/startup.js +159 -0
- package/dist/tools/storage.d.ts +3 -0
- package/dist/tools/storage.js +129 -0
- package/dist/tools/system.d.ts +3 -0
- package/dist/tools/system.js +84 -0
- package/dist/tools/system_mgmt.d.ts +3 -0
- package/dist/tools/system_mgmt.js +174 -0
- package/dist/tools/terminal.d.ts +3 -0
- package/dist/tools/terminal.js +80 -0
- package/dist/tools/theme.d.ts +3 -0
- package/dist/tools/theme.js +165 -0
- package/dist/tools/usb.d.ts +3 -0
- package/dist/tools/usb.js +52 -0
- package/dist/tools/virtual_desktop.d.ts +3 -0
- package/dist/tools/virtual_desktop.js +112 -0
- package/dist/tools/wifi.d.ts +3 -0
- package/dist/tools/wifi.js +136 -0
- package/dist/tools/window.d.ts +3 -0
- package/dist/tools/window.js +189 -0
- package/dist/tools/winget.d.ts +3 -0
- package/dist/tools/winget.js +79 -0
- package/dist/tools/wsl.d.ts +3 -0
- package/dist/tools/wsl.js +99 -0
- package/docs/API.md +63 -0
- package/docs/ARCHITECTURE.md +73 -0
- package/docs/INSTALLATION.md +102 -0
- package/docs/index.md +12 -0
- package/package.json +35 -0
- package/scripts/setup.mjs +123 -0
- package/src/index.ts +125 -0
- package/src/shell.ts +253 -0
- package/src/tools/apps.ts +76 -0
- package/src/tools/audio.ts +161 -0
- package/src/tools/audio_apps.ts +98 -0
- package/src/tools/automation.ts +297 -0
- package/src/tools/bluetooth.ts +114 -0
- package/src/tools/clipboard.ts +138 -0
- package/src/tools/config.ts +105 -0
- package/src/tools/dialog.ts +87 -0
- package/src/tools/display.ts +285 -0
- package/src/tools/drives.ts +124 -0
- package/src/tools/environment.ts +146 -0
- package/src/tools/execute.ts +35 -0
- package/src/tools/filesystem.ts +273 -0
- package/src/tools/firewall.ts +125 -0
- package/src/tools/hosts.ts +135 -0
- package/src/tools/maintenance.ts +299 -0
- package/src/tools/netstat.ts +72 -0
- package/src/tools/network.ts +84 -0
- package/src/tools/notification.ts +50 -0
- package/src/tools/power.ts +123 -0
- package/src/tools/printer.ts +114 -0
- package/src/tools/process.ts +80 -0
- package/src/tools/process_kill.ts +57 -0
- package/src/tools/recycle_bin.ts +126 -0
- package/src/tools/registry.ts +165 -0
- package/src/tools/scheduler.ts +140 -0
- package/src/tools/service.ts +102 -0
- package/src/tools/startup.ts +180 -0
- package/src/tools/storage.ts +141 -0
- package/src/tools/system.ts +99 -0
- package/src/tools/system_mgmt.ts +190 -0
- package/src/tools/terminal.ts +117 -0
- package/src/tools/theme.ts +205 -0
- package/src/tools/usb.ts +65 -0
- package/src/tools/virtual_desktop.ts +122 -0
- package/src/tools/wifi.ts +157 -0
- package/src/tools/window.ts +211 -0
- package/src/tools/winget.ts +100 -0
- package/src/tools/wsl.ts +112 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_registry_read (#29), windows_registry_write (#30)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
function expandHive(path: string): string {
|
|
12
|
+
return path
|
|
13
|
+
.replace(/^HKLM[:\\\/]/i, 'HKLM:\\')
|
|
14
|
+
.replace(/^HKCU[:\\\/]/i, 'HKCU:\\')
|
|
15
|
+
.replace(/^HKCR[:\\\/]/i, 'HKCR:\\')
|
|
16
|
+
.replace(/^HKU[:\\\/]/i, 'HKU:\\')
|
|
17
|
+
.replace(/^HKCC[:\\\/]/i, 'HKCC:\\');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function registerRegistryTools(server: McpServer): void {
|
|
21
|
+
server.tool(
|
|
22
|
+
'windows_registry_read',
|
|
23
|
+
'Read Windows Registry keys, subkeys, and values. Supports HKLM, HKCU, HKCR abbreviations.',
|
|
24
|
+
{
|
|
25
|
+
path: z.string().describe('Registry path (e.g. "HKCU:\\Software\\Microsoft")'),
|
|
26
|
+
value: z.string().optional().describe('Specific value name to read (omit to list all values)'),
|
|
27
|
+
subkeys: z.boolean().default(false).describe('List subkeys instead of values'),
|
|
28
|
+
},
|
|
29
|
+
async ({ path, value, subkeys }) => {
|
|
30
|
+
const regPath = expandHive(path);
|
|
31
|
+
|
|
32
|
+
if (subkeys) {
|
|
33
|
+
const ps = `
|
|
34
|
+
Get-ChildItem -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop | ForEach-Object {
|
|
35
|
+
[PSCustomObject]@{
|
|
36
|
+
Name = $_.PSChildName
|
|
37
|
+
SubKeyCount = $_.SubKeyCount
|
|
38
|
+
ValueCount = $_.ValueCount
|
|
39
|
+
}
|
|
40
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
41
|
+
|
|
42
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
43
|
+
if (result.exitCode !== 0) {
|
|
44
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
if (!result.stdout) {
|
|
47
|
+
return { content: [{ type: 'text', text: 'No subkeys found.' }] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const keys = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
51
|
+
const lines = keys.map((k: { Name: string; SubKeyCount: number; ValueCount: number }) =>
|
|
52
|
+
` ${k.Name.padEnd(40)} ${k.SubKeyCount} subkeys, ${k.ValueCount} values`,
|
|
53
|
+
);
|
|
54
|
+
return { content: [{ type: 'text', text: `${regPath}\n${'─'.repeat(70)}\n${lines.join('\n')}` }] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (value) {
|
|
58
|
+
const ps = `
|
|
59
|
+
$v = Get-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${value.replace(/'/g, "''")}' -ErrorAction Stop
|
|
60
|
+
$raw = $v.'${value.replace(/'/g, "''")}'
|
|
61
|
+
$kind = (Get-Item -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop).GetValueKind('${value.replace(/'/g, "''")}')
|
|
62
|
+
[PSCustomObject]@{
|
|
63
|
+
Name = '${value.replace(/'/g, "''")}'
|
|
64
|
+
Type = $kind.ToString()
|
|
65
|
+
Value = $raw
|
|
66
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
67
|
+
|
|
68
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
69
|
+
if (result.exitCode !== 0) {
|
|
70
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
71
|
+
}
|
|
72
|
+
const v = JSON.parse(result.stdout);
|
|
73
|
+
return { content: [{ type: 'text', text: `${regPath}\\${v.Name}\nType: ${v.Type}\nValue: ${JSON.stringify(v.Value)}` }] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// List all values
|
|
77
|
+
const ps = `
|
|
78
|
+
$key = Get-Item -Path '${regPath.replace(/'/g, "''")}' -ErrorAction Stop
|
|
79
|
+
$key.GetValueNames() | ForEach-Object {
|
|
80
|
+
$name = $_
|
|
81
|
+
$val = $key.GetValue($name)
|
|
82
|
+
$kind = $key.GetValueKind($name)
|
|
83
|
+
[PSCustomObject]@{
|
|
84
|
+
Name = if ($name) { $name } else { '(Default)' }
|
|
85
|
+
Type = $kind.ToString()
|
|
86
|
+
Value = $val
|
|
87
|
+
}
|
|
88
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
89
|
+
|
|
90
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
91
|
+
if (result.exitCode !== 0) {
|
|
92
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
93
|
+
}
|
|
94
|
+
if (!result.stdout) {
|
|
95
|
+
return { content: [{ type: 'text', text: 'No values found.' }] };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const values = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
99
|
+
const lines = values.map((v: { Name: string; Type: string; Value: unknown }) => {
|
|
100
|
+
const valStr = typeof v.Value === 'string' ? v.Value : JSON.stringify(v.Value);
|
|
101
|
+
return ` ${v.Name.padEnd(30)} ${v.Type.padEnd(12)} ${valStr.slice(0, 60)}`;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const header = ` ${'Name'.padEnd(30)} ${'Type'.padEnd(12)} Value`;
|
|
105
|
+
return { content: [{ type: 'text', text: `${regPath}\n${header}\n${'─'.repeat(80)}\n${lines.join('\n')}` }] };
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
server.tool(
|
|
110
|
+
'windows_registry_write',
|
|
111
|
+
'Write Windows Registry values. Restricted to HKCU by default. Use hklm_override for HKLM writes.',
|
|
112
|
+
{
|
|
113
|
+
path: z.string().describe('Registry path'),
|
|
114
|
+
name: z.string().describe('Value name'),
|
|
115
|
+
value: z.string().describe('Value data'),
|
|
116
|
+
type: z.enum(['String', 'DWord', 'QWord', 'Binary', 'ExpandString', 'MultiString']).default('String').describe('Value type'),
|
|
117
|
+
hklm_override: z.boolean().default(false).describe('Allow writing to HKLM (requires elevation)'),
|
|
118
|
+
action: z.enum(['set', 'delete', 'create_key', 'delete_key']).default('set').describe('Action'),
|
|
119
|
+
},
|
|
120
|
+
async ({ path, name, value, type, hklm_override, action }) => {
|
|
121
|
+
const regPath = expandHive(path);
|
|
122
|
+
|
|
123
|
+
// Safety check
|
|
124
|
+
if (regPath.startsWith('HKLM:') && !hklm_override) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: 'text', text: 'HKLM writes are restricted. Set hklm_override=true and ensure elevation.' }],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let ps: string;
|
|
132
|
+
|
|
133
|
+
switch (action) {
|
|
134
|
+
case 'set': {
|
|
135
|
+
const typeMap: Record<string, string> = {
|
|
136
|
+
String: 'String', DWord: 'DWord', QWord: 'QWord',
|
|
137
|
+
Binary: 'Binary', ExpandString: 'ExpandString', MultiString: 'MultiString',
|
|
138
|
+
};
|
|
139
|
+
ps = `
|
|
140
|
+
if (-not (Test-Path '${regPath.replace(/'/g, "''")}')) {
|
|
141
|
+
New-Item -Path '${regPath.replace(/'/g, "''")}' -Force | Out-Null
|
|
142
|
+
}
|
|
143
|
+
Set-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${name.replace(/'/g, "''")}' -Value '${value.replace(/'/g, "''")}' -Type ${typeMap[type]} -ErrorAction Stop
|
|
144
|
+
"Set ${regPath}\\${name} = ${value} (${type})"`;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case 'delete':
|
|
148
|
+
ps = `Remove-ItemProperty -Path '${regPath.replace(/'/g, "''")}' -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop; "Deleted ${regPath}\\${name}"`;
|
|
149
|
+
break;
|
|
150
|
+
case 'create_key':
|
|
151
|
+
ps = `New-Item -Path '${regPath.replace(/'/g, "''")}' -Force -ErrorAction Stop | Out-Null; "Created key ${regPath}"`;
|
|
152
|
+
break;
|
|
153
|
+
case 'delete_key':
|
|
154
|
+
ps = `Remove-Item -Path '${regPath.replace(/'/g, "''")}' -Recurse -Force -ErrorAction Stop; "Deleted key ${regPath}"`;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
159
|
+
return {
|
|
160
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
161
|
+
isError: result.exitCode !== 0,
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_task_scheduler_list (#27), windows_task_scheduler_manage (#28)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerSchedulerTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_task_scheduler_list',
|
|
14
|
+
'List Windows Task Scheduler tasks with status, last/next run, and trigger type.',
|
|
15
|
+
{
|
|
16
|
+
folder: z.string().default('\\').describe('Task folder path (e.g. "\\" for root, "\\Microsoft\\")'),
|
|
17
|
+
filter: z.string().optional().describe('Filter by task name (substring)'),
|
|
18
|
+
},
|
|
19
|
+
async ({ folder, filter }) => {
|
|
20
|
+
const filterClause = filter
|
|
21
|
+
? `| Where-Object { $_.TaskName -like '*${filter.replace(/'/g, "''")}*' }`
|
|
22
|
+
: '';
|
|
23
|
+
|
|
24
|
+
const ps = `
|
|
25
|
+
Get-ScheduledTask -TaskPath '${folder.replace(/'/g, "''")}*' -ErrorAction SilentlyContinue ${filterClause} | ForEach-Object {
|
|
26
|
+
$info = $_ | Get-ScheduledTaskInfo -ErrorAction SilentlyContinue
|
|
27
|
+
[PSCustomObject]@{
|
|
28
|
+
Name = $_.TaskName
|
|
29
|
+
Path = $_.TaskPath
|
|
30
|
+
State = $_.State.ToString()
|
|
31
|
+
LastRun = if ($info.LastRunTime -and $info.LastRunTime.Year -gt 1999) { $info.LastRunTime.ToString('yyyy-MM-dd HH:mm') } else { 'Never' }
|
|
32
|
+
NextRun = if ($info.NextRunTime -and $info.NextRunTime.Year -gt 1999) { $info.NextRunTime.ToString('yyyy-MM-dd HH:mm') } else { 'N/A' }
|
|
33
|
+
LastResult = if ($info) { '0x{0:X}' -f $info.LastTaskResult } else { 'N/A' }
|
|
34
|
+
Triggers = ($_.Triggers | ForEach-Object { $_.CimClass.CimClassName -replace 'MSFT_Task',''-replace 'Trigger','' }) -join ', '
|
|
35
|
+
}
|
|
36
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
37
|
+
|
|
38
|
+
const result = await runPowerShell(ps, { timeout: 30000 });
|
|
39
|
+
if (result.exitCode !== 0) {
|
|
40
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!result.stdout) {
|
|
44
|
+
return { content: [{ type: 'text', text: 'No tasks found.' }] };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const tasks = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
48
|
+
const lines = tasks.map((t: { Name: string; State: string; LastRun: string; NextRun: string; Triggers: string }) => {
|
|
49
|
+
const state = t.State === 'Ready' ? '[RDY]' : t.State === 'Running' ? '[RUN]' : t.State === 'Disabled' ? '[OFF]' : `[${t.State.slice(0, 3).toUpperCase()}]`;
|
|
50
|
+
return `${state} ${t.Name.padEnd(40).slice(0, 40)} ${t.LastRun.padEnd(16)} ${t.NextRun.padEnd(16)} ${t.Triggers}`;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const header = `State ${'Name'.padEnd(40)} ${'Last Run'.padEnd(16)} ${'Next Run'.padEnd(16)} Triggers`;
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${tasks.length} tasks` }],
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
server.tool(
|
|
61
|
+
'windows_task_scheduler_manage',
|
|
62
|
+
'Create, delete, enable, disable, or run a scheduled task.',
|
|
63
|
+
{
|
|
64
|
+
action: z.enum(['create', 'delete', 'enable', 'disable', 'run']).describe('Action to perform'),
|
|
65
|
+
name: z.string().describe('Task name'),
|
|
66
|
+
command: z.string().optional().describe('Command to execute (for create)'),
|
|
67
|
+
arguments: z.string().optional().describe('Command arguments (for create)'),
|
|
68
|
+
trigger: z.enum(['once', 'daily', 'weekly', 'hourly', 'logon', 'startup']).optional().describe('Trigger type (for create)'),
|
|
69
|
+
time: z.string().optional().describe('Time for trigger as HH:mm (for create with once/daily/weekly)'),
|
|
70
|
+
interval: z.number().optional().describe('Repetition interval in minutes (for create with hourly)'),
|
|
71
|
+
},
|
|
72
|
+
async ({ action, name, command, arguments: args, trigger, time, interval }) => {
|
|
73
|
+
let ps: string;
|
|
74
|
+
|
|
75
|
+
switch (action) {
|
|
76
|
+
case 'run':
|
|
77
|
+
ps = `Start-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop; "Task '${name}' started"`;
|
|
78
|
+
break;
|
|
79
|
+
case 'enable':
|
|
80
|
+
ps = `Enable-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop | Select-Object TaskName,State | ConvertTo-Json -Compress`;
|
|
81
|
+
break;
|
|
82
|
+
case 'disable':
|
|
83
|
+
ps = `Disable-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -ErrorAction Stop | Select-Object TaskName,State | ConvertTo-Json -Compress`;
|
|
84
|
+
break;
|
|
85
|
+
case 'delete':
|
|
86
|
+
ps = `Unregister-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -Confirm:$false -ErrorAction Stop; "Task '${name}' deleted"`;
|
|
87
|
+
break;
|
|
88
|
+
case 'create': {
|
|
89
|
+
if (!command) {
|
|
90
|
+
return { content: [{ type: 'text', text: 'Create requires command.' }], isError: true };
|
|
91
|
+
}
|
|
92
|
+
if (!trigger) {
|
|
93
|
+
return { content: [{ type: 'text', text: 'Create requires trigger type.' }], isError: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const actionPart = args
|
|
97
|
+
? `$action = New-ScheduledTaskAction -Execute '${command.replace(/'/g, "''")}' -Argument '${args.replace(/'/g, "''")}'`
|
|
98
|
+
: `$action = New-ScheduledTaskAction -Execute '${command.replace(/'/g, "''")}'`;
|
|
99
|
+
|
|
100
|
+
let triggerPart: string;
|
|
101
|
+
switch (trigger) {
|
|
102
|
+
case 'once':
|
|
103
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -Once -At '${time || '00:00'}'`;
|
|
104
|
+
break;
|
|
105
|
+
case 'daily':
|
|
106
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -Daily -At '${time || '00:00'}'`;
|
|
107
|
+
break;
|
|
108
|
+
case 'weekly':
|
|
109
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At '${time || '00:00'}'`;
|
|
110
|
+
break;
|
|
111
|
+
case 'hourly':
|
|
112
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -Once -At '00:00' -RepetitionInterval (New-TimeSpan -Minutes ${interval || 60}) -RepetitionDuration (New-TimeSpan -Days 9999)`;
|
|
113
|
+
break;
|
|
114
|
+
case 'logon':
|
|
115
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -AtLogOn`;
|
|
116
|
+
break;
|
|
117
|
+
case 'startup':
|
|
118
|
+
triggerPart = `$trigger = New-ScheduledTaskTrigger -AtStartup`;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
ps = `
|
|
123
|
+
${actionPart}
|
|
124
|
+
${triggerPart}
|
|
125
|
+
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
|
126
|
+
Register-ScheduledTask -TaskName '${name.replace(/'/g, "''")}' -Action $action -Trigger $trigger -Settings $settings -Force -ErrorAction Stop |
|
|
127
|
+
Select-Object TaskName,State | ConvertTo-Json -Compress`;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
133
|
+
if (result.exitCode !== 0) {
|
|
134
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { content: [{ type: 'text', text: result.stdout }] };
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_service_list (#4), windows_service_control (#5)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerServiceTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_service_list',
|
|
14
|
+
'List Windows services with status, startup type, and description.',
|
|
15
|
+
{
|
|
16
|
+
filter: z.string().optional().describe('Filter by service name or display name (substring)'),
|
|
17
|
+
status: z.enum(['running', 'stopped', 'all']).default('all').describe('Filter by status'),
|
|
18
|
+
},
|
|
19
|
+
async ({ filter, status }) => {
|
|
20
|
+
const filterClause = filter
|
|
21
|
+
? `| Where-Object { $_.Name -like '*${filter.replace(/'/g, "''") }*' -or $_.DisplayName -like '*${filter.replace(/'/g, "''")}*' }`
|
|
22
|
+
: '';
|
|
23
|
+
const statusClause = status === 'running'
|
|
24
|
+
? `| Where-Object { $_.Status -eq 'Running' }`
|
|
25
|
+
: status === 'stopped'
|
|
26
|
+
? `| Where-Object { $_.Status -eq 'Stopped' }`
|
|
27
|
+
: '';
|
|
28
|
+
|
|
29
|
+
const ps = `
|
|
30
|
+
$wmiCache = @{}
|
|
31
|
+
Get-CimInstance Win32_Service -ErrorAction SilentlyContinue | ForEach-Object { $wmiCache[$_.Name] = $_.Description }
|
|
32
|
+
Get-Service ${filterClause} ${statusClause} | Sort-Object DisplayName | ForEach-Object {
|
|
33
|
+
[PSCustomObject]@{
|
|
34
|
+
Name = $_.Name
|
|
35
|
+
DisplayName = $_.DisplayName
|
|
36
|
+
Status = $_.Status.ToString()
|
|
37
|
+
StartType = $_.StartType.ToString()
|
|
38
|
+
Description = if ($wmiCache[$_.Name]) { $wmiCache[$_.Name] } else { '' }
|
|
39
|
+
}
|
|
40
|
+
} | ConvertTo-Json -Depth 3 -Compress`;
|
|
41
|
+
|
|
42
|
+
const result = await runPowerShell(ps, { timeout: 45000 });
|
|
43
|
+
if (result.exitCode !== 0) {
|
|
44
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!result.stdout) {
|
|
48
|
+
return { content: [{ type: 'text', text: 'No services found.' }] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const services = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
52
|
+
const lines = services.map((s: { Name: string; DisplayName: string; Status: string; StartType: string }) =>
|
|
53
|
+
`${s.Status === 'Running' ? '[RUN]' : '[STP]'} ${s.StartType.padEnd(10)} ${s.Name.padEnd(35).slice(0, 35)} ${s.DisplayName}`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const header = `${'State'.padEnd(5)} ${'Startup'.padEnd(10)} ${'Name'.padEnd(35)} Display Name`;
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(100)}\n${lines.join('\n')}\n\n${services.length} services` }],
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
server.tool(
|
|
64
|
+
'windows_service_control',
|
|
65
|
+
'Start, stop, restart, or change startup type of a Windows service.',
|
|
66
|
+
{
|
|
67
|
+
name: z.string().describe('Service name or display name'),
|
|
68
|
+
action: z.enum(['start', 'stop', 'restart', 'enable', 'disable']).describe('Action to perform'),
|
|
69
|
+
},
|
|
70
|
+
async ({ name, action }) => {
|
|
71
|
+
let ps: string;
|
|
72
|
+
|
|
73
|
+
switch (action) {
|
|
74
|
+
case 'start':
|
|
75
|
+
ps = `Start-Service -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
|
|
76
|
+
break;
|
|
77
|
+
case 'stop':
|
|
78
|
+
ps = `Stop-Service -Name '${name.replace(/'/g, "''")}' -Force -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
|
|
79
|
+
break;
|
|
80
|
+
case 'restart':
|
|
81
|
+
ps = `Restart-Service -Name '${name.replace(/'/g, "''")}' -Force -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
|
|
82
|
+
break;
|
|
83
|
+
case 'enable':
|
|
84
|
+
ps = `Set-Service -Name '${name.replace(/'/g, "''")}' -StartupType Automatic -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
|
|
85
|
+
break;
|
|
86
|
+
case 'disable':
|
|
87
|
+
ps = `Set-Service -Name '${name.replace(/'/g, "''")}' -StartupType Disabled -ErrorAction Stop; Get-Service -Name '${name.replace(/'/g, "''")}' | Select-Object Name,Status,StartType | ConvertTo-Json -Compress`;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const result = await runPowerShell(ps, { timeout: 15000 });
|
|
92
|
+
if (result.exitCode !== 0) {
|
|
93
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const svc = JSON.parse(result.stdout);
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: 'text', text: `Service "${svc.Name}": ${action} → Status: ${svc.Status}, StartType: ${svc.StartType}` }],
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
*
|
|
4
|
+
* Tools: windows_startup_list (#33), windows_startup_manage (#34)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { runPowerShell } from '../shell.js';
|
|
10
|
+
|
|
11
|
+
export function registerStartupTools(server: McpServer): void {
|
|
12
|
+
server.tool(
|
|
13
|
+
'windows_startup_list',
|
|
14
|
+
'List all startup items from registry (Run/RunOnce), startup folder, and scheduled logon tasks.',
|
|
15
|
+
{},
|
|
16
|
+
async () => {
|
|
17
|
+
const ps = `
|
|
18
|
+
$items = [System.Collections.Generic.List[PSObject]]::new()
|
|
19
|
+
|
|
20
|
+
# Registry: HKCU Run
|
|
21
|
+
$regPaths = @(
|
|
22
|
+
@{ Path = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; Scope = 'User'; Source = 'Registry Run' }
|
|
23
|
+
@{ Path = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'; Scope = 'User'; Source = 'Registry RunOnce' }
|
|
24
|
+
@{ Path = 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'; Scope = 'System'; Source = 'Registry Run' }
|
|
25
|
+
@{ Path = 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce'; Scope = 'System'; Source = 'Registry RunOnce' }
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
foreach ($rp in $regPaths) {
|
|
29
|
+
if (Test-Path $rp.Path) {
|
|
30
|
+
$props = Get-ItemProperty -Path $rp.Path -ErrorAction SilentlyContinue
|
|
31
|
+
if ($props) {
|
|
32
|
+
$props.PSObject.Properties | Where-Object { $_.Name -notlike 'PS*' } | ForEach-Object {
|
|
33
|
+
$items.Add([PSCustomObject]@{
|
|
34
|
+
Name = $_.Name
|
|
35
|
+
Command = $_.Value
|
|
36
|
+
Source = $rp.Source
|
|
37
|
+
Scope = $rp.Scope
|
|
38
|
+
Enabled = $true
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Startup folder
|
|
46
|
+
$userStartup = [Environment]::GetFolderPath('Startup')
|
|
47
|
+
$commonStartup = [Environment]::GetFolderPath('CommonStartup')
|
|
48
|
+
foreach ($folder in @(@{Path=$userStartup;Scope='User'}, @{Path=$commonStartup;Scope='System'})) {
|
|
49
|
+
if (Test-Path $folder.Path) {
|
|
50
|
+
Get-ChildItem -Path $folder.Path -File -ErrorAction SilentlyContinue | ForEach-Object {
|
|
51
|
+
$items.Add([PSCustomObject]@{
|
|
52
|
+
Name = $_.BaseName
|
|
53
|
+
Command = $_.FullName
|
|
54
|
+
Source = 'Startup Folder'
|
|
55
|
+
Scope = $folder.Scope
|
|
56
|
+
Enabled = $true
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# Scheduled tasks at logon
|
|
63
|
+
Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object {
|
|
64
|
+
$_.Triggers | Where-Object { $_ -is [CimInstance] -and $_.CimClass.CimClassName -eq 'MSFT_TaskLogonTrigger' }
|
|
65
|
+
} | ForEach-Object {
|
|
66
|
+
$items.Add([PSCustomObject]@{
|
|
67
|
+
Name = $_.TaskName
|
|
68
|
+
Command = ($_.Actions | ForEach-Object { $_.Execute }) -join ' '
|
|
69
|
+
Source = 'Task Scheduler (Logon)'
|
|
70
|
+
Scope = 'System'
|
|
71
|
+
Enabled = $_.State -eq 'Ready'
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
$items | ConvertTo-Json -Depth 3 -Compress`;
|
|
76
|
+
|
|
77
|
+
const result = await runPowerShell(ps, { timeout: 20000 });
|
|
78
|
+
if (result.exitCode !== 0) {
|
|
79
|
+
return { content: [{ type: 'text', text: `Error: ${result.stderr}` }], isError: true };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!result.stdout) {
|
|
83
|
+
return { content: [{ type: 'text', text: 'No startup items found.' }] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const items = Array.isArray(JSON.parse(result.stdout)) ? JSON.parse(result.stdout) : [JSON.parse(result.stdout)];
|
|
87
|
+
const lines = items.map((i: { Name: string; Command: string; Source: string; Scope: string; Enabled: boolean }) => {
|
|
88
|
+
const status = i.Enabled ? '[ON] ' : '[OFF]';
|
|
89
|
+
return `${status} ${i.Scope.padEnd(6)} ${i.Source.padEnd(24)} ${i.Name.padEnd(30).slice(0, 30)} ${(i.Command || '').slice(0, 50)}`;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const header = `State ${'Scope'.padEnd(6)} ${'Source'.padEnd(24)} ${'Name'.padEnd(30)} Command`;
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: 'text', text: `${header}\n${'─'.repeat(110)}\n${lines.join('\n')}\n\n${items.length} startup items` }],
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
server.tool(
|
|
100
|
+
'windows_startup_manage',
|
|
101
|
+
'Add, remove, enable, or disable a startup item.',
|
|
102
|
+
{
|
|
103
|
+
action: z.enum(['add', 'remove', 'enable', 'disable']).describe('Action'),
|
|
104
|
+
name: z.string().describe('Startup item name'),
|
|
105
|
+
command: z.string().optional().describe('Command to run at startup (for add)'),
|
|
106
|
+
location: z.enum(['registry', 'startup_folder']).default('registry').describe('Where to add (for add)'),
|
|
107
|
+
},
|
|
108
|
+
async ({ action, name, command, location }) => {
|
|
109
|
+
let ps: string;
|
|
110
|
+
|
|
111
|
+
switch (action) {
|
|
112
|
+
case 'add':
|
|
113
|
+
if (!command) {
|
|
114
|
+
return { content: [{ type: 'text', text: 'Add requires a command.' }], isError: true };
|
|
115
|
+
}
|
|
116
|
+
if (location === 'startup_folder') {
|
|
117
|
+
ps = `
|
|
118
|
+
$startupPath = [Environment]::GetFolderPath('Startup')
|
|
119
|
+
$shortcutPath = Join-Path $startupPath '${name.replace(/'/g, "''")}.lnk'
|
|
120
|
+
$ws = New-Object -ComObject WScript.Shell
|
|
121
|
+
$sc = $ws.CreateShortcut($shortcutPath)
|
|
122
|
+
$sc.TargetPath = '${command.replace(/'/g, "''")}'
|
|
123
|
+
$sc.Save()
|
|
124
|
+
"Added startup shortcut: $shortcutPath"`;
|
|
125
|
+
} else {
|
|
126
|
+
ps = `
|
|
127
|
+
Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${name.replace(/'/g, "''")}' -Value '${command.replace(/'/g, "''")}' -ErrorAction Stop
|
|
128
|
+
"Added to HKCU Run: ${name}"`;
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'remove':
|
|
133
|
+
ps = `
|
|
134
|
+
$removed = $false
|
|
135
|
+
# Try registry
|
|
136
|
+
$regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
|
137
|
+
if ((Get-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue)) {
|
|
138
|
+
Remove-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop
|
|
139
|
+
$removed = $true
|
|
140
|
+
}
|
|
141
|
+
# Try startup folder
|
|
142
|
+
$startupPath = [Environment]::GetFolderPath('Startup')
|
|
143
|
+
$lnk = Join-Path $startupPath '${name.replace(/'/g, "''")}.lnk'
|
|
144
|
+
if (Test-Path $lnk) { Remove-Item $lnk -Force; $removed = $true }
|
|
145
|
+
if ($removed) { "Removed: ${name}" } else { "Not found: ${name}" }`;
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case 'disable':
|
|
149
|
+
ps = `
|
|
150
|
+
$regPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
|
151
|
+
$val = (Get-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue).'${name.replace(/'/g, "''")}'
|
|
152
|
+
if ($val) {
|
|
153
|
+
Remove-ItemProperty -Path $regPath -Name '${name.replace(/'/g, "''")}' -ErrorAction Stop
|
|
154
|
+
$disabledPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run_Disabled'
|
|
155
|
+
if (-not (Test-Path $disabledPath)) { New-Item -Path $disabledPath -Force | Out-Null }
|
|
156
|
+
Set-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -Value $val
|
|
157
|
+
"Disabled: ${name}"
|
|
158
|
+
} else { "Not found in registry Run: ${name}" }`;
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case 'enable':
|
|
162
|
+
ps = `
|
|
163
|
+
$disabledPath = 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run_Disabled'
|
|
164
|
+
$val = (Get-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue).'${name.replace(/'/g, "''")}'
|
|
165
|
+
if ($val) {
|
|
166
|
+
Set-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name '${name.replace(/'/g, "''")}' -Value $val
|
|
167
|
+
Remove-ItemProperty -Path $disabledPath -Name '${name.replace(/'/g, "''")}' -ErrorAction SilentlyContinue
|
|
168
|
+
"Enabled: ${name}"
|
|
169
|
+
} else { "Not found in disabled items: ${name}" }`;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = await runPowerShell(ps, { timeout: 10000 });
|
|
174
|
+
return {
|
|
175
|
+
content: [{ type: 'text', text: result.stdout || result.stderr }],
|
|
176
|
+
isError: result.exitCode !== 0,
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
}
|