ai-lens 0.8.98 → 0.8.100
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/.commithash +1 -1
- package/CHANGELOG.md +6 -0
- package/cli/hooks.js +97 -61
- package/cli/status.js +26 -2
- package/client/ai-lens-hook.ps1 +39 -0
- package/package.json +1 -1
package/.commithash
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3ebf574
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
History of changes to the `ai-lens` CLI package on npm. New entries go on top. Format: `## X.Y.Z — YYYY-MM-DD`, followed by user-facing bullets.
|
|
4
4
|
|
|
5
|
+
## 0.8.100 — 2026-06-17
|
|
6
|
+
- fix: Claude Code hooks on Windows now actually capture. The previous windowless wrapper (`conhost.exe --headless`) hid the console window but silently swallowed the hook's stdin, so the event payload never reached AI Lens and nothing was recorded. Claude Code now uses the same windowless launcher as Cursor (`ai-lens-hook.ps1`), which both suppresses the window and delivers the payload. Existing installs migrate on the next `ai-lens init` / `/setup`. macOS/Linux unchanged. (The launcher was renamed from `cursor-ai-lens-hook.ps1` since it now serves both tools.)
|
|
7
|
+
|
|
8
|
+
## 0.8.99 — 2026-06-17
|
|
9
|
+
- fix: Cursor hooks on Windows no longer flash a console window on every event. The flash was `node.exe` (a console app) allocating its own window when Cursor runs the hook; a new windowless launcher (`cursor-ai-lens-hook.ps1`) starts node with no window and forwards the event to it, so capture keeps working with no flash. Existing Cursor installs migrate automatically on the next `ai-lens init` / `/setup`. macOS/Linux and Claude Code are unchanged
|
|
10
|
+
|
|
5
11
|
## 0.8.98 — 2026-06-17
|
|
6
12
|
- fix: Cursor hooks on Windows no longer use the `conhost.exe --headless` wrapper — it made Cursor report a "JSON Parse Error" on every hook (conhost emits terminal escape codes that Cursor tries to parse as the hook's output) and broke capture. Cursor returns to the plain working form; machines that briefly got the conhost'd Cursor hook are auto-downgraded on the next `ai-lens init`/`/setup`. Claude Code keeps the windowless wrapper (it tolerates the output). The minor Cursor console flash on Windows is upstream Cursor behaviour
|
|
7
13
|
|
package/cli/hooks.js
CHANGED
|
@@ -295,11 +295,14 @@ export function captureCommand(opts = {}) {
|
|
|
295
295
|
const clientDir = ctx.clientDir ?? CLIENT_INSTALL_DIR;
|
|
296
296
|
const nodeResolution = ctx.nodeResolution;
|
|
297
297
|
const isWin = platform === 'win32';
|
|
298
|
-
// Windows
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
|
|
298
|
+
// Windows windowless launcher (ai-lens-hook.ps1). Gated by the `windowless` opt, so
|
|
299
|
+
// it applies to CLAUDE forms only here — Cursor builds its own launcher form in
|
|
300
|
+
// cursorCaptureCommand, Codex never sets windowless. We DON'T use conhost.exe
|
|
301
|
+
// --headless: it hides the window but breaks capture (swallows node's stdin → the
|
|
302
|
+
// payload never arrives; and for Cursor it corrupts stdout). The launcher uses
|
|
303
|
+
// ProcessStartInfo + CreateNoWindow + RedirectStandardInput instead — windowless AND
|
|
304
|
+
// stdin-preserving — and works on all Windows (no ≥1809 requirement). Never off Windows.
|
|
305
|
+
const winLauncher = isWin && windowless;
|
|
303
306
|
// shell hint distinguishes cmd.exe (Claude Code) from PowerShell (Cursor) on
|
|
304
307
|
// Windows — the two need different escaping for paths with spaces.
|
|
305
308
|
const isPS = shell === 'powershell';
|
|
@@ -314,12 +317,14 @@ export function captureCommand(opts = {}) {
|
|
|
314
317
|
// POSIX shells use $VAR.
|
|
315
318
|
if (projectDirRelPath != null) {
|
|
316
319
|
const rel = projectDirRelPath.replace(/\\/g, '/').replace(/^\.?\/+/, '');
|
|
317
|
-
if (
|
|
318
|
-
// Windowless form
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
320
|
+
if (winLauncher) {
|
|
321
|
+
// Windowless form via the launcher. Uses $CLAUDE_PROJECT_DIR — Claude Code
|
|
322
|
+
// substitutes that variable itself before exec on every OS. The launcher ships
|
|
323
|
+
// next to capture.js, so it resolves under the same $CLAUDE_PROJECT_DIR path.
|
|
324
|
+
// node stays bare (committed/machine-agnostic form) — the launcher's
|
|
325
|
+
// ProcessStartInfo resolves it via PATH (Claude Code requires node on PATH).
|
|
326
|
+
const launcherRel = rel.replace(/[^/]*$/, 'ai-lens-hook.ps1');
|
|
327
|
+
return `powershell -NoProfile -ExecutionPolicy Bypass -File "$CLAUDE_PROJECT_DIR/${launcherRel}" node "$CLAUDE_PROJECT_DIR/${rel}"`;
|
|
323
328
|
}
|
|
324
329
|
const dir = isWin ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
|
|
325
330
|
return `node "${dir}/${rel}"`;
|
|
@@ -342,12 +347,15 @@ export function captureCommand(opts = {}) {
|
|
|
342
347
|
: shellEscape(capturePath, platform);
|
|
343
348
|
const nodeNorm = nodeResolution.path.replace(/\\/g, '/');
|
|
344
349
|
|
|
345
|
-
// Windowless (Claude
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
|
|
350
|
+
// Windowless (Claude on Windows): run node through the ai-lens-hook.ps1 launcher,
|
|
351
|
+
// which starts node with CreateNoWindow + forwards stdin (conhost would swallow it).
|
|
352
|
+
// Claude Code spawns powershell to run the launcher; the payload arrives on the
|
|
353
|
+
// process's OS stdin, which the launcher reads via [Console]::In. The launcher ships
|
|
354
|
+
// next to capture.js. node + capture + launcher all double-quoted (may contain spaces).
|
|
355
|
+
if (winLauncher) {
|
|
356
|
+
const q = (s) => `"${s.replace(/"/g, '""')}"`;
|
|
357
|
+
const launcherAbs = capturePath.replace(/[^/]*$/, 'ai-lens-hook.ps1');
|
|
358
|
+
return `powershell -NoProfile -ExecutionPolicy Bypass -File ${q(launcherAbs)} ${q(nodeNorm)} ${q(capturePath)}`;
|
|
351
359
|
}
|
|
352
360
|
if (isWin && rawPath && nodeNorm.includes(' ')) {
|
|
353
361
|
const quotedNode = `"${nodeNorm.replace(/"/g, '""')}"`;
|
|
@@ -369,24 +377,35 @@ export function cursorCaptureCommand(opts = {}) {
|
|
|
369
377
|
const { useTilde = false, customPath = null } = opts;
|
|
370
378
|
const ctx = opts.ctx ?? resolveDefaultCtx();
|
|
371
379
|
const platform = ctx.platform ?? process.platform;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
380
|
+
|
|
381
|
+
if (platform === 'win32') {
|
|
382
|
+
// Windows: route node through the windowless launcher ai-lens-hook.ps1.
|
|
383
|
+
// Cursor spawns hooks via a hidden powershell and pipes the payload through the
|
|
384
|
+
// PS pipeline ($input); launching node.exe directly there pops a console window
|
|
385
|
+
// on every event (node is console-subsystem, parent powershell has no console).
|
|
386
|
+
// conhost.exe --headless hides the window but corrupts Cursor's JSON-parse of hook
|
|
387
|
+
// stdout with ConPTY VT codes (verified). The launcher starts node with
|
|
388
|
+
// CreateNoWindow=$true and forwards $input → node stdin: no window, clean stdout.
|
|
389
|
+
// Form: & "<...>/ai-lens-hook.ps1" "<absNode>" "<capture.js>"
|
|
390
|
+
const clientDir = (ctx.clientDir ?? CLIENT_INSTALL_DIR).replace(/\\/g, '/');
|
|
391
|
+
const nodeResolution = ctx.nodeResolution;
|
|
392
|
+
if (!nodeResolution || !nodeResolution.path) {
|
|
393
|
+
throw new Error('cursorCaptureCommand: nodeResolution is required');
|
|
394
|
+
}
|
|
395
|
+
const node = nodeResolution.path.replace(/\\/g, '/');
|
|
396
|
+
const capturePath = (customPath ?? `${clientDir}/capture.js`).replace(/\\/g, '/');
|
|
397
|
+
// The launcher ships next to capture.js (installed client dir, or the repo client
|
|
398
|
+
// dir for --use-repo-path) — derive its path from capturePath's directory.
|
|
399
|
+
const launcher = capturePath.replace(/[^/]*$/, 'ai-lens-hook.ps1');
|
|
400
|
+
const q = (s) => `"${s.replace(/"/g, '""')}"`;
|
|
401
|
+
return `& ${q(launcher)} ${q(node)} ${q(capturePath)}`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// macOS / Linux: direct node, unchanged. (No console-window concept; conhost/launcher
|
|
405
|
+
// are Windows-only.)
|
|
406
|
+
return customPath != null
|
|
407
|
+
? captureCommand({ rawPath: true, customPath, ctx })
|
|
408
|
+
: captureCommand({ useTilde, ctx });
|
|
390
409
|
}
|
|
391
410
|
|
|
392
411
|
// ---------------------------------------------------------------------------
|
|
@@ -401,7 +420,10 @@ export function cursorCaptureCommand(opts = {}) {
|
|
|
401
420
|
* every hook with ERR_MODULE_NOT_FOUND on a missing sibling import.
|
|
402
421
|
*/
|
|
403
422
|
export function listClientFiles(sourceDir = join(__dirname, '..', 'client')) {
|
|
404
|
-
|
|
423
|
+
// .js — all client modules (sibling imports must all be present).
|
|
424
|
+
// .ps1 — the Windows windowless hook launcher (ai-lens-hook.ps1), used by Cursor AND
|
|
425
|
+
// Claude Code; a missing launcher would break every Windows hook, so ship it too.
|
|
426
|
+
return readdirSync(sourceDir).filter(f => f.endsWith('.js') || f.endsWith('.ps1')).sort();
|
|
405
427
|
}
|
|
406
428
|
|
|
407
429
|
/**
|
|
@@ -796,6 +818,7 @@ export function _parseHookCommand(cmd) {
|
|
|
796
818
|
return { kind: 'launcher', path: first, prefix, nodePrefix: null };
|
|
797
819
|
}
|
|
798
820
|
|
|
821
|
+
|
|
799
822
|
if (tokens.length >= 2) {
|
|
800
823
|
const second = normalizePath(tokens[1]);
|
|
801
824
|
if (second.endsWith('capture.js')) {
|
|
@@ -853,8 +876,17 @@ function normalizePath(p) {
|
|
|
853
876
|
// still classifies run.* as ai-lens so `remove`/strip cleans up stale launchers.)
|
|
854
877
|
export function isGuiSafeHookCommand(cmd) {
|
|
855
878
|
if (!isAiLensCommand(cmd).isAiLens) return false;
|
|
856
|
-
//
|
|
857
|
-
//
|
|
879
|
+
// Our Windows windowless launcher (ai-lens-hook.ps1) is GUI-safe by construction — it
|
|
880
|
+
// starts node with CreateNoWindow. Matched by substring so it covers BOTH invocations:
|
|
881
|
+
// Cursor `& "<...>/ai-lens-hook.ps1" <node> <capture>` and Claude
|
|
882
|
+
// `powershell ... -File "<...>/ai-lens-hook.ps1" <node> <capture>` (also the older
|
|
883
|
+
// cursor-ai-lens-hook.ps1 name). The node arg's existence is validated in status.js.
|
|
884
|
+
const n = (cmd || '').replace(/\\/g, '/');
|
|
885
|
+
if (/ai-lens-hook\.ps1/i.test(n)) return true;
|
|
886
|
+
// Direct capture.js form: GUI-safe only with an ABSOLUTE node path baked in. Bare
|
|
887
|
+
// `node` / `/usr/bin/env node` are NOT GUI-safe (depend on PATH, which GUI
|
|
888
|
+
// Cursor/Claude often lack). The old run.sh/run.cmd launcher is NOT recognised here
|
|
889
|
+
// (ADR 0003) — it stays `outdated` so init migrates it.
|
|
858
890
|
const p = _parseHookCommand(cmd);
|
|
859
891
|
if (p.kind === 'captureJs' && p.nodePrefix) {
|
|
860
892
|
const np = p.nodePrefix;
|
|
@@ -894,11 +926,12 @@ function isAcceptableHookCommand(cmd) {
|
|
|
894
926
|
export function isWrongPlatformProjectDirCommand(cmd, platform = process.platform) {
|
|
895
927
|
if (!isClaudeProjectDirCommand(cmd)) return false;
|
|
896
928
|
const n = (cmd || '').replace(/\\/g, '/');
|
|
897
|
-
// The
|
|
898
|
-
//
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
|
|
929
|
+
// The Windows windowless launcher form (powershell … -File "$CLAUDE_PROJECT_DIR/…
|
|
930
|
+
// ai-lens-hook.ps1" …) and the older conhost.exe --headless form are unambiguously
|
|
931
|
+
// Windows commands. Both deliberately use $CLAUDE_PROJECT_DIR — Claude Code substitutes
|
|
932
|
+
// that itself before exec, so it expands on Windows. They are correct on win32 and
|
|
933
|
+
// wrong (won't run) anywhere else; the %VAR%/$VAR rule below doesn't apply to them.
|
|
934
|
+
if (/ai-lens-hook\.ps1/i.test(n) || isConhostHeadlessCommand(n)) return platform !== 'win32';
|
|
902
935
|
const correctVar = platform === 'win32' ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
|
|
903
936
|
return !n.includes(correctVar);
|
|
904
937
|
}
|
|
@@ -1093,28 +1126,31 @@ function isCurrentAiLensHook(entry, expected, opts = {}) {
|
|
|
1093
1126
|
// $CLAUDE_PROJECT_DIR/%CLAUDE_PROJECT_DIR% hook written for the OTHER OS won't
|
|
1094
1127
|
// expand on this platform, so flag it outdated to let init rewrite it.
|
|
1095
1128
|
//
|
|
1096
|
-
//
|
|
1097
|
-
// its stored
|
|
1098
|
-
//
|
|
1099
|
-
//
|
|
1100
|
-
//
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
//
|
|
1108
|
-
// Cursor, macOS, and old Windows produce a non-conhost `expected`, and only Claude
|
|
1109
|
-
// on conhost-capable Windows produces a conhost `expected`. Committed (tracked)
|
|
1110
|
-
// files are exempt via allowPlatformRewrite — they carry one OS-agnostic syntax
|
|
1111
|
-
// and can't bake a Windows-only wrapper; the per-machine overlay provides it.
|
|
1129
|
+
// Windows-wrapper exception (same allowPlatformRewrite gate): flag a hook outdated
|
|
1130
|
+
// when its stored windowless wrapper DISAGREES with the freshly regenerated `expected`
|
|
1131
|
+
// command, so init re-syncs it. We compare ONLY the wrapper kind — `conhost` vs the
|
|
1132
|
+
// `ai-lens-hook.ps1` launcher (keyed on its basename) vs `plain` — never the whole
|
|
1133
|
+
// command, so legitimate path/node/install-mode variation never false-flags.
|
|
1134
|
+
// `expected` bakes in tool+platform: on Windows BOTH Cursor and Claude now use the
|
|
1135
|
+
// launcher (`ps1:ai-lens-hook.ps1`); Codex, macOS → `plain`. So this self-scopes:
|
|
1136
|
+
// - conhost (old Claude/Cursor) → mismatch → outdated → migrate to the launcher;
|
|
1137
|
+
// - old `cursor-ai-lens-hook.ps1` → `ps1:cursor-ai-lens-hook.ps1` ≠ `ps1:ai-lens-hook.ps1`
|
|
1138
|
+
// → outdated → migrate to the renamed launcher;
|
|
1139
|
+
// - launcher already current → match → current; Codex/macOS plain → current.
|
|
1140
|
+
// Committed (tracked) files are exempt via allowPlatformRewrite.
|
|
1112
1141
|
const { platform = process.platform, allowPlatformRewrite = false } = opts;
|
|
1113
1142
|
const expectedCmd = expected?.command ?? expected?.hooks?.[0]?.command ?? '';
|
|
1114
|
-
const
|
|
1143
|
+
const wrapperKind = (c) => {
|
|
1144
|
+
const s = String(c || '');
|
|
1145
|
+
if (isConhostHeadlessCommand(s)) return 'conhost';
|
|
1146
|
+
const m = s.match(/([\w.-]*ai-lens-hook\.ps1)/i);
|
|
1147
|
+
if (m) return 'ps1:' + m[1].toLowerCase();
|
|
1148
|
+
return 'plain';
|
|
1149
|
+
};
|
|
1150
|
+
const expectedWrapper = wrapperKind(expectedCmd);
|
|
1115
1151
|
const ok = (cmd) => isAcceptableHookCommand(cmd)
|
|
1116
1152
|
&& !(allowPlatformRewrite && isWrongPlatformProjectDirCommand(cmd, platform))
|
|
1117
|
-
&& !(allowPlatformRewrite &&
|
|
1153
|
+
&& !(allowPlatformRewrite && wrapperKind(cmd) !== expectedWrapper);
|
|
1118
1154
|
// Flat format (Cursor): single command per entry.
|
|
1119
1155
|
if (entry?.command != null) {
|
|
1120
1156
|
return ok(entry.command);
|
package/cli/status.js
CHANGED
|
@@ -150,8 +150,32 @@ function validateHookCommandPaths(tool) {
|
|
|
150
150
|
|
|
151
151
|
const issues = [];
|
|
152
152
|
const launcherMatch = command.match(/["']([^"']*run\.(?:sh|cmd))["']|(\S*run\.(?:sh|cmd))/);
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
// Matches both ai-lens-hook.ps1 and the older cursor-ai-lens-hook.ps1.
|
|
154
|
+
const ps1Match = command.match(/["']([^"']*ai-lens-hook\.ps1)["']|(\S*ai-lens-hook\.ps1)/);
|
|
155
|
+
// Paths containing $CLAUDE_PROJECT_DIR / %CLAUDE_PROJECT_DIR% can't be resolved here
|
|
156
|
+
// (Claude Code substitutes the var at runtime) — skip existence checks for those.
|
|
157
|
+
const resolvable = (p) => !/CLAUDE_PROJECT_DIR/.test(p);
|
|
158
|
+
|
|
159
|
+
if (ps1Match) {
|
|
160
|
+
// Windows windowless launcher (Cursor: `& "<...>.ps1" <node> <capture>`; Claude:
|
|
161
|
+
// `powershell ... -File "<...>.ps1" <node> <capture>`). Validate launcher + node arg
|
|
162
|
+
// + capture.js exist (when their paths are resolvable, i.e. not $CLAUDE_PROJECT_DIR).
|
|
163
|
+
const ps1Path = expandTilde(ps1Match[1] || ps1Match[2]);
|
|
164
|
+
if (resolvable(ps1Path) && !existsSync(ps1Path)) issues.push(`AI Lens hook launcher not found at: ${ps1Path}`);
|
|
165
|
+
const capMatch = command.match(/["']([^"']*capture\.js)["']|(\S*capture\.js)/);
|
|
166
|
+
if (capMatch) {
|
|
167
|
+
const capturePath = expandTilde(capMatch[1] || capMatch[2]);
|
|
168
|
+
if (resolvable(capturePath) && !existsSync(capturePath)) issues.push(`capture.js not found at: ${capturePath}`);
|
|
169
|
+
}
|
|
170
|
+
// node = the launcher's first argument (token right after the .ps1 path)
|
|
171
|
+
const nodeArg = command.match(/ai-lens-hook\.ps1["']?\s+(?:["']([^"']+)["']|(\S+))/);
|
|
172
|
+
if (nodeArg) {
|
|
173
|
+
const nodePath = expandTilde(nodeArg[1] || nodeArg[2]);
|
|
174
|
+
if (nodePath !== 'node' && resolvable(nodePath) && !existsSync(nodePath)) {
|
|
175
|
+
issues.push(`node not found at: ${nodePath} — re-run \`ai-lens init\` to re-resolve node`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (launcherMatch) {
|
|
155
179
|
// Launcher form (0.8.68+): one path, no separate node validation — the node binary
|
|
156
180
|
// baked into the launcher is invoked from inside the script, not the hook command.
|
|
157
181
|
const launcherPath = expandTilde(launcherMatch[1] || launcherMatch[2]);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# AI Lens — windowless hook launcher (Windows only). Used by BOTH Cursor and Claude Code.
|
|
2
|
+
#
|
|
3
|
+
# Why this exists: a hook spawns node.exe, a console-subsystem binary; launched from a
|
|
4
|
+
# console-less parent it allocates its own console window → a flash on every event. The
|
|
5
|
+
# obvious windowless wrapper, `conhost.exe --headless`, is unusable for hook capture:
|
|
6
|
+
# - for Cursor it emits ConPTY VT escape codes onto stdout, which Cursor fails to JSON-parse;
|
|
7
|
+
# - for Claude Code it replaces node's stdin with the pseudoconsole, so the JSON payload
|
|
8
|
+
# never reaches capture.js (it reads empty stdin and drops the event).
|
|
9
|
+
# This launcher instead starts node via ProcessStartInfo with CreateNoWindow=$true (no
|
|
10
|
+
# window) and RedirectStandardInput (real stdin forwarded) — so capture works AND there's
|
|
11
|
+
# no flash, and nothing extra is written to stdout.
|
|
12
|
+
#
|
|
13
|
+
# Payload source differs by caller, so read BOTH:
|
|
14
|
+
# - Cursor pipes it through the PowerShell pipeline → $input.
|
|
15
|
+
# - Claude Code pipes it to the process's OS stdin → [Console]::In.
|
|
16
|
+
# Read $input first; if empty (Claude Code invocation), fall back to [Console]::In.
|
|
17
|
+
#
|
|
18
|
+
# Args: $args[0] = node path, $args[1] = capture.js path.
|
|
19
|
+
|
|
20
|
+
$ErrorActionPreference = 'Stop'
|
|
21
|
+
$node = $args[0]
|
|
22
|
+
$capture = $args[1]
|
|
23
|
+
|
|
24
|
+
$payload = @($input) -join "`n"
|
|
25
|
+
if ([string]::IsNullOrEmpty($payload)) {
|
|
26
|
+
$payload = [Console]::In.ReadToEnd()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
30
|
+
$psi.FileName = $node
|
|
31
|
+
$psi.Arguments = '"' + $capture + '"'
|
|
32
|
+
$psi.UseShellExecute = $false
|
|
33
|
+
$psi.CreateNoWindow = $true
|
|
34
|
+
$psi.RedirectStandardInput = $true
|
|
35
|
+
|
|
36
|
+
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
37
|
+
$proc.StandardInput.Write($payload)
|
|
38
|
+
$proc.StandardInput.Close()
|
|
39
|
+
$proc.WaitForExit()
|