ai-lens 0.8.99 → 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 CHANGED
@@ -1 +1 @@
1
- b23bf01
1
+ 3ebf574
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
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
+
5
8
  ## 0.8.99 — 2026-06-17
6
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
7
10
 
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-only windowless wrapper (see supportsConhostHeadless). Gated by the
299
- // `windowless` opt so it's applied to CLAUDE forms only — Cursor/Codex never set it
300
- // (no reported flash + unverified conhost+stdin for those tools; ADR 0003 §3.2).
301
- // A build < 1809 falls back to the bare form. Never applied off Windows.
302
- const conhost = isWin && windowless && (ctx.conhost ?? supportsConhostHeadless());
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 (conhost) {
318
- // Windowless form. conhost is the launched image (no cmd.exe shell), so the
319
- // path must use $CLAUDE_PROJECT_DIR Claude Code substitutes that variable
320
- // itself before exec on every OS (the %VAR% form would survive unexpanded
321
- // since there's no shell to expand it). Verified on Windows 11 / cc 2.1.177.
322
- return `conhost.exe --headless node "$CLAUDE_PROJECT_DIR/${rel}"`;
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 + Windows ≥ 1809): conhost.exe is the launched image and runs
346
- // node directly, so it needs neither the cmd.exe `call` nor the PowerShell `&`
347
- // wrapper. stdin is forwarded (verified). The node path is always double-quoted on
348
- // Windows (it can contain spaces, e.g. C:/Program Files/nodejs/node.exe).
349
- if (conhost) {
350
- return `conhost.exe --headless "${nodeNorm.replace(/"/g, '""')}" ${pathPart}`;
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, '""')}"`;
@@ -371,14 +379,14 @@ export function cursorCaptureCommand(opts = {}) {
371
379
  const platform = ctx.platform ?? process.platform;
372
380
 
373
381
  if (platform === 'win32') {
374
- // Windows: route node through the windowless launcher cursor-ai-lens-hook.ps1.
382
+ // Windows: route node through the windowless launcher ai-lens-hook.ps1.
375
383
  // Cursor spawns hooks via a hidden powershell and pipes the payload through the
376
384
  // PS pipeline ($input); launching node.exe directly there pops a console window
377
385
  // on every event (node is console-subsystem, parent powershell has no console).
378
386
  // conhost.exe --headless hides the window but corrupts Cursor's JSON-parse of hook
379
387
  // stdout with ConPTY VT codes (verified). The launcher starts node with
380
388
  // CreateNoWindow=$true and forwards $input → node stdin: no window, clean stdout.
381
- // Form: & "<...>/cursor-ai-lens-hook.ps1" "<absNode>" "<capture.js>"
389
+ // Form: & "<...>/ai-lens-hook.ps1" "<absNode>" "<capture.js>"
382
390
  const clientDir = (ctx.clientDir ?? CLIENT_INSTALL_DIR).replace(/\\/g, '/');
383
391
  const nodeResolution = ctx.nodeResolution;
384
392
  if (!nodeResolution || !nodeResolution.path) {
@@ -388,7 +396,7 @@ export function cursorCaptureCommand(opts = {}) {
388
396
  const capturePath = (customPath ?? `${clientDir}/capture.js`).replace(/\\/g, '/');
389
397
  // The launcher ships next to capture.js (installed client dir, or the repo client
390
398
  // dir for --use-repo-path) — derive its path from capturePath's directory.
391
- const launcher = capturePath.replace(/[^/]*$/, 'cursor-ai-lens-hook.ps1');
399
+ const launcher = capturePath.replace(/[^/]*$/, 'ai-lens-hook.ps1');
392
400
  const q = (s) => `"${s.replace(/"/g, '""')}"`;
393
401
  return `& ${q(launcher)} ${q(node)} ${q(capturePath)}`;
394
402
  }
@@ -413,8 +421,8 @@ export function cursorCaptureCommand(opts = {}) {
413
421
  */
414
422
  export function listClientFiles(sourceDir = join(__dirname, '..', 'client')) {
415
423
  // .js — all client modules (sibling imports must all be present).
416
- // .ps1 — the Windows windowless Cursor hook launcher (cursor-ai-lens-hook.ps1);
417
- // a missing launcher would break every Cursor hook on Windows, so ship it too.
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.
418
426
  return readdirSync(sourceDir).filter(f => f.endsWith('.js') || f.endsWith('.ps1')).sort();
419
427
  }
420
428
 
@@ -810,16 +818,6 @@ export function _parseHookCommand(cmd) {
810
818
  return { kind: 'launcher', path: first, prefix, nodePrefix: null };
811
819
  }
812
820
 
813
- // Windows windowless Cursor launcher: & "<...>/cursor-ai-lens-hook.ps1" "<node>" "<capture.js>".
814
- // node is the SECOND token (the launcher's first arg); capture.js is the third.
815
- if (first.endsWith('cursor-ai-lens-hook.ps1')) {
816
- return {
817
- kind: 'cursorWindowless',
818
- path: first,
819
- prefix,
820
- nodePrefix: tokens.length >= 2 ? normalizePath(tokens[1]) : null,
821
- };
822
- }
823
821
 
824
822
  if (tokens.length >= 2) {
825
823
  const second = normalizePath(tokens[1]);
@@ -878,14 +876,19 @@ function normalizePath(p) {
878
876
  // still classifies run.* as ai-lens so `remove`/strip cleans up stale launchers.)
879
877
  export function isGuiSafeHookCommand(cmd) {
880
878
  if (!isAiLensCommand(cmd).isAiLens) return false;
881
- // GUI-safe when an ABSOLUTE node path is baked ineither the direct capture.js form
882
- // (`<absNode> capture.js`) or the Windows windowless launcher form
883
- // (`& "<...>/cursor-ai-lens-hook.ps1" "<absNode>" "<capture.js>"`), where node is the
884
- // launcher's first arg. Bare `node` / `/usr/bin/env node` are NOT GUI-safe (depend on
885
- // PATH, which GUI Cursor/Claude often lack). The old run.sh/run.cmd launcher is NOT
886
- // recognised here (ADR 0003) — it stays `outdated` so init migrates it.
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.
887
890
  const p = _parseHookCommand(cmd);
888
- if ((p.kind === 'captureJs' || p.kind === 'cursorWindowless') && p.nodePrefix) {
891
+ if (p.kind === 'captureJs' && p.nodePrefix) {
889
892
  const np = p.nodePrefix;
890
893
  const isAbsolute = np.startsWith('/') || /^[a-zA-Z]:\//.test(np);
891
894
  const isEnvShim = np === '/usr/bin/env' || np.endsWith('/env');
@@ -923,11 +926,12 @@ function isAcceptableHookCommand(cmd) {
923
926
  export function isWrongPlatformProjectDirCommand(cmd, platform = process.platform) {
924
927
  if (!isClaudeProjectDirCommand(cmd)) return false;
925
928
  const n = (cmd || '').replace(/\\/g, '/');
926
- // The conhost windowless form is unambiguously a Windows command (conhost.exe is
927
- // Windows-only) and deliberately uses $CLAUDE_PROJECT_DIR Claude Code substitutes
928
- // that itself, so it expands on Windows too. It is therefore correct on win32 and
929
- // wrong (won't run) anywhere else; the %VAR% rule below doesn't apply to it.
930
- if (isConhostHeadlessCommand(n)) return platform !== 'win32';
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';
931
935
  const correctVar = platform === 'win32' ? '%CLAUDE_PROJECT_DIR%' : '$CLAUDE_PROJECT_DIR';
932
936
  return !n.includes(correctVar);
933
937
  }
@@ -1124,24 +1128,23 @@ function isCurrentAiLensHook(entry, expected, opts = {}) {
1124
1128
  //
1125
1129
  // Windows-wrapper exception (same allowPlatformRewrite gate): flag a hook outdated
1126
1130
  // when its stored windowless wrapper DISAGREES with the freshly regenerated `expected`
1127
- // command, so init re-syncs it. We compare ONLY the wrapper kind — conhost vs the
1128
- // Cursor `cursor-ai-lens-hook.ps1` launcher vs plain — never the whole command, so
1129
- // legitimate path/node/install-mode variation never false-flags. `expected` already
1130
- // bakes in tool+platform: Claude on conhost-capable Windows `conhost`; Cursor on
1131
- // Windows `cursorPs1` (the windowless launcher; conhost corrupts Cursor's stdout
1132
- // JSON-parse); Codex, macOS, and old Windows`plain`. So this self-scopes per tool:
1133
- // - Claude: stored plain outdated (add conhost); stored conhost → current.
1134
- // - Cursor: stored plain/conhost outdated (migrate to the .ps1 launcher); ps1 → current.
1135
- // - Codex/macOS: expected plain, stored plain → current (untouched).
1136
- // Committed (tracked) files are exempt via allowPlatformRewrite — they carry one
1137
- // OS-agnostic syntax and can't bake a Windows-only wrapper; the per-machine overlay
1138
- // (or the local .cursor/hooks.json) provides it.
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) mismatchoutdated 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.
1139
1141
  const { platform = process.platform, allowPlatformRewrite = false } = opts;
1140
1142
  const expectedCmd = expected?.command ?? expected?.hooks?.[0]?.command ?? '';
1141
1143
  const wrapperKind = (c) => {
1142
1144
  const s = String(c || '');
1143
1145
  if (isConhostHeadlessCommand(s)) return 'conhost';
1144
- if (/cursor-ai-lens-hook\.ps1/i.test(s)) return 'cursorPs1';
1146
+ const m = s.match(/([\w.-]*ai-lens-hook\.ps1)/i);
1147
+ if (m) return 'ps1:' + m[1].toLowerCase();
1145
1148
  return 'plain';
1146
1149
  };
1147
1150
  const expectedWrapper = wrapperKind(expectedCmd);
package/cli/status.js CHANGED
@@ -150,24 +150,28 @@ function validateHookCommandPaths(tool) {
150
150
 
151
151
  const issues = [];
152
152
  const launcherMatch = command.match(/["']([^"']*run\.(?:sh|cmd))["']|(\S*run\.(?:sh|cmd))/);
153
- const ps1Match = command.match(/["']([^"']*cursor-ai-lens-hook\.ps1)["']|(\S*cursor-ai-lens-hook\.ps1)/);
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);
154
158
 
155
159
  if (ps1Match) {
156
- // Windows windowless Cursor launcher: & "<...>/cursor-ai-lens-hook.ps1" "<node>" "<capture.js>".
157
- // Validate the launcher, the node arg (2nd token), and capture.js (3rd token) all exist.
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).
158
163
  const ps1Path = expandTilde(ps1Match[1] || ps1Match[2]);
159
- if (!existsSync(ps1Path)) issues.push(`Cursor hook launcher not found at: ${ps1Path}`);
164
+ if (resolvable(ps1Path) && !existsSync(ps1Path)) issues.push(`AI Lens hook launcher not found at: ${ps1Path}`);
160
165
  const capMatch = command.match(/["']([^"']*capture\.js)["']|(\S*capture\.js)/);
161
166
  if (capMatch) {
162
167
  const capturePath = expandTilde(capMatch[1] || capMatch[2]);
163
- if (!existsSync(capturePath)) issues.push(`capture.js not found at: ${capturePath}`);
168
+ if (resolvable(capturePath) && !existsSync(capturePath)) issues.push(`capture.js not found at: ${capturePath}`);
164
169
  }
165
170
  // node = the launcher's first argument (token right after the .ps1 path)
166
- const nodeArg = command.replace(/^& /, '')
167
- .match(/cursor-ai-lens-hook\.ps1["']?\s+(?:["']([^"']+)["']|(\S+))/);
171
+ const nodeArg = command.match(/ai-lens-hook\.ps1["']?\s+(?:["']([^"']+)["']|(\S+))/);
168
172
  if (nodeArg) {
169
173
  const nodePath = expandTilde(nodeArg[1] || nodeArg[2]);
170
- if (nodePath !== 'node' && !existsSync(nodePath)) {
174
+ if (nodePath !== 'node' && resolvable(nodePath) && !existsSync(nodePath)) {
171
175
  issues.push(`node not found at: ${nodePath} — re-run \`ai-lens init\` to re-resolve node`);
172
176
  }
173
177
  }
@@ -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()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.99",
3
+ "version": "0.8.100",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {
@@ -1,32 +0,0 @@
1
- # AI Lens — Cursor windowless hook launcher (Windows only).
2
- #
3
- # Why this exists: Cursor runs a hook by spawning a (hidden) powershell that pipes the
4
- # event payload through the PowerShell pipeline ($input) — e.g.
5
- # Get-Content <tmp>.json -Raw | & { $input | & "<this.ps1>" "<node>" "<capture.js>" }
6
- # Launching node.exe directly there pops a console window on every event (node is a
7
- # console-subsystem binary and the parent powershell has no console). conhost.exe
8
- # --headless hides the window but emits ConPTY VT escape codes onto stdout, which Cursor
9
- # then fails to JSON-parse. This launcher instead starts node with CreateNoWindow=$true
10
- # (no window) while forwarding the payload to node's stdin — so capture still works and
11
- # nothing extra is written to stdout.
12
- #
13
- # Args: $args[0] = absolute node path, $args[1] = capture.js path.
14
- # Payload arrives on the PowerShell pipeline as $input (NOT [Console]::In, which is empty
15
- # here because the data is an in-process pipeline object, not the process's OS stdin).
16
-
17
- $ErrorActionPreference = 'Stop'
18
- $node = $args[0]
19
- $capture = $args[1]
20
- $payload = @($input) -join "`n"
21
-
22
- $psi = New-Object System.Diagnostics.ProcessStartInfo
23
- $psi.FileName = $node
24
- $psi.Arguments = '"' + $capture + '"'
25
- $psi.UseShellExecute = $false
26
- $psi.CreateNoWindow = $true
27
- $psi.RedirectStandardInput = $true
28
-
29
- $proc = [System.Diagnostics.Process]::Start($psi)
30
- $proc.StandardInput.Write($payload)
31
- $proc.StandardInput.Close()
32
- $proc.WaitForExit()