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
package/src/smb.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SMB transfer bridge.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* SMB is the fast channel when the admin share is reachable, so `auto` tries it
|
|
5
|
+
* first and falls back to the WinRM streaming path. This module mounts the
|
|
6
|
+
* remote host's admin share with `net use` (password passed via stdin, never
|
|
7
|
+
* argv), copies whole files with node:fs, then verifies the SHA-256 on both
|
|
8
|
+
* ends.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { spawn } from 'node:child_process'
|
|
12
12
|
import { copyFile, stat } from 'node:fs/promises'
|
|
13
13
|
import { createHash } from 'node:crypto'
|
|
14
14
|
import { readFile } from 'node:fs/promises'
|
|
15
|
+
import { createConnection } from 'node:net'
|
|
15
16
|
import type { WinHostEntry } from './protocol.ts'
|
|
16
17
|
|
|
17
18
|
/** Per-transfer channel selector. */
|
|
@@ -85,6 +86,57 @@ async function remoteSha256(host: string, remotePath: string): Promise<string> {
|
|
|
85
86
|
return match[1].toUpperCase()
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/** How long a reachability verdict is trusted before probing again. */
|
|
90
|
+
const SMB_PROBE_TTL_MS = 60_000
|
|
91
|
+
|
|
92
|
+
/** Budget for the TCP reachability probe; far below `net use`'s own timeout. */
|
|
93
|
+
const SMB_PROBE_TIMEOUT_MS = 3_000
|
|
94
|
+
|
|
95
|
+
const smbProbeCache = new Map<string, { reachable: boolean; at: number }>()
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the target's SMB port (445) accepts a connection.
|
|
99
|
+
*
|
|
100
|
+
* `net use` is the only way to mount the admin share, but it has no useful
|
|
101
|
+
* timeout: against a host whose 445 is filtered or whose SMB service is down it
|
|
102
|
+
* spends a full minute before failing. That minute was being paid on *every*
|
|
103
|
+
* `auto` upload before the WinRM fallback could even start — measured at 61 s
|
|
104
|
+
* and 58 s on the test host, against a 12 s streaming transfer. A bare TCP
|
|
105
|
+
* connect answers the same question in milliseconds.
|
|
106
|
+
*
|
|
107
|
+
* A negative verdict is cached briefly so a burst of transfers does not re-probe
|
|
108
|
+
* for each one; a positive verdict is cached too, but the `net use` that follows
|
|
109
|
+
* still reports its own failure if the share turns out to be unmountable.
|
|
110
|
+
* @param host - target hostname or address.
|
|
111
|
+
* @returns true when port 445 accepted a connection.
|
|
112
|
+
*/
|
|
113
|
+
export async function smbReachable(host: string): Promise<boolean> {
|
|
114
|
+
const cached = smbProbeCache.get(host)
|
|
115
|
+
if (cached !== undefined && Date.now() - cached.at < SMB_PROBE_TTL_MS) return cached.reachable
|
|
116
|
+
const reachable = await new Promise<boolean>((resolve) => {
|
|
117
|
+
const socket = createConnection({ host, port: 445 })
|
|
118
|
+
let settled = false
|
|
119
|
+
const finish = (value: boolean): void => {
|
|
120
|
+
if (settled) return
|
|
121
|
+
settled = true
|
|
122
|
+
socket.destroy()
|
|
123
|
+
resolve(value)
|
|
124
|
+
}
|
|
125
|
+
const timer = setTimeout(() => finish(false), SMB_PROBE_TIMEOUT_MS)
|
|
126
|
+
socket.once('connect', () => { clearTimeout(timer); finish(true) })
|
|
127
|
+
socket.once('error', () => { clearTimeout(timer); finish(false) })
|
|
128
|
+
socket.once('timeout', () => { clearTimeout(timer); finish(false) })
|
|
129
|
+
socket.setTimeout(SMB_PROBE_TIMEOUT_MS)
|
|
130
|
+
})
|
|
131
|
+
smbProbeCache.set(host, { reachable, at: Date.now() })
|
|
132
|
+
return reachable
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Error thrown when SMB was requested or probed and the target is not listening. */
|
|
136
|
+
export function smbUnavailable(host: string): Error {
|
|
137
|
+
return new Error('SMB is not reachable on ' + host + ':445 (the admin share needs TCP 445 open and the Server service running)')
|
|
138
|
+
}
|
|
139
|
+
|
|
88
140
|
/**
|
|
89
141
|
* Copy one local file to a remote Windows host over SMB, verifying the
|
|
90
142
|
* remote SHA-256 matches the local source.
|
package/src/tools.ts
CHANGED
|
@@ -272,7 +272,7 @@ export function winrmUploadTool(engine: WinRmEngine) {
|
|
|
272
272
|
alias: { type: 'string', required: true, description: 'Host alias from winrm_list.' },
|
|
273
273
|
localPath: { type: 'string', required: true, description: 'Absolute local file path on this machine.' },
|
|
274
274
|
remotePath: { type: 'string', required: true, description: 'Destination path on the remote host, e.g. C:\\temp\\file.txt (parent dirs are created).' },
|
|
275
|
-
channel: { type: 'string', enum: ['auto', 'smb', 'winrm'], description: 'Transfer channel: smb (admin share), winrm (
|
|
275
|
+
channel: { type: 'string', enum: ['auto', 'smb', 'winrm'], description: 'Transfer channel: smb (admin share), winrm (streamed over the WinRS stdin channel), or auto (default, SMB first with WinRM fallback).' },
|
|
276
276
|
},
|
|
277
277
|
output: {
|
|
278
278
|
schema: {
|
|
@@ -311,7 +311,7 @@ export function winrmDownloadTool(engine: WinRmEngine) {
|
|
|
311
311
|
alias: { type: 'string', required: true, description: 'Host alias from winrm_list.' },
|
|
312
312
|
remotePath: { type: 'string', required: true, description: 'Remote file path, e.g. C:\\temp\\file.txt.' },
|
|
313
313
|
localPath: { type: 'string', required: true, description: 'Absolute destination path on this machine.' },
|
|
314
|
-
channel: { type: 'string', enum: ['auto', 'smb', 'winrm'], description: 'Transfer channel: smb (admin share), winrm (
|
|
314
|
+
channel: { type: 'string', enum: ['auto', 'smb', 'winrm'], description: 'Transfer channel: smb (admin share), winrm (streamed over parallel WinRS ranges), or auto (default, SMB first with WinRM fallback).' },
|
|
315
315
|
},
|
|
316
316
|
output: {
|
|
317
317
|
schema: {
|