From 836f2a989fd5567ee688482342c247e724723ce1 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Fri, 24 Jul 2026 21:05:33 +0200 Subject: [PATCH 1/2] Rewrite Maven site documentation on Sentry skin 8.0.00 (#113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single-page site (skin 6.4.01, legacy SITE 1.x descriptor) with a curated, multi-page documentation site on the latest Sentry Maven Skin. Site tooling: - Pin maven-site-plugin 4.0.0-M16 + maven-skin-tools 1.8.00. This fixes the project-info-reports LinkageError (oss-parent pins site plugin 3.12.1, too old for the Doxia 2.0 report plugins). - Migrate src/site/site.xml to the SITE 2.0 schema and skin 8.0.00, with the knobs (noDefaultLinks, keywords, GitHub social, additionalLinks), grouped menus (Getting Started / Usage / Reference / reports), and top-bar Releases + Issue Tracker links. - Reporting: add maven-surefire-report-plugin; disable the inherited maven-changelog-plugin (3.0.0-M1 predates Doxia 2.0). project-info, jxr, checkstyle, pmd, spotbugs, javadoc reports are inherited from oss-parent. Documentation pages (src/site/markdown/): index, installation, wql, commands, authentication, tls, timeouts-and-errors, migrating-from-1x — each with keywords/description front-matter and a MACRO{toc}, plus GitHub-flavored [!WARNING] alerts and [!TABS] blocks. Theming: keep the MetricsHub Poppins/blue site.css; set --content-font-size to 15px. Bump project.build.outputTimestamp to 2026 so the site copyright year and "Documentation as of" date are current (they derive from the reproducible-build timestamp). Docs cleanup: - README.md: drop the stale "WS-Man Client / winrm4j" credit (2.0.0 is a from-scratch dependency-free client). - README.md, CHANGELOG.md, migrating-from-1x.md: remove mentions of the never-public org.metricshub.winrm.backend system property (tracked by #121). GitHub Pages publishing is unchanged: the release workflow already deploys target/site via the shared metricshub/workflows maven-central-release.yml. Verified with `mvn clean verify site` (JDK 17): green, no LinkageError. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 2 - README.md | 6 +- pom.xml | 84 +++++++++++++- src/site/markdown/authentication.md | 111 ++++++++++++++++++ src/site/markdown/commands.md | 142 +++++++++++++++++++++++ src/site/markdown/index.md | 141 +++++++++++++--------- src/site/markdown/installation.md | 88 ++++++++++++++ src/site/markdown/migrating-from-1x.md | 76 ++++++++++++ src/site/markdown/timeouts-and-errors.md | 68 +++++++++++ src/site/markdown/tls.md | 78 +++++++++++++ src/site/markdown/wql.md | 124 ++++++++++++++++++++ src/site/resources/css/site.css | 2 +- src/site/site.xml | 61 +++++++--- 13 files changed, 905 insertions(+), 78 deletions(-) create mode 100644 src/site/markdown/authentication.md create mode 100644 src/site/markdown/commands.md create mode 100644 src/site/markdown/installation.md create mode 100644 src/site/markdown/migrating-from-1x.md create mode 100644 src/site/markdown/timeouts-and-errors.md create mode 100644 src/site/markdown/tls.md create mode 100644 src/site/markdown/wql.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 165a794..3be755d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,8 +56,6 @@ unaffected. Consequences: (`-Djavax.net.ssl.trustStore=...`); or - disable TLS validation with `-Dorg.metricshub.winrm.tls.insecure=true` (**insecure — for testing only**). -- Setting `-Dorg.metricshub.winrm.backend=cxf` now fails with a clear error instead of selecting - the removed backend: remove the property (or stay on winrm-java 1.x). - The jar shrinks dramatically: the Apache CXF / JAX-WS / JAXB stack is gone, and with the SMB file copy replaced by a WinRM-native transfer (see above), the library has **zero runtime dependencies**. diff --git a/README.md b/README.md index d5bbec0..0c5d6dc 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ ![GitHub top language](https://img.shields.io/github/languages/top/metricshub/winrm-java) ![License](https://img.shields.io/github/license/metricshub/winrm-java) -This project uses [WS-Man Client](https://github.com/OpenNMS/wsman) and [winrm4j](https://github.com/cloudsoft/winrm4j/) - See **[Project Documentation](https://metricshub.org/winrm-java)** and the [Javadoc](https://metricshub.org/winrm-java/apidocs) for more information on how to use this library in your code. The Windows Remote Management (WinRM) Java Client is a library that enables to: @@ -16,7 +14,7 @@ The Windows Remote Management (WinRM) Java Client is a library that enables to: > ## ⚠️ Upgrading from 1.x > > Version 2.0.0 **removed the legacy Apache CXF backend**: the dependency-free **light** client is -> the only implementation (same public API — calling code is unaffected). Two consequences: +> the only implementation (same public API — calling code is unaffected). The main consequence: > > * Unlike the CXF-based client, which silently trusted every TLS certificate, the light client > **validates the server certificate and verifies the hostname by default**. @@ -24,8 +22,6 @@ The Windows Remote Management (WinRM) Java Client is a library that enables to: > will fail** during the TLS handshake unless you install the server certificate (or its issuing > CA) into a Java trust store (e.g. `-Djavax.net.ssl.trustStore=...`) or disable TLS validation > with `-Dorg.metricshub.winrm.tls.insecure=true` (**insecure — for testing only**). -> * Setting `-Dorg.metricshub.winrm.backend=cxf` now fails with a clear error instead of selecting -> the removed backend. Remove the property (or stay on winrm-java 1.x). ## The WinRM client diff --git a/pom.xml b/pom.xml index 00c8572..5d5105e 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,8 @@ - 2025-04-03T12:15:29Z + + 2026-07-24T00:00:00Z @@ -202,12 +203,82 @@ + + + maven-site-plugin + 4.0.0-M16 + + + org.sentrysoftware.maven + maven-skin-tools + 1.8.00 + + + + + + + + + maven-project-info-reports-plugin + 3.9.0 + + + + ci-management + dependencies + dependency-info + distribution-management + issue-management + licenses + plugins + scm + summary + team + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + javadoc + + + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.5.6 + + maven-pmd-plugin @@ -222,6 +293,17 @@ + + + maven-changelog-plugin + + + + + + + diff --git a/src/site/markdown/authentication.md b/src/site/markdown/authentication.md new file mode 100644 index 0000000..4cd6ecb --- /dev/null +++ b/src/site/markdown/authentication.md @@ -0,0 +1,111 @@ +keywords: authentication, ntlm, kerberos, spnego, domain, realm, kdc, krb5, ticket cache +description: Authenticate to WinRM with NTLM or Kerberos (SPNEGO), including domain accounts and Kerberos configuration. + +# Authentication + + + +The client authenticates with either **NTLM** or **Kerberos (SPNEGO)**. The scheme is chosen by the +`authentications` argument, a +`List<`[`AuthenticationEnum`](apidocs/org/metricshub/winrm/service/client/auth/AuthenticationEnum.html)`>` +that both `executeWql(...)` and `WinRMCommandExecutor.execute(...)` accept. + +```java +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.KERBEROS; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; + +singletonList(NTLM); // NTLM only (also the default when null or empty) +singletonList(KERBEROS); // Kerberos only +``` + +If the list is `null` or empty, **NTLM** is used. + +## User name and domain + +The user name may be given as `DOMAIN\user` or as a bare `user`. When a backslash is present, the +part before it is treated as the Windows domain and the part after it as the account name. In Java, +remember to escape the backslash in a string literal: + +```java +"DOMAIN\\Administrator" // domain = DOMAIN, user = Administrator +"Administrator" // no domain +``` + +## NTLM + +NTLM is the default. It works over both transports: + +* **HTTP** — the WinRM payload is protected with **NTLM message encryption**, so credentials and + data are not sent in the clear even without TLS. +* **HTTPS** — NTLM runs inside the TLS tunnel. See [TLS / HTTPS](tls.html). + +NTLM needs no extra configuration beyond the user name and password. + +## Kerberos (SPNEGO) + +Kerberos authentication uses SPNEGO through the JDK's GSS-API and **requires HTTPS**. + +```java +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.KERBEROS; + +executeWql( + HTTPS, "server.example.com", null, + "DOMAIN\\Administrator", password, null, + "SELECT Name FROM Win32_ComputerSystem", + 30_000L, + ticketCache, // optional java.nio.file.Path to a ticket cache + singletonList(KERBEROS) +); +``` + +### Kerberos configuration + +By default, Kerberos relies on the **ambient JDK Kerberos configuration** — the platform `krb5.conf` +(or the file named by `-Djava.security.krb5.conf`), or the realm and KDC given directly with +`-Djava.security.krb5.realm` and `-Djava.security.krb5.kdc`: + +```bash +java -Djava.security.krb5.realm=EXAMPLE.COM \ + -Djava.security.krb5.kdc=dc01.example.com \ + -cp ... MyApp +``` + +The optional `ticketCache` parameter points to a Kerberos ticket cache to use for the connection. + +## Choosing the scheme on the command line + +The standalone jar selects the scheme with `--ntlm` (the default) or `--kerberos`. The two are +mutually exclusive, and `--kerberos` requires `--https`: + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.example.com -u 'DOMAIN\user' -pf password.txt \ + --https --kerberos \ + command whoami +``` + +Instead of relying on the ambient configuration, the CLI can set the JDK Kerberos configuration for +the current invocation: + +| Option | Meaning | +| --- | --- | +| `--kerberos-kdc ` | Sets the KDC and, unless `--kerberos-realm` is given, infers the realm from the KDC's DNS suffix. | +| `--kerberos-realm ` | Overrides the inferred realm. Requires `--kerberos-kdc`. | + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.internal.example.com -u 'DOMAIN\user' -pf password.txt \ + --https --kerberos --kerberos-kdc dc01.internal.example.com \ + command whoami +``` + +Here the realm is inferred as `INTERNAL.EXAMPLE.COM` by dropping the KDC's first DNS label and +upper-casing the rest. This follows a common Active Directory naming convention but is not +guaranteed by Kerberos — pass `--kerberos-realm` when the realm does not match the KDC's DNS suffix, +or when the KDC is not a fully qualified DNS name. + +## See also + +* [TLS / HTTPS](tls.html) — required for Kerberos and recommended for NTLM +* [Timeouts and Errors](timeouts-and-errors.html) — how authentication failures surface diff --git a/src/site/markdown/commands.md b/src/site/markdown/commands.md new file mode 100644 index 0000000..f14414f --- /dev/null +++ b/src/site/markdown/commands.md @@ -0,0 +1,142 @@ +keywords: command, execute, cmd, stdout, stderr, exit code, file copy, script +description: Execute remote commands with WinRMCommandExecutor, capture output and exit codes, and copy local files to the host. + +# Remote Commands + + + +The client can run an arbitrary command on the remote host and hand back its standard output, +standard error, and exit code. It can also copy local script files to the host first and rewrite the +command so it references them. + +## `WinRMCommandExecutor.execute(...)` + +Commands are run with the static method +[`WinRMCommandExecutor.execute(...)`](apidocs/org/metricshub/winrm/command/WinRMCommandExecutor.html), +which returns a +[`WindowsRemoteCommandResult`](apidocs/org/metricshub/winrm/WindowsRemoteCommandResult.html). + +```java +import static java.util.Collections.singletonList; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; + +import org.metricshub.winrm.WindowsRemoteCommandResult; +import org.metricshub.winrm.command.WinRMCommandExecutor; + +WindowsRemoteCommandResult result = WinRMCommandExecutor.execute( + "ipconfig /all", // command (mandatory) + HTTPS, // protocol + "server.example.com", // hostname (mandatory) + null, // port (null → 5985 for HTTP, 5986 for HTTPS) + "DOMAIN\\Administrator", // username (mandatory) + "the-password".toCharArray(), // password + null, // working directory (nullable) + 30_000L, // timeout in milliseconds + null, // local files to copy (nullable) + null, // Kerberos ticket cache (null for NTLM) + singletonList(NTLM) // authentication schemes +); + +System.out.println("exit code: " + result.getStatusCode()); +System.out.print(result.getStdout()); +System.err.print(result.getStderr()); +``` + +### Parameters + +| Parameter | Type | Notes | +| --- | --- | --- | +| `command` | `String` | The command line to run. **Mandatory.** | +| `protocol` | [`WinRMHttpProtocolEnum`](apidocs/org/metricshub/winrm/WinRMHttpProtocolEnum.html) | `HTTP` or `HTTPS`. `null` defaults to `HTTP`. | +| `hostname` | `String` | Host name or IP address. **Mandatory.** | +| `port` | `Integer` | `null` uses the protocol default. | +| `username` | `String` | `DOMAIN\user` or `user`. **Mandatory.** | +| `password` | `char[]` | The password. | +| `workingDirectory` | `String` | Working directory of the spawned process on the remote host. May be `null`. | +| `timeout` | `long` | Timeout in milliseconds. Must be **greater than zero**. | +| `localFileToCopyList` | `List` | Local files to copy to the host before running (see below). May be `null`. | +| `ticketCache` | `java.nio.file.Path` | Kerberos ticket cache path. `null` for NTLM. | +| `authentications` | `List<`[`AuthenticationEnum`](apidocs/org/metricshub/winrm/service/client/auth/AuthenticationEnum.html)`>` | Requested schemes. `null` or empty means NTLM only. | + +## The result + +[`WindowsRemoteCommandResult`](apidocs/org/metricshub/winrm/WindowsRemoteCommandResult.html) is an +immutable value: + +| Method | Returns | Description | +| --- | --- | --- | +| `getStdout()` | `String` | The command's standard output. | +| `getStderr()` | `String` | The command's standard error. | +| `getStatusCode()` | `int` | The process exit code. | +| `getExecutionTime()` | `float` | The measured execution time of the command. | + +## Character set + +The output character set does not need to be specified: the client detects the remote host's active +code page before the command runs and decodes standard output and standard error accordingly. + +## Copying local files to the host + +Pass one or more local paths in `localFileToCopyList` to have them copied to the remote host before +the command runs. Every reference to a listed file in the `command` string is rewritten to the path +where the file lands on the host — typically under `C:\Windows\Temp`. For example: + +```java +WinRMCommandExecutor.execute( + "CSCRIPT c:\\MyScript.vbs", + /* protocol */ HTTPS, + /* hostname */ "server.example.com", + /* port */ null, + /* username */ "DOMAIN\\Administrator", + /* password */ password, + /* workingDir */ null, + /* timeout */ 30_000L, + /* files */ java.util.List.of("c:\\MyScript.vbs"), + /* ticket */ null, + /* auth */ singletonList(NTLM) +); +``` + +copies `c:\MyScript.vbs` to the host and runs the equivalent of: + +```text +CSCRIPT "C:\Windows\Temp\...\MyScript.vbs" +``` + +How the transfer works, and what to keep in mind: + +* Files travel **through the WinRM command shell itself** — chunked base64, decoded on the host with + `certutil` and verified with a digest. There is **no SMB**: TCP port 445 does not need to be + reachable, no administrative share is created, and the copy works from any client OS. +* The transfer is **content-addressed**: a fragment of the content digest is inserted before the + file extension (for example `MyScript.1a2b3c4d.vbs`), so files with the same name but different + content never overwrite each other. A script that inspects its own name (`WScript.ScriptName`) + therefore sees the digest fragment. +* A file already present on the host with an identical digest is **not transferred again**. +* The mechanism is designed for **small script files**, not bulk data — base64 over SOAP is not a + fast bulk transport. + +## Exceptions + +`execute(...)` declares: + +| Exception | When | +| --- | --- | +| `java.io.IOException` | An I/O error, including a copied-file problem. | +| `java.util.concurrent.TimeoutException` | The operation did not complete within `timeout`. | +| [`WindowsRemoteException`](apidocs/org/metricshub/winrm/exceptions/WindowsRemoteException.html) | Any problem on the remote host (in practice a [`WinRMException`](apidocs/org/metricshub/winrm/exceptions/WinRMException.html)). | + +See [Timeouts and Errors](timeouts-and-errors.html) for details. + +## From the command line + +The standalone jar runs a command with the `command` subcommand (aliases: `cmd`, `exec`, `run`). +Standard output and standard error are forwarded to the corresponding local streams, and the +process exits with the remote exit code when it fits in 0–255: + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.example.com -u 'DOMAIN\user' -pf password.txt --https \ + exec ipconfig /all +``` diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index ad29726..cfd50ab 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,71 +1,100 @@ +keywords: winrm java client, windows remote management, wsman, dependency-free, overview +description: A dependency-free Java client for Windows Remote Management (WinRM): run WQL queries and remote commands over NTLM or Kerberos. + # WinRM Java Client -The Windows Remote Management (WinRM) Java Client is a library that enables to: -* Connect to a remote Windows server using one of the two authentication types (NTLM, KERBEROS) -* Execute WMI Query Language (WQL) queries which uses HTTP/HTTPS protocols. - -> ## ⚠️ Upgrading from 1.x -> -> Version 2.0.0 **removed the legacy Apache CXF backend**: the dependency-free client is the only -> implementation (same public API). Unlike the CXF-based client, which silently trusted every TLS -> certificate, it **validates the server certificate and verifies the hostname by default**, so -> **WinRM-over-HTTPS connections to hosts with self-signed or untrusted certificates will fail** -> during the TLS handshake. To restore connectivity, either install the certificate into a Java -> trust store or set `-Dorg.metricshub.winrm.tls.insecure=true` (insecure — for testing only). -> The client supports NTLM over HTTP/HTTPS and Kerberos (SPNEGO) over HTTPS. Setting -> `-Dorg.metricshub.winrm.backend=cxf` now fails with a clear error; remove the property (or stay -> on winrm-java 1.x). - -# How to run the WinRM Client inside Java - -Add WinRM in the list of dependencies in your [Maven **pom.xml**](https://maven.apache.org/pom.html): - -```xml - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - - -``` + + +## Overview + +The **WinRM Java Client** is a small library that talks to the Windows Remote Management +(WS-Management) service on a remote Windows host. It lets a Java application: + +* run **WQL / WMI queries** such as `SELECT Name, State FROM Win32_Service` and read the rows back + ([WQL Queries](wql.html)), and +* **execute remote commands**, capturing standard output, standard error and the exit code — + optionally copying local script files to the host first ([Remote Commands](commands.html)). + +It supports **NTLM** over HTTP (with message encryption) and HTTPS, and **Kerberos (SPNEGO)** over +HTTPS ([Authentication](authentication.html)). + +Since 2.0.0 the client has **zero runtime dependencies** (no Apache CXF / JAX-WS / JAXB stack, no SMB +stack) and is immune by construction to JAXP `ServiceLoader` conflicts, because it uses the +JDK-default XML factories. Problems are reported through exceptions only — the library pulls in no +logging framework. + +> [!WARNING] +> **Upgrading from 1.x?** Version 2.0.0 removed the legacy Apache CXF backend and now +> **validates TLS certificates and verifies hostnames by default**. If you connect over HTTPS to +> hosts with self-signed certificates, read [Migrating from 1.x](migrating-from-1x.html) first. + +## Add the dependency + +The library is published on [Maven Central](https://central.sonatype.com/artifact/${project.groupId}/${project.artifactId}). -Use it as follows: -```Java -import static java.nio.file.Paths.get; +> [!TABS] +> * Maven +> ```xml +> +> ${project.groupId} +> ${project.artifactId} +> ${project.version} +> +> ``` +> * Gradle (Groovy) +> ```groovy +> implementation '${project.groupId}:${project.artifactId}:${project.version}' +> ``` +> * Gradle (Kotlin) +> ```kotlin +> implementation("${project.groupId}:${project.artifactId}:${project.version}") +> ``` + +See [Installation](installation.html) for the coordinates, the supported JDKs, and the standalone +command-line jar. + +## A first WQL query + +Everything starts with the static +[`WinRMWqlExecutor.executeWql(...)`](apidocs/org/metricshub/winrm/wql/WinRMWqlExecutor.html) method: + +```java import static java.util.Collections.singletonList; import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTP; import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; import static org.metricshub.winrm.wql.WinRMWqlExecutor.executeWql; -import java.nio.file.Path; -import java.util.List; -import java.util.concurrent.TimeoutException; - -import org.metricshub.winrm.exceptions.WinRMException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; import org.metricshub.winrm.wql.WinRMWqlExecutor; -public class Main { +public class Example { - public static void main(String[] args) throws WinRMException, WqlQuerySyntaxException, TimeoutException { + public static void main(String[] args) throws Exception { - final String wqlQuery = "SELECT Name, Path, Type FROM Win32_Share"; - final String hostname = "my-hostname-or-ip-address"; - final String username = "my-username"; - final char[] password = "my-password".toCharArray(); - final long timeout = 50 * 1000L; // in milliseconds - final Path ticketCache = get("path"); + WinRMWqlExecutor result = executeWql( + HTTP, // protocol (HTTP or HTTPS) + "server.example.com", // hostname (mandatory) + 5985, // port (null for the protocol default) + "DOMAIN\\Administrator", // username (DOMAIN\user or user) + "the-password".toCharArray(), // password + null, // namespace (null → ROOT\CIMV2) + "SELECT Name, State FROM Win32_Service", // WQL query + 30_000L, // timeout in milliseconds + null, // Kerberos ticket cache (null for NTLM) + singletonList(NTLM) // authentication schemes + ); - // Authentication type : NTLM or KERBEROS - final List authentications = singletonList(NTLM); - - // Execute a WQL Query in the hostname and print the result - executeWql(HTTP, hostname, 5985, username, password, null, wqlQuery, timeout, ticketCache, authentications) - .getRows().forEach(System.out::println); - - } + System.out.println(result.getHeaders()); // [Name, State] + result.getRows().forEach(System.out::println); // one List per row + } } ``` + +## Where to go next + +* [Installation](installation.html) — coordinates, supported JDKs, and the standalone CLI jar +* [WQL Queries](wql.html) — query WMI and read the result +* [Remote Commands](commands.html) — run commands and copy files to the host +* [Authentication](authentication.html) — NTLM and Kerberos +* [TLS / HTTPS](tls.html) — certificate validation and trust stores +* [Timeouts and Errors](timeouts-and-errors.html) — timeout semantics and the exception surface +* [Migrating from 1.x](migrating-from-1x.html) — the 2.0.0 breaking changes diff --git a/src/site/markdown/installation.md b/src/site/markdown/installation.md new file mode 100644 index 0000000..4a7461e --- /dev/null +++ b/src/site/markdown/installation.md @@ -0,0 +1,88 @@ +keywords: install, maven, gradle, dependency, standalone, cli, jdk +description: Add the WinRM Java Client to your build, or run the standalone command-line jar. + +# Installation + + + +## Coordinates + +The library is published on +[Maven Central](https://central.sonatype.com/artifact/${project.groupId}/${project.artifactId}) under: + +| Field | Value | +| --- | --- | +| `groupId` | `${project.groupId}` | +| `artifactId` | `${project.artifactId}` | +| `version` | `${project.version}` | + +## Add it to your build + +> [!TABS] +> * Maven +> ```xml +> +> ${project.groupId} +> ${project.artifactId} +> ${project.version} +> +> ``` +> * Gradle (Groovy) +> ```groovy +> implementation '${project.groupId}:${project.artifactId}:${project.version}' +> ``` +> * Gradle (Kotlin) +> ```kotlin +> implementation("${project.groupId}:${project.artifactId}:${project.version}") +> ``` + +For other build tools, the [dependency information](dependency-info.html) report lists the snippet +for Ivy, SBT, Leiningen, and others. + +## Supported JDKs + +The library targets **Java 11** and runs on any later JDK. + +## Runtime dependencies + +Since 2.0.0 the client has **zero runtime dependencies**. There is no longer an Apache CXF / +JAX-WS / JAXB stack, no BouncyCastle, and no SMB stack (`smbj`) on the classpath — the client speaks +WS-Management over the JDK's own HTTP and XML APIs, and copies files through the WinRM channel +itself. If you upgraded from 1.x, see [Migrating from 1.x](migrating-from-1x.html) for the details +and the behavior changes this implies. + +## Standalone command-line jar + +Every release also ships a self-contained executable jar that bundles the client and a small CLI. +Download `${project.artifactId}-${project.version}-standalone.jar` from the +[latest release](https://github.com/metricshub/winrm-java/releases/latest), then run it with Java: + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar --help +``` + +Run a WQL query: + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + --hostname server.example.com --username 'DOMAIN\user' \ + --password-file password.txt --ntlm \ + wql 'SELECT Name, State FROM Win32_Service' +``` + +Run a remote command (`cmd`, `exec`, and `run` are aliases for `command`): + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.example.com -u Administrator -pf password.txt --https \ + exec ipconfig /all +``` + +The CLI is covered in more detail throughout the [Usage](wql.html) pages; `--version` prints the +build version. + +## Where to go next + +* [WQL Queries](wql.html) +* [Remote Commands](commands.html) +* [Authentication](authentication.html) diff --git a/src/site/markdown/migrating-from-1x.md b/src/site/markdown/migrating-from-1x.md new file mode 100644 index 0000000..62f8c1e --- /dev/null +++ b/src/site/markdown/migrating-from-1x.md @@ -0,0 +1,76 @@ +keywords: migration, upgrade, 1.x, 2.0, cxf, tls, smb, breaking changes +description: What changed in WinRM Java Client 2.0.0 and how to upgrade from the 1.x releases. + +# Migrating from 1.x + + + +Version 2.0.0 is a major cleanup: the legacy Apache CXF backend and the SMB-based file copy are +gone, leaving a **dependency-free** client. The **public API is unchanged**, so calling code +compiles and runs without modification — but two runtime behaviors changed, and you should read this +page before upgrading. + +## TL;DR + +* The Apache CXF backend was **removed**; the dependency-free client is the only implementation. +* HTTPS now **validates the certificate and verifies the hostname by default** (1.x trusted every + certificate). Self-signed hosts that used to work will now fail the TLS handshake until you trust + the certificate or opt out. +* File copy for `localFileToCopyList` now goes **through the WinRM channel** instead of SMB. +* A few CXF/SMB-only classes were removed. + +## TLS is validated by default + +The 1.x CXF client silently trusted every TLS certificate and skipped hostname verification. The +2.0.0 client uses the JDK's default validating socket factory instead, so **HTTPS connections to +hosts with self-signed or otherwise untrusted certificates now fail** during the handshake. + +To restore connectivity, either: + +* install the server certificate (or its issuing CA) into a Java trust store — + `-Djavax.net.ssl.trustStore=...` (recommended); or +* disable validation with `-Dorg.metricshub.winrm.tls.insecure=true` (**insecure — testing only**). + +See [TLS / HTTPS](tls.html) for details. + +## The CXF backend was removed + +The dependency-free client introduced in the previous release is now the only implementation. There +is no switch to fall back to the Apache CXF backend — if you still need it, stay on winrm-java 1.x. + +## File copy no longer uses SMB + +Files listed in `localFileToCopyList` for +[`WinRMCommandExecutor.execute(...)`](commands.html) are no longer copied over SMB. They are +transferred **through the WinRM command shell** (chunked base64, decoded on the host with `certutil` +and verified with a digest). The consequences: + +* **No SMB requirement** — TCP port 445 no longer needs to be reachable, no administrative or + temporary share is created on the host, and the copy now works from **any client OS** (1.x wrote + through a Windows UNC path and only worked from a Windows client). +* The remote copy is **content-addressed**: a fragment of the content digest is inserted before the + file extension (for example `script.1a2b3c4d.vbs`). Same-named files with different content can no + longer overwrite each other, and a script that reads its own name (`WScript.ScriptName`) will see + the digest fragment. +* A file already present on the host with an identical digest is not transferred again. +* The transport is meant for **small script files**, not bulk data. + +## Fewer dependencies + +Removing CXF and SMB leaves the library with **zero runtime dependencies**: the Apache CXF / +JAX-WS / JAXB stack is gone, and so are `smbj`, BouncyCastle, SLF4J, `mbassador`, and `asn-one`. The +standalone CLI jar shrinks from around 9 MB to a few hundred kB, and the library no longer references +any logging API — problems are reported through [exceptions](timeouts-and-errors.html) only. + +## Removed classes + +If your code referenced these internal or SMB/CXF-only types, they no longer exist: + +* `KerberosCredentialsException` — was thrown only by CXF internals. +* `SmbTempShare` and `WindowsRemoteProcessUtils.copyLocalFilesToShare(...)` — replaced by the + WinRM-channel file transfer. `WindowsTempShare` is unchanged. +* The Apache CXF-based `WinRMService` and its `service.client` internals, along with the generated + WSDL/XSD resources. + +The documented entry points — `WinRMWqlExecutor`, `WinRMCommandExecutor`, `WinRMEndpoint`, +`WindowsRemoteCommandResult`, the enums, and the exception types — are unchanged. diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md new file mode 100644 index 0000000..100e44e --- /dev/null +++ b/src/site/markdown/timeouts-and-errors.md @@ -0,0 +1,68 @@ +keywords: timeout, exception, error, winrmexception, wsmanfault, exit code +description: Timeout semantics and the exception surface of the WinRM Java Client, plus the command-line exit codes. + +# Timeouts and Errors + + + +## Timeouts + +Both `executeWql(...)` and `WinRMCommandExecutor.execute(...)` take a `timeout` in +**milliseconds**. The value must be **greater than zero** — passing `0` or a negative value throws +an `IllegalArgumentException` immediately. + +The timeout is a **budget for the whole operation**, not for a single network round trip. Opening +the connection, detecting the remote code page, copying files, and running the query or command all +draw from it. When the budget is exhausted, the call throws +`java.util.concurrent.TimeoutException`. + +```java +try { + executeWql(HTTP, host, null, user, password, null, query, 30_000L, null, singletonList(NTLM)); +} catch (java.util.concurrent.TimeoutException e) { + // the operation did not finish within 30 seconds +} +``` + +## The exception surface + +| Exception | Checked? | Meaning | +| --- | --- | --- | +| [`WindowsRemoteException`](apidocs/org/metricshub/winrm/exceptions/WindowsRemoteException.html) | yes | Base type for a problem on the remote host. | +| [`WinRMException`](apidocs/org/metricshub/winrm/exceptions/WinRMException.html) | yes | A WinRM/WSMan failure — authentication rejection, WMI error, protocol fault, connection or TLS problem. Extends `WindowsRemoteException`. | +| [`WqlQuerySyntaxException`](apidocs/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.html) | yes | The WQL query does not match the supported `SELECT` syntax. | +| `java.util.concurrent.TimeoutException` | yes | The operation exceeded its `timeout`. | +| `java.io.IOException` | yes | Declared by `WinRMCommandExecutor.execute(...)` for I/O problems (including copied-file errors). | +| `IllegalArgumentException` | no | A mandatory argument is missing, or `timeout` is not greater than zero. | + +`executeWql(...)` declares `WinRMException`, `WqlQuerySyntaxException`, and `TimeoutException`. +`WinRMCommandExecutor.execute(...)` declares `IOException`, `TimeoutException`, and +`WindowsRemoteException`. + +### Fault detail + +When the remote host returns a WSMan fault, the exception message carries the detailed `WSManFault` +text — including the provider-level detail such as WMI `WBEM_E_*` mnemonics — alongside the SOAP +reason text, so the underlying cause is visible in the message. + +### Authentication failures + +A rejected credential surfaces as a `WinRMException` whose message is of the form +`Authentication error on with user name ""`. + +## Command-line exit codes + +The standalone jar maps outcomes to stable process exit codes: + +| Exit code | Meaning | +| ---: | --- | +| `0` | Successful WQL query or remote command. | +| `0`–`255` | Remote command exit code, when it fits in that range. | +| `64` | Invalid CLI usage. | +| `69` | Connection, DNS, socket, or TLS failure. | +| `70` | WinRM protocol or other remote failure. | +| `77` | Authentication failure. | +| `124` | Operation timeout. | + +Diagnostics are written only to standard error, so a WQL query's JSON Lines output on standard +output is never mixed with error messages. diff --git a/src/site/markdown/tls.md b/src/site/markdown/tls.md new file mode 100644 index 0000000..c79f841 --- /dev/null +++ b/src/site/markdown/tls.md @@ -0,0 +1,78 @@ +keywords: tls, https, certificate, trust store, hostname verification, insecure, self-signed +description: How the client validates TLS certificates over HTTPS, how to trust a certificate, and the insecure test-only opt-out. + +# TLS / HTTPS + + + +Use HTTPS by passing `HTTPS` as the protocol. HTTPS uses port **5986** by default (HTTP uses +**5985**); pass an explicit `port` to override either. + +```java +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; + +executeWql(HTTPS, "server.example.com", null, /* ... */); +``` + +## Validation is on by default + +Since 2.0.0 the client uses the JDK's default, **validating** `SSLSocketFactory`: it checks the +server certificate against the platform trust store and **verifies the server hostname** during the +handshake. + +> [!WARNING] +> This is a change from the 1.x CXF-based client, which silently trusted every certificate and +> skipped hostname verification. Connections over HTTPS to hosts with **self-signed or otherwise +> untrusted certificates now fail** during the TLS handshake unless you trust the certificate or +> explicitly opt out (below). See [Migrating from 1.x](migrating-from-1x.html). + +## Trusting a certificate + +The recommended fix for a self-signed or private-CA host is to add the server certificate (or its +issuing CA) to a Java trust store and point the JVM at it with the standard system properties: + +```bash +java -Djavax.net.ssl.trustStore=/path/to/truststore.jks \ + -Djavax.net.ssl.trustStorePassword=changeit \ + -cp ... MyApp +``` + +Because the client uses the JDK default socket factory, any trust store configured this way (or the +platform's default trust store) applies automatically. + +## Disabling validation (insecure — testing only) + +For a self-signed test host where installing a trust store is not practical, set the system property +`org.metricshub.winrm.tls.insecure` to `true`. This trusts **all** certificates and skips hostname +verification: + +```bash +java -Dorg.metricshub.winrm.tls.insecure=true -cp ... MyApp +``` + +> [!WARNING] +> This defeats the protection TLS provides against man-in-the-middle attacks. Use it only for +> testing or for isolated hosts, never in production. + +## On the command line + +The standalone jar mirrors this behavior: + +| Option | Meaning | +| --- | --- | +| `--https` | Use HTTPS (port 5986 by default). | +| `--https-permissive` | Trust any certificate and hostname. Intentionally insecure; testing only. Requires `--https`. | + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.example.com -u 'DOMAIN\user' -pf password.txt \ + --https --https-permissive \ + wql 'SELECT Name FROM Win32_ComputerSystem' +``` + +`--https-permissive` sets `org.metricshub.winrm.tls.insecure=true` for that invocation. + +## See also + +* [Authentication](authentication.html) — Kerberos requires HTTPS +* [Timeouts and Errors](timeouts-and-errors.html) — a TLS failure surfaces as a connection error diff --git a/src/site/markdown/wql.md b/src/site/markdown/wql.md new file mode 100644 index 0000000..0f553ff --- /dev/null +++ b/src/site/markdown/wql.md @@ -0,0 +1,124 @@ +keywords: wql, wmi, query, win32, namespace, root cimv2, cim +description: Run WQL / WMI queries with WinRMWqlExecutor and read the result rows. + +# WQL Queries + + + +WQL (WMI Query Language) is the SQL-like language used to query the Windows Management +Instrumentation (WMI) repository. The client runs a query on the remote host and returns the rows. + +## `WinRMWqlExecutor.executeWql(...)` + +A query is executed with the static method +[`WinRMWqlExecutor.executeWql(...)`](apidocs/org/metricshub/winrm/wql/WinRMWqlExecutor.html). It +opens a connection, runs the query, collects every row, closes the connection, and returns a +[`WinRMWqlExecutor`](apidocs/org/metricshub/winrm/wql/WinRMWqlExecutor.html) holding the result. + +```java +import static java.util.Collections.singletonList; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTP; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; +import static org.metricshub.winrm.wql.WinRMWqlExecutor.executeWql; + +import org.metricshub.winrm.wql.WinRMWqlExecutor; + +WinRMWqlExecutor result = executeWql( + HTTP, // protocol + "server.example.com", // hostname + null, // port (null → 5985 for HTTP, 5986 for HTTPS) + "DOMAIN\\Administrator", // username + "the-password".toCharArray(), // password + "ROOT\\CIMV2", // namespace (null → ROOT\CIMV2) + "SELECT Name, State FROM Win32_Service", // WQL query + 30_000L, // timeout in milliseconds + null, // Kerberos ticket cache (null for NTLM) + singletonList(NTLM) // authentication schemes +); +``` + +### Parameters + +| Parameter | Type | Notes | +| --- | --- | --- | +| `protocol` | [`WinRMHttpProtocolEnum`](apidocs/org/metricshub/winrm/WinRMHttpProtocolEnum.html) | `HTTP` or `HTTPS`. `null` defaults to `HTTP`. | +| `hostname` | `String` | Host name or IP address. **Mandatory.** | +| `port` | `Integer` | `null` uses the protocol default (5985 for HTTP, 5986 for HTTPS). | +| `username` | `String` | `DOMAIN\user` or `user`. **Mandatory.** See [Authentication](authentication.html). | +| `password` | `char[]` | The password. | +| `namespace` | `String` | WMI namespace. `null` or blank defaults to `ROOT\CIMV2`. Backslashes and forward slashes are both accepted. | +| `wqlQuery` | `String` | The WQL query. **Mandatory.** | +| `timeout` | `long` | Timeout in milliseconds. Must be **greater than zero** (an `IllegalArgumentException` is thrown otherwise). See [Timeouts and Errors](timeouts-and-errors.html). | +| `ticketCache` | `java.nio.file.Path` | Kerberos ticket cache path. `null` for NTLM. See [Authentication](authentication.html). | +| `authentications` | `List<`[`AuthenticationEnum`](apidocs/org/metricshub/winrm/service/client/auth/AuthenticationEnum.html)`>` | Requested schemes. `null` or empty means NTLM only. | + +## Reading the result + +The returned [`WinRMWqlExecutor`](apidocs/org/metricshub/winrm/wql/WinRMWqlExecutor.html) exposes: + +| Method | Returns | Description | +| --- | --- | --- | +| `getHeaders()` | `List` | The property (column) names. | +| `getRows()` | `List>` | One `List` per row, with values in the same order as the headers. | +| `getExecutionTime()` | `long` | Wall-clock time of the whole call, in milliseconds. | + +```java +List headers = result.getHeaders(); // e.g. [Name, State] +for (List row : result.getRows()) { + System.out.println(headers + " = " + row); +} +``` + +### Property order and case + +* When you select explicit properties (`SELECT Name, State FROM ...`), the headers keep the **order + of the query** and the **exact case reported by WMI**. +* With `SELECT * FROM ...`, the properties are returned in **alphabetical order** (case-insensitive). +* If the query returns no rows, the headers fall back to the property names exactly as written in + the query (WMI's own casing cannot be recovered from an empty result set). + +## Supported WQL syntax + +The client validates that the query is a simple `SELECT`: + +```sql +SELECT * FROM Win32_OperatingSystem +SELECT Name, State, StartMode FROM Win32_Service +SELECT Name FROM Win32_Process WHERE Name = 'explorer.exe' +``` + +The grammar is a single `SELECT` of either `*` or a comma-separated property list, a `FROM` clause, +and an optional `WHERE` clause. Joins, sub-selects, and other advanced constructs are not part of +the supported syntax. An invalid query raises a +[`WqlQuerySyntaxException`](apidocs/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.html). + +## Choosing a namespace + +Most Windows classes live under the default `ROOT\CIMV2` namespace. To query a different one — for +example `ROOT\Microsoft\SqlServer` or `ROOT\WMI` — pass it as the `namespace` argument. Both +`ROOT\WMI` and `ROOT/WMI` are accepted. + +## Exceptions + +`executeWql(...)` declares three checked exceptions: + +| Exception | When | +| --- | --- | +| [`WqlQuerySyntaxException`](apidocs/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.html) | The query does not match the supported `SELECT` syntax. | +| [`WinRMException`](apidocs/org/metricshub/winrm/exceptions/WinRMException.html) | Any WinRM/WSMan problem on the remote host (authentication, WMI error, protocol fault, ...). | +| `java.util.concurrent.TimeoutException` | The operation did not complete within `timeout`. | + +See [Timeouts and Errors](timeouts-and-errors.html) for the full exception surface. + +## From the command line + +The standalone jar exposes the same capability through the `wql` subcommand, printing one compact +UTF-8 JSON object per row ([JSON Lines](https://jsonlines.org/)): + +```bash +java -jar ${project.artifactId}-${project.version}-standalone.jar \ + -h server.example.com -u 'DOMAIN\user' -pf password.txt --ntlm \ + wql 'SELECT Name, State FROM Win32_Service' +``` + +Property order follows the WinRM response; diagnostics go only to standard error. diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css index e85911b..24a024c 100644 --- a/src/site/resources/css/site.css +++ b/src/site/resources/css/site.css @@ -64,7 +64,7 @@ --heading-font: "Poppins", sans-serif; --content-font: "Poppins", sans-serif; - --content-font-size: medium; + --content-font-size: 15px; --banner-font-size: 40px; --banner-font-weight: 800; diff --git a/src/site/site.xml b/src/site/site.xml index d042bbf..c4a25f5 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -1,36 +1,71 @@ - - + + org.sentrysoftware.maven sentry-maven-skin - 6.4.01 + 8.0.00 true - winrm + winrm, wql, wmi, windows, ntlm, kerberos, java, remote management + + + WinRM Java Client on GitHub + https://github.com/metricshub/winrm-java + fa-brands fa-github + + + + + Issue Tracker + https://github.com/metricshub/winrm-java/issues + + + Licenses + licenses.html + + + Releases + https://github.com/metricshub/winrm-java/releases + + - - images/metricshub-logo-only.png - https://metricshub.org + + MetricsHub - + + + - - - - + + + + + + + + + + + + + + + + - \ No newline at end of file + From ff9f856fed03a2e308f42546baf21a01f446eac9 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Fri, 24 Jul 2026 21:17:21 +0200 Subject: [PATCH 2/2] Address Codex review: qualify API-compat claim and timeout semantics - migrating-from-1x.md / README.md / CHANGELOG.md: stop claiming the whole public API is unchanged. A few CXF/SMB-only public types (WinRMService, SmbTempShare, KerberosCredentialsException, WindowsRemoteProcessUtils.copyLocalFilesToShare) were removed, so code that referenced them will not compile. Qualify the claim to the documented entry points and rename the migration section to "Removed types", noting the compile break. - timeouts-and-errors.md: the timeout is not a single whole-operation budget. In WinRMCommandExecutor.execute without files to copy, executeCommand() receives the full timeout after code-page detection already consumed part of it, so chained slow steps can exceed one timeout. Document the actual per-step behavior instead. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 5 +++-- README.md | 3 ++- src/site/markdown/migrating-from-1x.md | 11 ++++++----- src/site/markdown/timeouts-and-errors.md | 11 +++++++---- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be755d..e77c21d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,8 +45,9 @@ Consequences: ### ⚠️ Breaking — the CXF backend was removed Version 2.0.0 removes the legacy Apache CXF backend. The dependency-free client introduced in the -previous release is the only implementation; the public API is unchanged, so calling code is -unaffected. Consequences: +previous release is the only implementation; the documented entry points are unchanged, so typical +calling code is unaffected (though a few CXF/SMB-only public types were removed — see Removed). +Consequences: - **WinRM over HTTPS with self-signed certificates**: unlike the CXF-based client — which silently trusted every TLS certificate and skipped hostname verification — this client **validates the diff --git a/README.md b/README.md index 0c5d6dc..1823662 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ The Windows Remote Management (WinRM) Java Client is a library that enables to: > ## ⚠️ Upgrading from 1.x > > Version 2.0.0 **removed the legacy Apache CXF backend**: the dependency-free **light** client is -> the only implementation (same public API — calling code is unaffected). The main consequence: +> the only implementation (same documented entry points; a few CXF/SMB-only public types were +> removed). The main consequence: > > * Unlike the CXF-based client, which silently trusted every TLS certificate, the light client > **validates the server certificate and verifies the hostname by default**. diff --git a/src/site/markdown/migrating-from-1x.md b/src/site/markdown/migrating-from-1x.md index 62f8c1e..797be58 100644 --- a/src/site/markdown/migrating-from-1x.md +++ b/src/site/markdown/migrating-from-1x.md @@ -6,9 +6,9 @@ description: What changed in WinRM Java Client 2.0.0 and how to upgrade from the Version 2.0.0 is a major cleanup: the legacy Apache CXF backend and the SMB-based file copy are -gone, leaving a **dependency-free** client. The **public API is unchanged**, so calling code -compiles and runs without modification — but two runtime behaviors changed, and you should read this -page before upgrading. +gone, leaving a **dependency-free** client. The **documented entry points are unchanged**, so typical +calling code is unaffected — but a few CXF/SMB-only public types were removed (see the *Removed types* +section below), and two runtime behaviors changed. Read this page before upgrading. ## TL;DR @@ -62,9 +62,10 @@ JAX-WS / JAXB stack is gone, and so are `smbj`, BouncyCastle, SLF4J, `mbassador` standalone CLI jar shrinks from around 9 MB to a few hundred kB, and the library no longer references any logging API — problems are reported through [exceptions](timeouts-and-errors.html) only. -## Removed classes +## Removed types -If your code referenced these internal or SMB/CXF-only types, they no longer exist: +These types and members were public in 1.x but are **removed** in 2.0.0. Code that referenced them +will not compile against 2.0.0 (all were CXF- or SMB-specific): * `KerberosCredentialsException` — was thrown only by CXF internals. * `SmbTempShare` and `WindowsRemoteProcessUtils.copyLocalFilesToShare(...)` — replaced by the diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index 100e44e..3c8ac57 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -11,10 +11,13 @@ Both `executeWql(...)` and `WinRMCommandExecutor.execute(...)` take a `timeout` **milliseconds**. The value must be **greater than zero** — passing `0` or a negative value throws an `IllegalArgumentException` immediately. -The timeout is a **budget for the whole operation**, not for a single network round trip. Opening -the connection, detecting the remote code page, copying files, and running the query or command all -draw from it. When the budget is exhausted, the call throws -`java.util.concurrent.TimeoutException`. +The `timeout` applies to the remote operation and to the preparatory steps the client performs — +opening the connection, detecting the remote code page, and (for commands) copying files. It is +enforced **per step**, not as a single cumulative deadline: each major step is given up to `timeout` +to complete, so a call that chains several slow steps can take longer than one `timeout` overall +before a step finally exceeds its own limit and throws `java.util.concurrent.TimeoutException`. (When +files are copied, the later stages are budgeted against the time already spent, so that path stays +close to a single overall deadline.) ```java try {