From e7150a6a04a5e3abb161b18318e63dbe42471037 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Wed, 29 Jul 2026 18:53:56 +0200 Subject: [PATCH 1/2] Rename the remote transfer directory to winrm-upload- The SEN_ShareFor_$ name (and its SEN_TempFor_/SEN_ cousins) was inherited from the ancient pre-2.0.0 SMB-share implementation; since no share is created anymore, the trailing $ (hidden-share convention) was meaningless for a plain directory. The winrm-upload- prefix is kept as short as SEN_ShareFor_ so the MAX_PATH staging budget is unchanged and the legacy SMB share name stays under the 80-character limit. Closes #150 Co-Authored-By: Claude Fable 5 --- .../metricshub/winrm/WindowsRemoteProcessUtils.java | 2 +- .../java/org/metricshub/winrm/WindowsTempShare.java | 7 ++++--- .../winrm/command/WinRMCommandExecutor.java | 4 ++-- src/site/markdown/file-transfers.md | 11 ++++++----- src/site/markdown/preparing-the-host.md | 2 +- .../java/org/metricshub/winrm/ShellFileCopyTest.java | 4 ++-- .../metricshub/winrm/wql/WinRMWqlExecutorTest.java | 4 ++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java index e97e085..c31c7c4 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java @@ -129,7 +129,7 @@ public static Charset getWindowsEncodingCharset( */ public static String buildNewOutputFileName() { return String.format( - "SEN_%s_%d_%d", + "winrm-%s-%d-%d", Utils.getComputerName(), Utils.getCurrentTimeMillis(), (long) (Math.random() * 1000000) diff --git a/src/main/java/org/metricshub/winrm/WindowsTempShare.java b/src/main/java/org/metricshub/winrm/WindowsTempShare.java index 4dadf93..54298c9 100644 --- a/src/main/java/org/metricshub/winrm/WindowsTempShare.java +++ b/src/main/java/org/metricshub/winrm/WindowsTempShare.java @@ -216,7 +216,7 @@ static String buildCreateRemoteDirectoryCommand(final String remotePath) { static String buildPathOnCluster(final String path) { Utils.checkNonNull(path, "path"); - return String.format("%s\\Temp\\SEN_TempFor_%s", path, Utils.getComputerName()); + return String.format("%s\\Temp\\winrm-upload-%s", path, Utils.getComputerName()); } /** @@ -234,12 +234,13 @@ static String buildRemotePath(final String folder, final String shareName) { } /** - * Build the Share name with the computer name. + * Build the name of the per-client-machine transfer directory (also used as the share name + * in the legacy SMB flow) with the computer name. * * @return The share name. */ static String buildShareName() { - return String.format("SEN_ShareFor_%s$", Utils.getComputerName()); + return String.format("winrm-upload-%s", Utils.getComputerName()); } /** diff --git a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java index 55815e4..9121202 100644 --- a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java +++ b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java @@ -53,9 +53,9 @@ private WinRMCommandExecutor() {} * "CSCRIPT c:\\MyScript.vbs", null, "remote-srv", null, null, null, 30000, Arrays.asList("c:\\MyScript.vbs"), false); * * This will copy c:\\MyScript.vbs to remote-srv, typically in - * C:\\Windows\\Temp\\SEN_ShareFor_MYHOST and the command that is executed will therefore + * C:\\Windows\\Temp\\winrm-upload-MYHOST and the command that is executed will therefore * become: - * CSCRIPT "C:\\Windows\\Temp\\SEN_ShareFor_MYHOST\\MyScript.vbs" + * CSCRIPT "C:\\Windows\\Temp\\winrm-upload-MYHOST\\MyScript.vbs" * * @param command The command to execute. (Mandatory) * @param protocol The HTTP protocol (HTTP by default) diff --git a/src/site/markdown/file-transfers.md b/src/site/markdown/file-transfers.md index 7887b2f..9e0a7b8 100644 --- a/src/site/markdown/file-transfers.md +++ b/src/site/markdown/file-transfers.md @@ -40,7 +40,7 @@ With **transfer-and-run**, files are copied to a per-client-machine transfer dir remote host: ```text -\Temp\SEN_ShareFor_$ +\Temp\winrm-upload- ``` * `` is discovered on the remote host with the WQL query @@ -50,15 +50,16 @@ remote host: directory. Clients that report the same computer name (cloned machines, containers) share one — which is safe, because the content-addressed file names below prevent them from ever overwriting each other's payloads; they simply also share the cache and the 30-day cleanup. - The name and the trailing `$` are kept from the pre-2.0.0 SMB implementation, which used this - directory as a hidden share — no share is created anymore. + Versions before 2.0.0 used `\Temp\SEN_ShareFor_$`, exposed as a + hidden SMB share — no share is created anymore, and a directory left behind by an older + version is neither reused nor cleaned up: it can simply be deleted. * The directory is created if missing (`IF NOT EXIST ... MKDIR ...`). Inside that directory the remote file name is **content-addressed**: a 12-hex-digit fragment of the file's SHA-256 digest is inserted before the extension: ```text -collect.vbs → \Temp\SEN_ShareFor_MYHOST$\collect.1a2b3c4d5e6f.vbs +collect.vbs → \Temp\winrm-upload-MYHOST\collect.1a2b3c4d5e6f.vbs ``` Because the name identifies the content, two files with the same name but different content get @@ -143,7 +144,7 @@ path, and the result is executed through `CMD.EXE /C (...)`: ```text given: CSCRIPT c:\scripts\collect.vbs /debug uploads: c:\scripts\collect.vbs -executes: CMD.EXE /C (CSCRIPT C:\Windows\Temp\SEN_ShareFor_MYHOST$\collect.1a2b3c4d5e6f.vbs /debug) +executes: CMD.EXE /C (CSCRIPT C:\Windows\Temp\winrm-upload-MYHOST\collect.1a2b3c4d5e6f.vbs /debug) ``` Notes: diff --git a/src/site/markdown/preparing-the-host.md b/src/site/markdown/preparing-the-host.md index 10162be..a953a66 100644 --- a/src/site/markdown/preparing-the-host.md +++ b/src/site/markdown/preparing-the-host.md @@ -263,7 +263,7 @@ separately: | --- | --- | | **WQL queries** (`client.wql(...)`) | Remote access to the listener, access to the **WMI plug-in**, and rights on the target **WMI namespace** (`ROOT\CIMV2` by default) — plus whatever the queried class itself demands. | | **Remote commands** (`client.command(...)`) | Remote access to the listener, remote shell access on the host (`AllowRemoteShellAccess`, `True` by default), and whatever rights **the command itself** needs once it runs. | -| **Transfer-and-run** (`upload(...)`) | Both of the above, plus write access to `\Temp\SEN_ShareFor_$`, and `certutil` and `forfiles` present on the host. See [File Transfers](file-transfers.html). | +| **Transfer-and-run** (`upload(...)`) | Both of the above, plus write access to `\Temp\winrm-upload-`, and `certutil` and `forfiles` present on the host. See [File Transfers](file-transfers.html). | | **`uploadFile(...)`** to an explicit path | Remote shell access, write access to the destination directory, and `certutil` on the host (the same transfer engine, minus the transfer directory and its `forfiles` housekeeping). | So an account can perfectly well run WQL queries and fail to run commands, or the reverse. When diff --git a/src/test/java/org/metricshub/winrm/ShellFileCopyTest.java b/src/test/java/org/metricshub/winrm/ShellFileCopyTest.java index b91be90..b743f62 100644 --- a/src/test/java/org/metricshub/winrm/ShellFileCopyTest.java +++ b/src/test/java/org/metricshub/winrm/ShellFileCopyTest.java @@ -396,7 +396,7 @@ void rejectsFileNamesUnsafeForTheCommandShell() { @Test void buildUploadCommandsChunksBelowTheCommandLineLimit() { - final String base64File = expectedRemoteDirectory() + "\\big.bin.SEN_X_1_2.b64"; + final String base64File = expectedRemoteDirectory() + "\\big.bin.winrm-X-1-2.b64"; final byte[] content = new byte[15000]; for (int i = 0; i < content.length; i++) { content[i] = (byte) i; @@ -450,7 +450,7 @@ void buildsContentAddressedRemoteNames() { // The explicit bound derived from the directory keeps the COMPLETE staging path (with // the "..part.b64" suffixes) within the traditional Windows MAX_PATH limit, // even for the longest allowed (64-character) client computer name - final String longDirectory = "C:\\Windows\\Temp\\SEN_ShareFor_" + "h".repeat(64) + "$"; + final String longDirectory = "C:\\Windows\\Temp\\winrm-upload-" + "h".repeat(64); final int budget = ShellFileCopy.maxRemoteNameLength(longDirectory); final String bounded = ShellFileCopy.contentAddressedName("x".repeat(300) + ".vbs", content, budget); assertTrue( diff --git a/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java b/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java index 6a71f69..d70886e 100644 --- a/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java +++ b/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java @@ -114,7 +114,7 @@ void executesTheQueryThroughTheRealProtocolStack() throws Exception { enqueueEnumeration( server, instance("Win32_Share", "Name", "C$", "Path", "C:\\"), - instance("Win32_Share", "Name", "SEN_ShareFor_PC$", "Path", "C:\\Windows\\Temp\\SEN_ShareFor_PC$") + instance("Win32_Share", "Name", "winrm-upload-PC", "Path", "C:\\Windows\\Temp\\winrm-upload-PC") ); final WinRMWqlExecutor actual = executeWql( @@ -133,7 +133,7 @@ void executesTheQueryThroughTheRealProtocolStack() throws Exception { // Headers keep the order of the WQL SELECT clause, and each row is mapped onto it assertEquals(asList("Name", "Path"), actual.getHeaders()); assertEquals( - asList(asList("C$", "C:\\"), asList("SEN_ShareFor_PC$", "C:\\Windows\\Temp\\SEN_ShareFor_PC$")), + asList(asList("C$", "C:\\"), asList("winrm-upload-PC", "C:\\Windows\\Temp\\winrm-upload-PC")), actual.getRows() ); assertTrue(actual.getExecutionTime() >= 0); From af37939903119d570dabe79ab5a56a0bae553d1a Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Wed, 29 Jul 2026 18:58:55 +0200 Subject: [PATCH 2/2] Address Codex: document the transfer-directory rename in the README upgrade notes Co-Authored-By: Claude Fable 5 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 55469e0..a81f796 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,16 @@ The Windows Remote Management (WinRM) Java Client is a library that enables to: > `WqlQuery.getSelectedProperties()`/`getSubPropertiesMap()` are now **unmodifiable views** > (and `WinRMWqlExecutor` copies the lists passed to its constructor): callers that mutated > the returned collections must now copy them first. +> * The remote transfer directory used by `upload(...)`/`localFileToCopyList` is now +> **`\Temp\winrm-upload-`** instead of the +> `SEN_ShareFor_$` name kept from the old SMB implementation. Directories +> left behind by earlier versions are neither reused nor cleaned up (they can simply be +> deleted), and hosts hardened with per-directory ACLs must grant write access on the new path. +> For consumers of the public `WindowsTempShare.getOrCreateShare(...)` API, the created SMB +> share (and the returned UNC path) follows the same rename — and without the trailing `$` the +> share is **no longer hidden** from network browsing. The generated unique file names of +> `WindowsRemoteProcessUtils.buildNewOutputFileName()` changed from `SEN_...` to `winrm-...` +> accordingly. ## Prerequisites on the target host