dsh-winrm 0.1.1 → 0.3.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/README.md +14 -7
- package/README.zh.md +14 -7
- package/lib/client.js +73 -73
- package/lib/index.js +1184 -134
- package/lib/types/engine/client.d.ts +53 -6
- package/lib/types/engine/wsman.d.ts +153 -0
- package/lib/types/engine.d.ts +4 -4
- package/lib/types/powershell.d.ts +194 -27
- package/lib/types/smb.d.ts +24 -5
- package/package.json +17 -16
- package/scripts/verify-live.mts +116 -0
- package/src/engine/client.ts +460 -24
- package/src/engine/wsman.ts +357 -0
- package/src/engine.ts +104 -40
- package/src/powershell.ts +491 -60
- package/src/smb.ts +57 -5
- package/src/tools.ts +2 -2
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Live verification against a real WinRM host — exercises the stdin channel,
|
|
2
|
+
// the labelled envelope, the exit-code semantics, and a full transfer round
|
|
3
|
+
// trip. Unlike the unit tests this needs a reachable host, so it is a
|
|
4
|
+
// maintainer tool rather than part of `npm test`.
|
|
5
|
+
//
|
|
6
|
+
// Run from the repo root so `winrm-client` resolves:
|
|
7
|
+
// node --experimental-strip-types scripts/verify-live.mts
|
|
8
|
+
import { HostStore } from '../src/store.ts'
|
|
9
|
+
import { connOf, runScript, TRANSFER_CHUNK, uploadBuffer, downloadChunks, remoteFileSize } from '../src/engine/client.ts'
|
|
10
|
+
import { psWriteChunk, psReadChunk, psFileSize } from '../src/powershell.ts'
|
|
11
|
+
|
|
12
|
+
const store = new HostStore()
|
|
13
|
+
const entry = store.list()[0]
|
|
14
|
+
if (!entry) throw new Error('no host configured')
|
|
15
|
+
const conn = connOf(entry)
|
|
16
|
+
|
|
17
|
+
type Case = { name: string; script: string; code?: number; out?: string | RegExp; err?: string; contains?: string }
|
|
18
|
+
const cases: Case[] = [
|
|
19
|
+
{ name: 'plain', script: `Write-Output "hi"`, code: 0, out: 'hi' },
|
|
20
|
+
{ name: 'empty', script: `''`, code: 0, out: '' },
|
|
21
|
+
{ name: 'utf8', script: `Write-Output '中文测试 ✓ 𝄞'`, code: 0, out: '中文测试 ✓ 𝄞' },
|
|
22
|
+
{ name: 'out then exit 3', script: `Write-Output 'before'; exit 3`, code: 3, out: 'before' },
|
|
23
|
+
{ name: 'utf8 exit 5', script: `Write-Output '中文'; exit 5`, code: 5, out: '中文' },
|
|
24
|
+
{ name: 'throw', script: `throw 'kaboom'`, code: 1, contains: 'kaboom' },
|
|
25
|
+
{ name: 'nonterm error', script: `Get-ChildItem C:\\definitely-not-here-xyz`, code: 0, contains: 'Cannot find path' },
|
|
26
|
+
{ name: 'write-error', script: `Write-Error "werr"`, code: 0, contains: 'werr' },
|
|
27
|
+
{ name: 'cmd exit 7', script: `cmd /c exit 7`, code: 7 },
|
|
28
|
+
{ name: 'stderr only', script: `[Console]::Error.WriteLine('e1')`, code: 0, out: '', err: 'e1' },
|
|
29
|
+
{ name: 'out and err', script: `Write-Output 'o1'; [Console]::Error.WriteLine('e1')`, code: 0, out: 'o1', err: 'e1' },
|
|
30
|
+
{ name: 'flush before exit', script: `Write-Output 'before'; exit 4`, code: 4, out: 'before' },
|
|
31
|
+
{ name: 'error then exit', script: `Get-ChildItem C:\\definitely-not-here-xyz; exit 6`, code: 6, contains: 'Cannot find path' },
|
|
32
|
+
{ name: 'exit in function', script: `function f { exit 9 }; f`, code: 9 },
|
|
33
|
+
{ name: 'negative-ish LASTEXITCODE', script: `cmd /c "exit 0"`, code: 0 },
|
|
34
|
+
{ name: 'pipeline ok', script: `1..3 | ForEach-Object { $_ }`, code: 0, out: '1\r\n2\r\n3' },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
let bad = 0
|
|
38
|
+
console.log('=== edge cases ===')
|
|
39
|
+
for (const c of cases) {
|
|
40
|
+
try {
|
|
41
|
+
const r = await runScript(conn, c.script, { timeoutMs: 60_000 })
|
|
42
|
+
const problems: string[] = []
|
|
43
|
+
if (c.code !== undefined && r.exitCode !== c.code) problems.push(`code=${r.exitCode} want=${c.code}`)
|
|
44
|
+
if (typeof c.out === 'string' && r.stdout.trimEnd() !== c.out) problems.push(`out=${JSON.stringify(r.stdout)} want=${JSON.stringify(c.out)}`)
|
|
45
|
+
if (c.err !== undefined && r.stderr.trim() !== c.err) problems.push(`err=${JSON.stringify(r.stderr)} want=${JSON.stringify(c.err)}`)
|
|
46
|
+
if (c.contains !== undefined && !r.stdout.includes(c.contains)) problems.push(`missing ${JSON.stringify(c.contains)}`)
|
|
47
|
+
if (problems.length > 0) { bad++; console.log(`BAD ${c.name}: ${problems.join('; ')}`) }
|
|
48
|
+
else console.log(`ok ${c.name} [${r.durationMs}ms]`)
|
|
49
|
+
} catch (error) {
|
|
50
|
+
bad++
|
|
51
|
+
console.log(`THREW ${c.name}: ${error instanceof Error ? error.message : String(error)}`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Big output that a mid-script exit must not truncate.
|
|
56
|
+
{
|
|
57
|
+
const r = await runScript(conn, `1..2000 | ForEach-Object { "line$_" }; exit 5`, { timeoutMs: 120_000 })
|
|
58
|
+
const lines = r.stdout.trim().split(/\r?\n/).length
|
|
59
|
+
const ok = r.exitCode === 5 && lines >= 2000
|
|
60
|
+
if (!ok) bad++
|
|
61
|
+
console.log(`${ok ? 'ok ' : 'BAD '} flush big then exit: code=${r.exitCode} lines=${lines} [${r.durationMs}ms]`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Scripts far past the ~8191-char command line: the whole point of the rewrite.
|
|
65
|
+
console.log('=== large payloads (command line cannot carry these) ===')
|
|
66
|
+
for (const size of [5_000, 50_000, 200_000]) {
|
|
67
|
+
const r = await runScript(conn, `# ${'x'.repeat(size)}\nWrite-Output 'ok'`, { timeoutMs: 120_000 })
|
|
68
|
+
const ok = r.exitCode === 0 && r.stdout.trim() === 'ok'
|
|
69
|
+
if (!ok) bad++
|
|
70
|
+
console.log(`${ok ? 'ok ' : 'BAD '} script=${size} chars code=${r.exitCode} out=${JSON.stringify(r.stdout.trim())} [${r.durationMs}ms]`)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Multi-byte payload: length is counted in bytes, split on code points.
|
|
74
|
+
{
|
|
75
|
+
const r = await runScript(conn, `# ${'中'.repeat(50_000)}\nWrite-Output 'ok'`, { timeoutMs: 120_000 })
|
|
76
|
+
const ok = r.exitCode === 0 && r.stdout.trim() === 'ok'
|
|
77
|
+
if (!ok) bad++
|
|
78
|
+
console.log(`${ok ? 'ok ' : 'BAD '} multibyte 150KB code=${r.exitCode} [${r.durationMs}ms]`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The transfer chunk that used to blow the command line (~65K base64 chars).
|
|
82
|
+
console.log('=== transfer chunk (previously structurally broken) ===')
|
|
83
|
+
{
|
|
84
|
+
const r = await runScript(conn, psReadChunk('C:\\Windows\\System32\\notepad.exe', 0, TRANSFER_CHUNK), { timeoutMs: 120_000 })
|
|
85
|
+
const bytes = Buffer.from(r.stdout.trim(), 'base64').length
|
|
86
|
+
const ok = r.exitCode === 0 && bytes === TRANSFER_CHUNK
|
|
87
|
+
if (!ok) bad++
|
|
88
|
+
console.log(`${ok ? 'ok ' : 'BAD '} read ${TRANSFER_CHUNK} bytes: code=${r.exitCode} decoded=${bytes} [${r.durationMs}ms]`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Real upload/download round trip well past the old limit.
|
|
92
|
+
console.log('=== upload/download round trip ===')
|
|
93
|
+
{
|
|
94
|
+
const remote = 'C:\\temp\\dsh-winrm-verify\\blob.bin'
|
|
95
|
+
const data = Buffer.alloc(200 * 1024)
|
|
96
|
+
for (let i = 0; i < data.length; i++) data[i] = (i * 31 + 7) & 0xff
|
|
97
|
+
const written = await uploadBuffer(conn, remote, data, undefined, 120_000)
|
|
98
|
+
const size = await remoteFileSize(conn, remote, 60_000)
|
|
99
|
+
const chunks: Buffer[] = []
|
|
100
|
+
const read = await downloadChunks(conn, remote, b64 => { chunks.push(Buffer.from(b64, 'base64')) }, undefined, 120_000)
|
|
101
|
+
const back = Buffer.concat(chunks)
|
|
102
|
+
const same = back.length === data.length && back.equals(data)
|
|
103
|
+
const ok = written === data.length && size === data.length && read === data.length && same
|
|
104
|
+
if (!ok) bad++
|
|
105
|
+
console.log(`${ok ? 'ok ' : 'BAD '} 200KB round trip: written=${written} size=${size} read=${read} identical=${same}`)
|
|
106
|
+
await runScript(conn, `Remove-Item -LiteralPath 'C:\\temp\\dsh-winrm-verify' -Recurse -Force -ErrorAction SilentlyContinue`, { timeoutMs: 60_000 })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Sanity: a script that never reaches the envelope must not claim success.
|
|
110
|
+
{
|
|
111
|
+
const r = await runScript(conn, `Write-Output 'ok'`, { timeoutMs: 60_000 })
|
|
112
|
+
console.log(`ok sanity re-check code=${r.exitCode} out=${JSON.stringify(r.stdout.trim())}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(bad === 0 ? '\nALL PASS' : `\n${bad} FAILURE(S)`)
|
|
116
|
+
process.exit(bad === 0 ? 0 : 1)
|