PRA-only-demo/packages/rpa-core/src/capabilities/windows-system.ts
yu caa8e19332 阶段1-rpa-core建包: engine+31个纯Node能力迁入packages/rpa-core, 领域类型单一源化, createEngine工厂收拢模块级状态
- engine/(registry/types/runtime/expression)物理迁入包, 解耦shared/types(域类型归包, 宿主type-only re-export)
- 31个纯Node能力+27个测试迁入包; 宿主留守Electron/浏览器/AI依赖能力, index.ts经@rpa/core/capabilities拉起
- 新增createEngine()/defaultEngine: excel缓存/Excel句柄/db连接/FTP客户端/OCR实例/拟真设置挂引擎实例
- remaining-core子流程依赖改为host-bridge注入, 包内不再import宿主flows
- 宿主main/index.ts启动时注入子流程源; electron-vite/vitest/tsconfig配@rpa/core源码别名
- 验证: typecheck绿, 快照909个id diff为零, 332测试过(2个桌面冒烟因会话无交互SetForegroundWindow被拒, 环境阻塞)
2026-11-04 12:36:03 +08:00

115 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { register } from "../engine/registry";
import { RuntimeError } from "../engine/types";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
const ps = (script: string) => run("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 60000 });
// cmdkey 在中文 Windows 上输出 GBK,直接 execFile 按 UTF-8 解码会乱码;统一经 PowerShell 强制 UTF-8 控制台编码捕获
const cmdkey = async (args: string[]): Promise<string> => {
const quoted = args.map((item) => "'" + item.replace(/'/g, "''") + "'");
const script = "[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); & cmdkey " + quoted.join(" ");
const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script], { windowsHide: true, timeout: 60000 });
return stdout;
};
function reqText(args: Record<string, unknown>, keys: string[], label: string, nodeId: string): string {
for (const key of keys) {
const v = String(args[key] ?? "").trim();
if (v) return v;
}
throw new RuntimeError("validation_error", label + "为空", { nodeId, componentId: nodeId });
}
register("ScreenControl_Screenlock", async () => {
await run("rundll32.exe", ["user32.dll,LockWorkStation"], { windowsHide: true });
return { locked: true };
});
register("ScreenControl_ScreenUnlock", async () => {
// 仅唤醒屏幕/解除屏保;系统锁屏需人工输入密码,无法通用自动解锁
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern void mouse_event(uint f, uint dx, uint dy, uint d, UIntPtr e);' -Name W32U -Namespace WinRpa | Out-Null; [WinRpa.W32U]::mouse_event(0x0001, 1, 1, 0, [UIntPtr]::Zero); Start-Sleep -Milliseconds 200; [WinRpa.W32U]::mouse_event(0x0001, 0xFFFFFFFF, 0, 0, [UIntPtr]::Zero)");
return { awakened: true, loginUnlockSupported: false, note: "仅尝试唤醒屏幕;Windows 登录锁屏仍需本人手动解锁" };
});
const SC_SCREENSAVE = 0xF140;
const WM_SYSCOMMAND = 0x0112;
register(["System_ScreenSaverShow", "System_ScreenSaverShowV1"], async (_ctx, node, args) => {
if ([true, 1, "1", "true", "True"].includes(args.set_password as never)) {
throw new RuntimeError("validation_error", "系统屏保不支持设置自定义解锁密码,请关闭该选项并使用 Windows 原有登录保护", { nodeId: node.id, componentId: node.componentId });
}
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern IntPtr SendMessage(IntPtr h, uint m, IntPtr w, IntPtr l);' -Name W32S -Namespace WinRpa | Out-Null; [WinRpa.W32S]::SendMessage([IntPtr]0xFFFF, [uint32]0x0112, [IntPtr]0xF140, [IntPtr]::Zero) | Out-Null");
return { screensaver: true };
});
register("System_ScreenSaverStop", async () => {
await ps("Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern IntPtr FindWindow(string cls, string win); [DllImport(\"user32.dll\")] public static extern bool PostMessage(IntPtr h, uint m, IntPtr w, IntPtr l);' -Name W32P -Namespace WinRpa | Out-Null; $h = [WinRpa.W32P]::FindWindow('WindowsScreenSaverClass', $null); if ($h -ne [IntPtr]::Zero) { [WinRpa.W32P]::PostMessage($h, 0x0010, [IntPtr]::Zero, [IntPtr]::Zero) | Out-Null }");
return { stopped: true };
});
register("System_ReadCredential", async (ctx, node, args) => {
const app = reqText(args, ["application"], "凭据目标", node.id);
const stdout = await cmdkey(["/list"]);
const exists = stdout.includes(app);
// cmdkey /list 每条凭据是「目标/类型/用户」多行块(空行分隔);取整块才有用户名等有效信息
const lines = stdout.split(/\r?\n/);
const idx = lines.findIndex((l) => l.includes(app));
const block: string[] = [];
if (idx >= 0) {
for (let i = idx; i < lines.length && lines[i].trim(); i++) block.push(lines[i].trim());
}
return { target: app, exists, detail: exists ? block.join(" | ") : "" };
});
register("System_AddCredential", async (ctx, node, args) => {
const app = reqText(args, ["application"], "凭据目标", node.id);
const user = reqText(args, ["name"], "用户名", node.id);
const pwd = String(args.password ?? "");
const psArgs = pwd ? ["/generic:" + app, "/user:" + user, "/pass:" + pwd] : ["/generic:" + app, "/user:" + user];
await cmdkey(psArgs);
return { added: true, target: app };
});
register("System_DeleteCredential", async (ctx, node, args) => {
const app = reqText(args, ["application"], "凭据目标", node.id);
await cmdkey(["/delete:" + app]);
return { deleted: true, target: app };
});
register("System_ConnectRemoteDesktop", async (ctx, node, args) => {
const ip = reqText(args, ["ip"], "远程地址", node.id);
const port = String(args.port ?? "").trim();
const target = port ? ip + ":" + port : ip;
await run("mstsc.exe", ["/v:" + target], { windowsHide: true });
return { connected: true, target };
});
register("System_CloseRemoteDesktop", async () => {
await run("tsdiscon.exe", [], { windowsHide: true });
return { disconnected: true };
});
register("System_GetInputMethodState", async () => {
const { stdout } = await ps("Add-Type -AssemblyName System.Windows.Forms | Out-Null; $cur = [System.Windows.Forms.InputLanguage]::CurrentInputLanguage; Write-Output ($cur.Culture.Name + '|' + $cur.LayoutName)");
const [culture, layout] = stdout.split("|");
return { culture, layout };
});
register("System_SetInputMethodState", async (ctx, node, args) => {
const mode = reqText(args, ["mode"], "输入法文化名", node.id);
await ps("Add-Type -AssemblyName System.Windows.Forms | Out-Null; $lang = [System.Windows.Forms.InputLanguage]::FromCulture('" + mode.replace(/'/g, "''") + "'); if ($lang) { [System.Windows.Forms.InputLanguage]::CurrentInputLanguage = $lang }");
return { set: true, mode };
});
register("File_OpenFile", async (ctx, node, args) => {
const p = reqText(args, ["path"], "文件夹路径", node.id);
try {
await run("explorer.exe", [p], { windowsHide: true });
} catch (error) {
// explorer.exe 打开成功也会返回退出码 1(Windows 已知行为),仅路径不存在时才是真失败
const code = (error as { code?: unknown }).code;
if (code !== 1) throw error;
}
return { opened: true, path: p };
});