See Project Documentation and the Javadoc 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:
- 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.
Version 2.0.0 removed the legacy Apache CXF backend: the dependency-free light client is 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. WinRM-over-HTTPS connections to hosts with self-signed or otherwise untrusted certificates 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).- The collections returned by
WinRMWqlExecutor.getHeaders()/getRows()andWqlQuery.getSelectedProperties()/getSubPropertiesMap()are now unmodifiable views (andWinRMWqlExecutorcopies the lists passed to its constructor): callers that mutated the returned collections must now copy them first.- The remote transfer directory used by
upload(...)/localFileToCopyListis now<windir>\Temp\winrm-upload-<CLIENT-COMPUTER-NAME>instead of theSEN_ShareFor_<CLIENT-COMPUTER-NAME>$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 publicWindowsTempShare.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 ofWindowsRemoteProcessUtils.buildNewOutputFileName()changed fromSEN_...towinrm-...accordingly.
WinRM must be enabled on the targeted Windows host, and the account must have sufficient privileges:
- Windows Server 2012 and later have WinRM enabled by default — service running, HTTP listener
on port 5985, firewall open,
NegotiateandKerberosauthentication enabled. An administrator account works with no host-side configuration. - Windows 10 / 11 (and other client editions) do not: run
winrm quickconfigorEnable-PSRemoting -Forcefrom an elevated prompt. Being domain-joined does not enable WinRM — being a Server edition does. - Privileges: domain administrators, any domain account in the host's local
Administrators, and the built-in localAdministratorwork as-is. Other local administrator accounts are denied by UAC remote token filtering unlessLocalAccountTokenFilterPolicyis set to 1. Non-administrator accounts need an explicit grant on the WinRM listener (RootSDDL), plus — only if they run WQL queries — WMI grants (WinRMRemoteWMIUsers__and namespace rights). An account that only runs commands never reaches WMI and needs nothing there. AllowUnencrypted,Basic,CredSSPandTrustedHostsdo not need to be changed: over plain HTTP the payload is protected by NTLM message encryption, andTrustedHostsis a Windows-client setting that a Java client never reads.
The full prerequisites — enabling WinRM over HTTP or HTTPS, Group Policy, firewall rules, the privileges each operation requires, configuring a non-administrator account, host quotas, and a symptom-to-cause troubleshooting table — are documented on the Preparing the Windows Host page.
The fluent WinRMClient is the entry point of the library: one client authenticates once and can
run any number of WQL queries and commands over the same connection. All failures are reported
through the unchecked WinRMClientException hierarchy (WinRMAuthenticationException,
WinRMFaultException with the WSMan fault code and detail as fields, WinRMTimeoutException,
WqlSyntaxException).
import java.nio.file.Path;
import java.time.Duration;
import org.metricshub.winrm.*;
try (WinRMClient client = WinRMClient.builder("server01.acme.com")
.credentials("ACME\\admin", password) // char[], wiped by you afterward
.timeout(Duration.ofSeconds(30)) // default for all operations
.build()) {
// WQL query
WqlResult services = client.wql("SELECT Name, State FROM Win32_Service").execute();
for (WqlRow row : services) {
System.out.println(row.string("Name") + " is " + row.string("State"));
}
// Remote command
CommandResult result = client.command("ipconfig /all").execute();
System.out.println(result.stdout());
// Copy a file to the host (through the WinRM channel itself — no SMB)
client.uploadFile(Path.of("collect.ps1"), "C:\\Windows\\Temp\\collect.ps1");
}Connection-scoped options on the builder: https(), port(int),
authentication(AuthScheme.KERBEROS, AuthScheme.NTLM) (ordered fallback; NTLM is the default),
ticketCache(Path), namespace(String), trustAllCertificates() (per-client alternative to the
org.metricshub.winrm.tls.insecure system property; insecure, testing only), and
sslContext(SSLContext) for a dedicated trust store.
Per-operation options: namespace(...), timeout(...), and for WQL enumeration tuning
pageSize(int) (WS-Enumeration MaxElements, 32000 by default) and pullTimeout(Duration)
(MaxTime per Pull). Commands accept workingDirectory(String), charset(Charset) (see
Character encoding), and upload(Path...) to copy local script files and
rewrite the command to reference the remote copies.
The remote command shell is created with console code page 65001, so command output is decoded as UTF-8 — whatever the remote machine's locale. Accented and non-Latin characters come back exactly as the remote host wrote them, and nothing needs to be detected or configured:
// On a French Windows host:
client.command("vol").execute().stdout(); // " Le numéro de série du volume est …"A few legacy console tools — net.exe and chcp.com are the known ones — ignore the console code
page and write text pre-converted to the machine's OEM code page. Their accented characters
cannot be decoded as UTF-8 (they arrive as U+FFFD); decode such a command with the matching OEM
charset instead:
client.command("net user Administrateur")
.charset(Charset.forName("IBM850")) // French/Western European OEM code page
.execute();WQL results are unaffected: they travel as UTF-8 inside the SOAP envelope.
Changed in 2.0.00 — earlier versions ran a
SELECT CodeSet FROM Win32_OperatingSystemquery before each command and decoded the output with the code page it reported. That is the remote machine's ANSI code page, which never matched what the shell emitted, so all non-ASCII output was mangled on non-English hosts.WindowsRemoteProcessUtils.getWindowsEncodingCharset()is deprecated as a result, and the extra query is gone.
Both operations also have a streaming terminal for large result sets and long-running commands — everything upstream (authentication, TLS, namespace, options) is shared with the blocking terminals:
// WQL rows are pulled from the server page by page as the stream advances:
// memory stays bounded by one page (pageSize(int)), not by the whole result set.
try (Stream<WqlRow> rows = client.wql("SELECT * FROM Win32_NTLogEvent").stream()) {
rows.filter(r -> "Error".equals(r.string("Type")))
.limit(100)
.forEach(System.out::println);
}
// Commands can be consumed while they run, java.lang.Process-style:
try (RemoteProcess p = client.command("wevtutil qe System /f:text").start()) {
try (BufferedReader out = p.stdout()) {
out.lines().forEach(System.out::println);
}
int exitCode = p.waitFor(); // or waitFor(Duration) for a deadline
}
// Commands can be fed standard input — pre-supplied (stdin(String|Path|InputStream),
// the remote equivalent of a `< file` redirection)...
CommandResult sorted = client.command("sort").stdin(Path.of("data.txt")).execute();
// ...or written interactively through the process handle (flush() delivers, close() is EOF):
try (RemoteProcess p = client.command("some-repl.exe").stdin().start()) {
try (BufferedWriter in = p.stdin()) {
in.write("first request\n");
in.flush();
System.out.println(p.stdout().readLine());
}
p.waitFor();
}
// Middle ground: tail the output live, keep the blocking terminal and its full result.
client.command("longRunningThing.exe")
.onStdout(chunk -> log.info(chunk))
.onStderr(chunk -> log.warn(chunk))
.execute();Streams and processes must be closed (try-with-resources): they hold the client's serial
connection while open, and closing early releases the server-side enumeration (WS-Enumeration
Release) or terminates the remote command (WinRM terminate Signal). For the streaming
terminals the configured timeout is an inactivity timeout — the longest silence tolerated from
the server between two responses — not an overall deadline, so long tails can stream indefinitely.
Output is decoded incrementally: a multibyte character split across protocol chunks is decoded
correctly.
The pre-existing static helpers (WinRMWqlExecutor.executeWql(...),
WinRMCommandExecutor.execute(...)) keep working unchanged — see Legacy API below.
The client has zero runtime dependencies (no Apache CXF / JAX-WS / JAXB, no BouncyCastle, no
SLF4J — problems are reported through exceptions only) and is immune by construction to JAXP
ServiceLoader conflicts (it uses the JDK-default XML factories). It supports NTLM over HTTP
(with message encryption) and HTTPS and Kerberos (SPNEGO) over HTTPS.
Files passed to upload(...) (or localFileToCopyList in the legacy API) are copied to the
remote host through the WinRM channel itself (chunked base64 through the command shell,
decoded with certutil and verified with a digest): no SMB, no TCP port 445, no administrative
share — and it works from any client OS. A file already present on the remote host with an
identical digest is not transferred again. This transport is designed for small script files, not
bulk data. The full mechanics — destination directory, temporary files, integrity verification,
cleanup, and command-line substitution — are documented on the
File Transfers page. Over HTTPS it validates the certificate and verifies the hostname by default (see the
upgrade warning above); trustAllCertificates() on the builder or
-Dorg.metricshub.winrm.tls.insecure=true trusts all certificates (insecure, testing only).
Kerberos uses the ambient Kerberos configuration (krb5.conf / -Djava.security.krb5.*) unless
the command-line KDC and realm options described below are used.
The static one-shot helpers that predate WinRMClient remain available and unchanged, with their
checked exceptions: WinRMWqlExecutor.executeWql(...) and WinRMCommandExecutor.execute(...).
They open a connection, run one operation, and close it — prefer WinRMClient for anything that
runs more than one operation against the same host.
Every build produces the regular library JAR and an additional self-contained executable:
target/winrm-java-<version>.jar
target/winrm-java-<version>-standalone.jar
Run a WQL query:
java -jar target/winrm-java-<version>-standalone.jar \
--hostname server.example.net --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):
java -jar target/winrm-java-<version>-standalone.jar \
-h server.example.net -u Administrator -pf password.txt --https \
exec ipconfig /allOpen an interactive session on the remote host (winrs-style, line-oriented):
java -jar target/winrm-java-<version>-standalone.jar \
-h server.example.net -u 'DOMAIN\user' -pf password.txt shellUse --help for the option list and --version for the build version. The CLI is built on the
streaming API: WQL rows are written to stdout as UTF-8 JSON Lines as
the enumeration pages arrive, and remote command stdout and stderr are forwarded live to the
corresponding local streams while the command runs — with local stdin forwarded to the remote
command when it is piped or redirected (... command sort < data.txt). Diagnostics go only to
stderr, and the exit codes are stable for scripting.
The full manual — options, password handling, Kerberos configuration, streaming and timeout semantics, exit codes — is the Command-Line Client page.
This is a simple Maven project. Build with:
mvn verifyThe build includes in-process protocol tests (WsmanProtocolTest) that exercise the client's
full WSMan path — NTLM handshake, message encryption, multipart/encrypted framing, WQL
Enumerate/Pull paging, the command shell lifecycle, and fault mapping — against a fake WSMan
server, so no Windows host is needed in CI.
WinRMLiveTest runs a WQL query and a command against a real WinRM host (the successor of
the pre-2.0.0 CXF-vs-light differential harness). It is skipped unless winrm.live.host is set:
mvn test -Dtest=WinRMLiveTest \
-Dwinrm.live.host=myhost.example.com \
-Dwinrm.live.protocol=https \
-Dwinrm.live.username='MYDOMAIN\myuser' \
-Dwinrm.live.password-file=/path/to/password.txtOptional properties: winrm.live.port, winrm.live.password (inline), winrm.live.namespace,
winrm.live.wql, winrm.live.command, and winrm.live.tls.insecure=true (skip TLS validation
for hosts with self-signed certificates).
The artifact is deployed to Sonatype's Maven Central.
The actual repository URL is https://s01.oss.sonatype.org/, with server Id ossrh and requires credentials to deploy
artifacts manually.
But it is strongly recommended to only use GitHub Actions "Release to Maven Central" to perform a release:
- Manually trigger the "Release" workflow
- Specify the version being released and the next version number (SNAPSHOT)
- Release the corresponding staging repository on Sonatype's Nexus server
- Merge the PR that has been created to prepare the next version
License is Apache-2. Each source file must include the Apache-2 header (build will fail otherwise). To update source files with the proper header, simply execute the below command:
mvn license:update-file-header