From 1e723b7729070fafb36508b4eb5d30e17f9ec211 Mon Sep 17 00:00:00 2001 From: Jan Philipp Date: Wed, 20 Oct 2021 15:38:24 +0200 Subject: [PATCH 1/8] Allow overriding codepage option (opt-in utf8) --- .../cloudsoft/winrm4j/client/WinRmClient.java | 4 +- .../winrm4j/client/WinRmClientBuilder.java | 23 ++++++++++ .../io/cloudsoft/winrm4j/winrm/WinRmTool.java | 46 ++++++++++++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java index 36d1c843..7514da7c 100644 --- a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java +++ b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java @@ -96,6 +96,7 @@ public class WinRmClient implements AutoCloseable { private final Locale locale; private final Map environment; private final PayloadEncryptionMode payloadEncryptionMode; + private final int codePage; private final WinRm service; private AsyncHttpEncryptionAwareConduitFactory factoryToCleanup; @@ -205,6 +206,7 @@ public Builder(String endpoint, String authenticationScheme) { this.workingDirectory = builder.workingDirectory; this.locale = builder.locale; + this.codePage = builder.codePage; this.operationTimeout = toDuration(builder.operationTimeout); this.retryReceiveAfterOperationTimeout = builder.retryReceiveAfterOperationTimeout; this.environment = builder.environment; @@ -534,7 +536,7 @@ public ShellCommand createShell() { optSetCreate.getOption().add(optNoProfile); OptionType optCodepage = new OptionType(); optCodepage.setName("WINRS_CODEPAGE"); - optCodepage.setValue("437"); + optCodepage.setValue(Integer.toString(codePage)); optSetCreate.getOption().add(optCodepage); ResourceCreated resourceCreated = null; diff --git a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClientBuilder.java b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClientBuilder.java index 23e412a6..850372db 100644 --- a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClientBuilder.java +++ b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClientBuilder.java @@ -22,6 +22,19 @@ public class WinRmClientBuilder { private static final java.util.Locale DEFAULT_LOCALE = java.util.Locale.US; + + /** + * Older Windows systems may have issues with a modern UTF-8, so the default sticks with + * the legacy codepage 437 which is referred by en-us. However, for modern systems, + * the UTF-8 variant 65001 may be more suitable. + */ + public static final int DEFAULT_CODEPAGE = 437; + + /** + * Means using a UTF-8 codepage. If the remote system is older, this may have issues. + */ + public static final int UTF8_CODEPAGE = 65001; + /** * Timeout applied by default on client side for the opening of the socket (0 meaning infinite waiting). */ @@ -52,6 +65,7 @@ public class WinRmClientBuilder { protected String password; protected String workingDirectory; protected Locale locale; + protected int codePage; protected long operationTimeout; protected Predicate retryReceiveAfterOperationTimeout; protected long connectionTimeout; @@ -79,6 +93,7 @@ public class WinRmClientBuilder { this.endpoint = WinRmClient.checkNotNull(endpoint, "endpoint"); authenticationScheme(AuthSchemes.NTLM); locale(DEFAULT_LOCALE); + codePage(DEFAULT_CODEPAGE); operationTimeout(DEFAULT_OPERATION_TIMEOUT); retryReceiveAfterOperationTimeout(alwaysRetryReceiveAfterOperationTimeout()); connectionTimeout(DEFAULT_CONNECTION_TIMEOUT); @@ -116,6 +131,14 @@ public WinRmClientBuilder locale(java.util.Locale locale) { return this; } + /** + * @param codePage The shell's codePage + */ + public WinRmClientBuilder codePage(int codePage) { + this.codePage = codePage; + return this; + } + /** * If operations cannot be completed in a specified time, * the service returns a fault so that a client can comply with its obligations. diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 2f3cead9..6b3f608c 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -41,6 +41,16 @@ public class WinRmTool { public static final int DEFAULT_WINRM_HTTPS_PORT = 5986; public static final Boolean DEFAULT_SKIP_COMMAND_SHELL = Boolean.FALSE; + /** + * @see WinRmClientBuilder#DEFAULT_CODEPAGE + */ + public static final int DEFAULT_CODEPAGE = WinRmClientBuilder.DEFAULT_CODEPAGE; + + /** + * @see WinRmClientBuilder#UTF8_CODEPAGE + */ + public static final int UTF8_CODEPAGE = WinRmClientBuilder.UTF8_CODEPAGE; + // TODO consider make them non-final and accessing the properties directly from builder. // This impose moving getEndpointUrl() to the WinRmTool. private final String address; @@ -64,6 +74,7 @@ public class WinRmTool { private final WinRmClientContext context; private final boolean requestNewKerberosTicket; private PayloadEncryptionMode payloadEncryptionMode; + private int codePage = DEFAULT_CODEPAGE; public static class Builder { private String authenticationScheme = AuthSchemes.NTLM; @@ -83,6 +94,7 @@ public static class Builder { private WinRmClientContext context; private boolean requestNewKerberosTicket; private PayloadEncryptionMode payloadEncryptionMode; + private int codePage = DEFAULT_CODEPAGE; private static final Pattern matchPort = Pattern.compile(".*:(\\d+)$"); @@ -173,12 +185,17 @@ public Builder payloadEncryptionMode(PayloadEncryptionMode x) { return this; } + public Builder codePage(int codePage) { + this.codePage = codePage; + return this; + } + public WinRmTool build() { return new WinRmTool(getEndpointUrl(address, useHttps, port), domain, username, password, authenticationScheme, allowChunking, disableCertificateChecks, workingDirectory, environment, hostnameVerifier, sslSocketFactory, sslContext, - context, requestNewKerberosTicket, payloadEncryptionMode); + context, requestNewKerberosTicket, payloadEncryptionMode, codePage); } // TODO remove arguments when method WinRmTool.connect() is removed @@ -219,12 +236,36 @@ private static String getEndpointUrl(String address, Boolean useHttps, Integer p } } + @Deprecated /** @deprecated use bigger constructor */ + private WinRmTool(String address, String domain, String username, + String password, String authenticationScheme, + boolean allowChunking, boolean disableCertificateChecks, String workingDirectory, + Map environment, HostnameVerifier hostnameVerifier, + SSLSocketFactory sslSocketFactory, SSLContext sslContext, WinRmClientContext context, + boolean requestNewKerberosTicket) { + this.allowChunking = allowChunking; + this.disableCertificateChecks = disableCertificateChecks; + this.address = address; + this.domain = domain; + this.username = username; + this.password = password; + this.authenticationScheme = authenticationScheme; + this.workingDirectory = workingDirectory; + this.environment = environment; + this.hostnameVerifier = hostnameVerifier; + this.sslSocketFactory = sslSocketFactory; + this.sslContext = sslContext; + this.context = context; + this.requestNewKerberosTicket = requestNewKerberosTicket; + } + private WinRmTool(String address, String domain, String username, String password, String authenticationScheme, boolean allowChunking, boolean disableCertificateChecks, String workingDirectory, Map environment, HostnameVerifier hostnameVerifier, SSLSocketFactory sslSocketFactory, SSLContext sslContext, WinRmClientContext context, - boolean requestNewKerberosTicket, PayloadEncryptionMode payloadEncryptionMode) { + boolean requestNewKerberosTicket, PayloadEncryptionMode payloadEncryptionMode, + int codePage) { this.allowChunking = allowChunking; this.disableCertificateChecks = disableCertificateChecks; this.address = address; @@ -240,6 +281,7 @@ private WinRmTool(String address, String domain, String username, this.context = context; this.requestNewKerberosTicket = requestNewKerberosTicket; this.payloadEncryptionMode = payloadEncryptionMode; + this.codePage = codePage; } /** From 93b53ec3b1fbccc4ac98a29913b5527940840ffc Mon Sep 17 00:00:00 2001 From: Jan Philipp Date: Wed, 24 Nov 2021 11:11:51 +0100 Subject: [PATCH 2/8] Ensure active shell's CP will be used for wrapped PS --- winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 6b3f608c..98a0fb18 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -463,7 +463,7 @@ public WinRmToolResponse executePs(String psCommand, Writer out, Writer err) { } public WinRmToolResponse executePs(String psCommand, Boolean skipCommandShell, Writer out, Writer err) { - return executeCommand("powershell", Arrays.asList("-encodedcommand", compileBase64(psCommand)), skipCommandShell, out, err); + return executeCommand("chcp " + codePage + " & powershell", Arrays.asList("-encodedcommand", compileBase64(psCommand)), skipCommandShell, out, err); } /** From 4b40d3834a6fcd65ab1fdc99d5d9802080e34ae9 Mon Sep 17 00:00:00 2001 From: Jan Philipp Date: Wed, 1 Dec 2021 13:47:03 +0100 Subject: [PATCH 3/8] Reduce codepage: Ensure active shell's CP will be used for wrapped PS --- winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 98a0fb18..6e1ca343 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -463,7 +463,7 @@ public WinRmToolResponse executePs(String psCommand, Writer out, Writer err) { } public WinRmToolResponse executePs(String psCommand, Boolean skipCommandShell, Writer out, Writer err) { - return executeCommand("chcp " + codePage + " & powershell", Arrays.asList("-encodedcommand", compileBase64(psCommand)), skipCommandShell, out, err); + return executeCommand("chcp " + codePage + " > NUL & powershell", Arrays.asList("-encodedcommand", compileBase64(psCommand)), skipCommandShell, out, err); } /** From 3dd78bcdea07718d4c1c771d0ecd2975e761221e Mon Sep 17 00:00:00 2001 From: Jan Philipp Date: Thu, 14 Oct 2021 18:03:55 +0200 Subject: [PATCH 4/8] Add WinRmTool#buildClient utility --- .../io/cloudsoft/winrm4j/winrm/WinRmTool.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 2f3cead9..664efbea 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -332,6 +332,59 @@ public WinRmToolResponse executeCommand(String command, List args) { return executeCommand(command, args, DEFAULT_SKIP_COMMAND_SHELL, null, null); } + public WinRmClient buildClient(Writer out, Writer err) { + WinRmClient.checkNotNull(out, "Out Writer"); + WinRmClient.checkNotNull(err, "Err Writer"); + WinRmClientBuilder builder = WinRmClient.builder(address); + builder.authenticationScheme(authenticationScheme); + if (operationTimeout != null) { + builder.operationTimeout(operationTimeout); + } + if (retryReceiveAfterOperationTimeout != null) { + builder.retryReceiveAfterOperationTimeout(retryReceiveAfterOperationTimeout); + } + if (connectionTimeout != null) { + builder.connectionTimeout(connectionTimeout); + } + if (receiveTimeout != null) { + builder.receiveTimeout(receiveTimeout); + } + if (username != null && password != null) { + builder.credentials(domain, username, password); + } + if (disableCertificateChecks) { + LOG.trace("Disabled check for https connections " + this); + builder.disableCertificateChecks(disableCertificateChecks); + } + if (hostnameVerifier != null) { + builder.hostnameVerifier(hostnameVerifier); + } + if (sslSocketFactory != null) { + builder.sslSocketFactory(sslSocketFactory); + } + if (sslContext != null) { + builder.sslContext(sslContext); + } + if (workingDirectory != null) { + builder.workingDirectory(workingDirectory); + } + if (environment != null) { + builder.environment(environment); + } + if (failureRetryPolicy != null) { + builder.failureRetryPolicy(failureRetryPolicy); + } + if (context != null) { + builder.context(context); + } + if (requestNewKerberosTicket) { + builder.requestNewKerberosTicket(requestNewKerberosTicket); + } + builder.payloadEncryptionMode(payloadEncryptionMode); + + return builder.build(); + } + public WinRmToolResponse executeCommand(String command, Writer out, Writer err) { return executeCommand(command, null, DEFAULT_SKIP_COMMAND_SHELL, out, err); } From 82b8f04950d023cd6fee1dd9d30d61901b55ed23 Mon Sep 17 00:00:00 2001 From: Andrew Dodd <47298244+AndrewDodd42@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:50:12 +0100 Subject: [PATCH 5/8] Added allowChunking into buildClient() method. --- .../src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 664efbea..79a84ed9 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -356,6 +356,9 @@ public WinRmClient buildClient(Writer out, Writer err) { LOG.trace("Disabled check for https connections " + this); builder.disableCertificateChecks(disableCertificateChecks); } + if (allowChunking) { + builder.allowChunking(allowChunking); + } if (hostnameVerifier != null) { builder.hostnameVerifier(hostnameVerifier); } From 48726496a67d24d1b3701fbc24fbba5e7760509d Mon Sep 17 00:00:00 2001 From: Andrew Dodd <47298244+AndrewDodd42@users.noreply.github.com> Date: Tue, 23 Aug 2022 16:56:50 +0100 Subject: [PATCH 6/8] Added codepage to executeCommand() builder. --- winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java | 1 + 1 file changed, 1 insertion(+) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 6e1ca343..302cc4e2 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -430,6 +430,7 @@ public WinRmToolResponse executeCommand(String command, List args, Boole builder.requestNewKerberosTicket(requestNewKerberosTicket); } builder.payloadEncryptionMode(payloadEncryptionMode); + builder.codePage(codePage); WinRmToolResponse winRmToolResponse; From 1b30a8b55c044f1f6f2c5a1a65e438465728b010 Mon Sep 17 00:00:00 2001 From: Jan Philipp Date: Tue, 19 Oct 2021 14:21:07 +0200 Subject: [PATCH 7/8] Add client support for enumerating a resource Basically, this adds these: * generated service/client `WinRm` adapter gets support for the enumerate a resourceUri using a filter, and its corresponding enumration-pull operation * the high-level client `WinRmClient` gets support for the aggregated operation enumerateAndPull for fetching all elements using a reosurceUri and a filter * as an easy-to-use operation, the client also gets support for `runWql(namespace, query)` which allows executing a wmi query and fetching the results (using the `enumerateAndPull`) Ref https://www.w3.org/Submission/WS-Enumeration/ Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/10cfb548-845b-4979-aae3-3f39d7080e17 Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/b79bcdd9-125c-49e0-8a4f-bac4ce878592 Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/dfe7084a-dea6-4f7f-b35c-cc7d1ad8060d Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/939e283a-5518-4e43-9d9f-4f0b1a199815 Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/0fddd40a-b5c4-4a63-a0bf-3ff9966e9e3e Ref https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/8923a1bb-ea8b-49cb-8495-5f2612e7a0f9 --- client/src/jaxws/bindings.xml | 6 + .../winrm4j/client/EnumerateCommand.java | 235 ++++++++++++++++++ .../cloudsoft/winrm4j/client/WinRmClient.java | 37 +++ .../client/RetryingProxyHandlerTest.java | 18 ++ .../io/cloudsoft/winrm4j/service/WinRm.java | 51 ++++ .../service/enumerate/EnumerateRequest.java | 87 +++++++ .../service/enumerate/EnumerateResponse.java | 49 ++++ .../winrm4j/service/enumerate/Items.java | 22 ++ .../service/enumerate/PullRequest.java | 60 +++++ .../service/enumerate/PullResponse.java | 50 ++++ 10 files changed, 615 insertions(+) create mode 100644 client/src/main/java/io/cloudsoft/winrm4j/client/EnumerateCommand.java create mode 100644 service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateRequest.java create mode 100644 service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateResponse.java create mode 100644 service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/Items.java create mode 100644 service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullRequest.java create mode 100644 service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullResponse.java diff --git a/client/src/jaxws/bindings.xml b/client/src/jaxws/bindings.xml index cf7069ad..a7a86d08 100644 --- a/client/src/jaxws/bindings.xml +++ b/client/src/jaxws/bindings.xml @@ -26,5 +26,11 @@ + + + + + diff --git a/client/src/main/java/io/cloudsoft/winrm4j/client/EnumerateCommand.java b/client/src/main/java/io/cloudsoft/winrm4j/client/EnumerateCommand.java new file mode 100644 index 00000000..baab3e1e --- /dev/null +++ b/client/src/main/java/io/cloudsoft/winrm4j/client/EnumerateCommand.java @@ -0,0 +1,235 @@ +package io.cloudsoft.winrm4j.client; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.ws.soap.SOAPFaultException; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import io.cloudsoft.winrm4j.client.enumeration.EnumerateResponse; +import io.cloudsoft.winrm4j.client.enumeration.PullResponse; +import io.cloudsoft.winrm4j.client.wsman.Enumerate; +import io.cloudsoft.winrm4j.client.wsman.Filter; +import io.cloudsoft.winrm4j.client.wsman.Items; +import io.cloudsoft.winrm4j.client.wsman.Locale; +import io.cloudsoft.winrm4j.client.wsman.OptionSetType; +import io.cloudsoft.winrm4j.client.wsman.Pull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import static io.cloudsoft.winrm4j.client.WinRmClient.MAX_ENVELOPER_SIZE; + +public class EnumerateCommand implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(EnumerateCommand.class.getName()); + + /** + * If no output is available before the wsman:OperationTimeout expires, the server MUST return a WSManFault with the Code attribute equal to "2150858793" + * https://msdn.microsoft.com/en-us/library/cc251676.aspx + */ + static final String WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED = "2150858793"; + + private final WinRm winrm; + private final String resourceUri; + private final String sessionId; + private final long maxElements; + private final Supplier operationTimeout; + private final Supplier locale; + private final Predicate retryReceiveAfterOperationTimeout; + + private final DocumentBuilder documentBuilder; + + public EnumerateCommand(final WinRm winrm, + final String resourceUri, + final long maxElements, + final Supplier operationTimeout, + final Supplier locale, + final Predicate retryReceiveAfterOperationTimeout) { + this.winrm = winrm; + this.resourceUri = resourceUri; + this.sessionId = "uuid:" + UUID.randomUUID(); + this.maxElements = maxElements; + this.operationTimeout = operationTimeout; + this.locale = locale; + this.retryReceiveAfterOperationTimeout = retryReceiveAfterOperationTimeout; + try { + this.documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IllegalStateException("Failed to create instance of DocumentBuilder"); + } + } + + public List execute(final String filter, final String dialect) { + final EnumerateResponse enumerateResponse = enumerate(filter, dialect); + final List result = new ArrayList<>(); + collectAndIterateEnumeratedResults(result, new EnumerationPullState( + resourceUri, + maxElements, + enumerateResponse.getEnumerationContext(), + enumerateResponse.getItems(), + enumerateResponse.getEndOfSequence() != null + )); + return result; + } + + private EnumerateResponse enumerate(final String filter, final String dialect) { + while (true) { + try { + final Enumerate enumerate = new Enumerate(); + enumerate.setFilter(new Filter()); + enumerate.getFilter().setValue(filter); + enumerate.getFilter().setDialect(dialect); + enumerate.setMaxElements(maxElements); + return winrm.enumerate( + enumerate, + resourceUri, + sessionId, + MAX_ENVELOPER_SIZE, + operationTimeout.get(), + locale.get(), + new OptionSetType() + ); + } catch (final SOAPFaultException soapFault) { + /** + * If such Exception which has a code 2150858793 the client is expected to again trigger immediately a receive request. + * https://msdn.microsoft.com/en-us/library/cc251676.aspx + */ + assertFaultCode(soapFault, WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED, + retryReceiveAfterOperationTimeout); + } + } + } + + private PullResponse pull(final EnumerationPullState state) { + while (true) { + try { + final Pull pull = new Pull(); + pull.setEnumerationContext(state.getEnumerationContext()); + pull.setMaxElements(maxElements); + return winrm.enumeratePull( + pull, + state.getResourceId(), + sessionId, + MAX_ENVELOPER_SIZE, + operationTimeout.get(), + locale.get(), + new OptionSetType() + ); + } catch (final SOAPFaultException soapFault) { + /** + * If such Exception which has a code 2150858793 the client is expected to again trigger immediately a receive request. + * https://msdn.microsoft.com/en-us/library/cc251676.aspx + */ + assertFaultCode(soapFault, WSMAN_FAULT_CODE_OPERATION_TIMEOUT_EXPIRED, + retryReceiveAfterOperationTimeout); + } + } + } + + void collectAndIterateEnumeratedResults(final List result, final EnumerationPullState state) { + + final Document doc = documentBuilder.newDocument(); + final Element root = doc.createElement("results"); + doc.appendChild(root); + + final Items items = state.getItems(); + if (items != null) { + final List elements = items.getAny(); + if (elements != null) { + for (Object element : elements) { + if (element instanceof Node) { + final Node node = doc.importNode((Node) element, true); + root.appendChild(node); + result.add(node); + } else { + LOG.debug("{} unexpected element type {}", this, element.getClass().getCanonicalName()); + } + } + } + } + // There will be additional data available if context is given and the element sequence is not ended. + if (state.getEnumerationContext() != null && !state.isEndOfSequence()) { + final PullResponse next = pull(state); + final boolean endOfSequence = next.getEndOfSequence() != null; + LOG.debug("{} endOfSequence = {}", this, endOfSequence); + collectAndIterateEnumeratedResults(result, new EnumerationPullState( + state.getResourceId(), + state.getMaxElements(), + next.getEnumerationContext(), + next.getItems(), + endOfSequence + )); + } + } + + void assertFaultCode(SOAPFaultException soapFault, String code, Predicate retry) { + try { + NodeList faultDetails = soapFault.getFault().getDetail().getChildNodes(); + for (int i = 0; i < faultDetails.getLength(); i++) { + if (faultDetails.item(i).getLocalName().equals("WSManFault")) { + if (faultDetails.item(i).getAttributes().getNamedItem("Code").getNodeValue().equals(code) + && retry.test(code)) { + LOG.trace("winrm client {} received error 500 response with code {}, response {}", this, code, soapFault); + return; + } else { + throw soapFault; + } + } + } + throw soapFault; + } catch (NullPointerException e) { + LOG.debug("Error reading Fault Code {}", soapFault.getFault()); + throw soapFault; + } + } + + @Override + public void close() throws Exception { + } + + static class EnumerationPullState { + private final String resourceId; + private final long maxElements; + private final String enumerationContext; + private final Items items; + private final boolean endOfSequence; + + public EnumerationPullState(final String resourceId, final long maxElements, final String enumerationContext, final Items items, final boolean endOfSequence) { + this.resourceId = resourceId; + this.maxElements = maxElements; + this.enumerationContext = enumerationContext; + this.items = items; + this.endOfSequence = endOfSequence; + } + + public String getResourceId() { + return resourceId; + } + + public long getMaxElements() { + return maxElements; + } + + public String getEnumerationContext() { + return enumerationContext; + } + + public Items getItems() { + return items; + } + + public boolean isEndOfSequence() { + return endOfSequence; + } + } + +} diff --git a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java index 36d1c843..c903aeaa 100644 --- a/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java +++ b/client/src/main/java/io/cloudsoft/winrm4j/client/WinRmClient.java @@ -82,6 +82,7 @@ import io.cloudsoft.winrm4j.client.wsman.OptionSetType; import io.cloudsoft.winrm4j.client.wsman.OptionType; import sun.awt.image.ImageWatched.Link; +import org.w3c.dom.Node; /** * TODO confirm if commands can be called in parallel in one shell (probably not)! @@ -565,6 +566,42 @@ private static String getShellId(ResourceCreated resourceCreated) { throw new IllegalStateException("Shell ID not fount in " + resourceCreated); } + /** + * Executes a WMI query and returns all results as a list. + * + * @param namespace wmi namespace, default may be "root/cimv2/*" + * @param query wmi query, e.g. "Select * From Win32_TimeZone" + * @return list of nodes + */ + public List runWql(String namespace, String query) { + String resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/wmi/" + namespace; + String dialect = "http://schemas.microsoft.com/wbem/wsman/1/WQL"; + return enumerateAndPull(resourceUri, dialect, query); + } + + /** + * Executes, enumerates and returns the result list. + * + * @param resourceUri remote resource uri to filter (must support enumeration) + * @param dialect filter dialect + * @param filter resource filter + * @return list of nodes + */ + public List enumerateAndPull(String resourceUri, String dialect, String filter) { + try (EnumerateCommand command = new EnumerateCommand( + winrm, + resourceUri, + 32000L, + () -> operationTimeout, + () -> locale, + retryReceiveAfterOperationTimeout + )) { + return command.execute(filter, dialect); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + /** * @deprecated since 0.6.0. Use {@link ShellCommand#close()} instead. */ diff --git a/client/src/test/java/io/cloudsoft/winrm4j/client/RetryingProxyHandlerTest.java b/client/src/test/java/io/cloudsoft/winrm4j/client/RetryingProxyHandlerTest.java index 68986d39..1edd1a8c 100644 --- a/client/src/test/java/io/cloudsoft/winrm4j/client/RetryingProxyHandlerTest.java +++ b/client/src/test/java/io/cloudsoft/winrm4j/client/RetryingProxyHandlerTest.java @@ -16,6 +16,10 @@ import javax.xml.ws.WebServiceException; +import io.cloudsoft.winrm4j.client.enumeration.EnumerateResponse; +import io.cloudsoft.winrm4j.client.enumeration.PullResponse; +import io.cloudsoft.winrm4j.client.wsman.Enumerate; +import io.cloudsoft.winrm4j.client.wsman.Pull; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -197,5 +201,19 @@ public ResourceCreated create(Shell shell, String resourceURI, int maxEnvelopeSi calls.add(call); return (ResourceCreated) handler.apply(call); } + + @Override + public EnumerateResponse enumerate(Enumerate enumerate, String resourceURI, String sessionId, int maxEnvelopeSize, String operationTimeout, Locale locale, OptionSetType optionSet) { + RecordedCall call = new RecordedCall("enumerate", Arrays.asList(enumerate, resourceURI, sessionId, maxEnvelopeSize, operationTimeout, locale, optionSet)); + calls.add(call); + return (EnumerateResponse) handler.apply(call); + } + + @Override + public PullResponse enumeratePull(Pull pull, String resourceURI, String sessionId, int maxEnvelopeSize, String operationTimeout, Locale locale, OptionSetType optionSet) { + RecordedCall call = new RecordedCall("enumeratePull", Arrays.asList(pull, resourceURI, sessionId, maxEnvelopeSize, operationTimeout, locale, optionSet)); + calls.add(call); + return (PullResponse) handler.apply(call); + } } } diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/WinRm.java b/service/src/main/java/io/cloudsoft/winrm4j/service/WinRm.java index 55b965c1..b571a10f 100644 --- a/service/src/main/java/io/cloudsoft/winrm4j/service/WinRm.java +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/WinRm.java @@ -11,6 +11,10 @@ import javax.xml.ws.BindingType; import javax.xml.ws.RequestWrapper; +import io.cloudsoft.winrm4j.service.enumerate.EnumerateRequest; +import io.cloudsoft.winrm4j.service.enumerate.EnumerateResponse; +import io.cloudsoft.winrm4j.service.enumerate.PullRequest; +import io.cloudsoft.winrm4j.service.enumerate.PullResponse; import io.cloudsoft.winrm4j.service.shell.Receive; import io.cloudsoft.winrm4j.service.shell.ReceiveResponse; import io.cloudsoft.winrm4j.service.shell.Shell; @@ -135,6 +139,53 @@ public ResourceCreated create( @WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) OptionSetType optionSet ) { + + return null; + } + + @WebMethod(operationName = "Enumerate", action = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate") + @Action(input = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Enumerate", output = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/EnumerateResponse") + @WebResult(name = "EnumerateResponse", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", partName = "EnumerateResponse") + @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) + public EnumerateResponse enumerate( + @WebParam(name = "Enumerate", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + EnumerateRequest enumerate, + @WebParam(name = "ResourceURI", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String resourceURI, + @WebParam(name = "SessionId", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String sessionId, + @WebParam(name = "MaxEnvelopeSize", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + int maxEnvelopeSize, + @WebParam(name = "OperationTimeout", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String operationTimeout, + @WebParam(name = "Locale", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + Locale locale, + @WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + OptionSetType optionSet + ) { + return null; + } + + @WebMethod(operationName = "EnumeratePull", action = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull") + @Action(input = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/Pull", output = "http://schemas.xmlsoap.org/ws/2004/09/enumeration/PullResponse") + @WebResult(name = "PullResponse", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", partName = "PullResponse") + @SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) + public PullResponse enumeratePull( + @WebParam(name = "Pull", targetNamespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + PullRequest pull, + @WebParam(name = "ResourceURI", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String resourceURI, + @WebParam(name = "SessionId", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String sessionId, + @WebParam(name = "MaxEnvelopeSize", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + int maxEnvelopeSize, + @WebParam(name = "OperationTimeout", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + String operationTimeout, + @WebParam(name = "Locale", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + Locale locale, + @WebParam(name = "OptionSet", targetNamespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", header = true) + OptionSetType optionSet + ) { return null; } diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateRequest.java b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateRequest.java new file mode 100644 index 00000000..c0e56203 --- /dev/null +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateRequest.java @@ -0,0 +1,87 @@ +package io.cloudsoft.winrm4j.service.enumerate; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.XmlValue; + +import java.util.ArrayList; +import java.util.List; + +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/10cfb548-845b-4979-aae3-3f39d7080e17 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Enumerate", propOrder = { + "maxElements", + "filter", + "any" +}) +public class EnumerateRequest { + + @XmlElement(name = "MaxElements", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + @XmlSchemaType(name = "unsignedLong") + protected Long maxElements; + + @XmlElement(name = "Filter", namespace = "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd", required = true) + protected Filter filter; + + @XmlAnyElement(lax = true) + protected List any; + + @XmlAccessorType(XmlAccessType.FIELD) + public static class Filter { + + @XmlAttribute(name = "Dialect") + protected String dialect; + + @XmlValue + private String value; + + public String getDialect() { + return dialect; + } + + public void setDialect(final String dialect) { + this.dialect = dialect; + } + + public String getValue() { + return value; + } + + public void setValue(final String value) { + this.value = value; + } + } + + public Long getMaxElements() { + return maxElements; + } + + public void setMaxElements(final Long maxElements) { + this.maxElements = maxElements; + } + + public Filter getFilter() { + return filter; + } + + public void setFilter(final Filter filter) { + this.filter = filter; + } + + public List getAny() { + if (any == null) { + any = new ArrayList<>(); + } + return any; + } + + public void setAny(final List any) { + this.any = any; + } + +} diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateResponse.java b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateResponse.java new file mode 100644 index 00000000..db2cb6e6 --- /dev/null +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/EnumerateResponse.java @@ -0,0 +1,49 @@ +package io.cloudsoft.winrm4j.service.enumerate; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/b79bcdd9-125c-49e0-8a4f-bac4ce878592 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "EnumerateResponse", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", propOrder = { + "enumerationContext", + "items", + "endOfSequence" +}) +public class EnumerateResponse { + + @XmlElement(name = "EnumerationContext", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected String enumerationContext; + + @XmlElement(name = "Items", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected Items items; + + @XmlElement(name = "EndOfSequence", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected String endOfSequence; + + public String getEnumerationContext() { + return enumerationContext; + } + + public void setEnumerationContext(final String enumerationContext) { + this.enumerationContext = enumerationContext; + } + + public Items getItems() { + return items; + } + + public void setItems(final Items items) { + this.items = items; + } + + public String getEndOfSequence() { + return endOfSequence; + } + + public void setEndOfSequence(final String endOfSequence) { + this.endOfSequence = endOfSequence; + } +} diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/Items.java b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/Items.java new file mode 100644 index 00000000..118200b3 --- /dev/null +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/Items.java @@ -0,0 +1,22 @@ +package io.cloudsoft.winrm4j.service.enumerate; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; + +import java.util.List; + +@XmlAccessorType(XmlAccessType.FIELD) +public class Items { + + @XmlAnyElement(lax = true) + protected List value; + + public List getValue() { + return value; + } + + public void setValue(final List value) { + this.value = value; + } +} diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullRequest.java b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullRequest.java new file mode 100644 index 00000000..bfacb866 --- /dev/null +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullRequest.java @@ -0,0 +1,60 @@ +package io.cloudsoft.winrm4j.service.enumerate; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlType; + +import java.util.ArrayList; +import java.util.List; + +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/dfe7084a-dea6-4f7f-b35c-cc7d1ad8060d +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/0fddd40a-b5c4-4a63-a0bf-3ff9966e9e3e +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "Pull", propOrder = { + "enumerationContext", + "maxElements", + "any" +}) +public class PullRequest { + + @XmlElement(name = "EnumerationContext", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", required = true) + protected String enumerationContext; + + @XmlElement(name = "MaxElements", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + @XmlSchemaType(name = "unsignedLong") + protected Long maxElements; + + @XmlAnyElement(lax = true) + protected List any; + + public String getEnumerationContext() { + return enumerationContext; + } + + public void setEnumerationContext(final String enumerationContext) { + this.enumerationContext = enumerationContext; + } + + public Long getMaxElements() { + return maxElements; + } + + public void setMaxElements(final Long maxElements) { + this.maxElements = maxElements; + } + + public List getAny() { + if (any == null) { + any = new ArrayList<>(); + } + return any; + } + + public void setAny(final List any) { + this.any = any; + } + +} diff --git a/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullResponse.java b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullResponse.java new file mode 100644 index 00000000..9ec2c14b --- /dev/null +++ b/service/src/main/java/io/cloudsoft/winrm4j/service/enumerate/PullResponse.java @@ -0,0 +1,50 @@ +package io.cloudsoft.winrm4j.service.enumerate; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlType; + +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/939e283a-5518-4e43-9d9f-4f0b1a199815 +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/8923a1bb-ea8b-49cb-8495-5f2612e7a0f9 +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "PullResponse", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration", propOrder = { + "enumerationContext", + "items", + "endOfSequence" +}) +public class PullResponse { + + @XmlElement(name = "EnumerationContext", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected String enumerationContext; + + @XmlElement(name = "Items", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected Items items; + + @XmlElement(name = "EndOfSequence", namespace = "http://schemas.xmlsoap.org/ws/2004/09/enumeration") + protected String endOfSequence; + + public String getEnumerationContext() { + return enumerationContext; + } + + public void setEnumerationContext(final String enumerationContext) { + this.enumerationContext = enumerationContext; + } + + public Items getItems() { + return items; + } + + public void setItems(final Items items) { + this.items = items; + } + + public String getEndOfSequence() { + return endOfSequence; + } + + public void setEndOfSequence(final String endOfSequence) { + this.endOfSequence = endOfSequence; + } +} From ab7eaa29493e30d1861007992b4d869939e5da0d Mon Sep 17 00:00:00 2001 From: Andrew Dodd <47298244+AndrewDodd42@users.noreply.github.com> Date: Tue, 23 Aug 2022 17:19:09 +0100 Subject: [PATCH 8/8] Modified to use buildClient() method for executeCommand() method as well. --- .../io/cloudsoft/winrm4j/winrm/WinRmTool.java | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java index 79a84ed9..6ba40d44 100644 --- a/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java +++ b/winrm4j/src/main/java/io/cloudsoft/winrm4j/winrm/WinRmTool.java @@ -395,59 +395,10 @@ public WinRmToolResponse executeCommand(String command, Writer out, Writer err) public WinRmToolResponse executeCommand(String command, List args, Boolean skipCommandShell, Writer out, Writer err) { if (out==null) out = new StringWriter(); if (err==null) err = new StringWriter(); - WinRmClientBuilder builder = WinRmClient.builder(address); - builder.authenticationScheme(authenticationScheme); - if (operationTimeout != null) { - builder.operationTimeout(operationTimeout); - } - if (retryReceiveAfterOperationTimeout != null) { - builder.retryReceiveAfterOperationTimeout(retryReceiveAfterOperationTimeout); - } - if (connectionTimeout != null) { - builder.connectionTimeout(connectionTimeout); - } - if (receiveTimeout != null) { - builder.receiveTimeout(receiveTimeout); - } - if (username != null && password != null) { - builder.credentials(domain, username, password); - } - if (disableCertificateChecks) { - LOG.trace("Disabled check for https connections " + this); - builder.disableCertificateChecks(disableCertificateChecks); - } - if (allowChunking) { - builder.allowChunking(allowChunking); - } - if (hostnameVerifier != null) { - builder.hostnameVerifier(hostnameVerifier); - } - if (sslSocketFactory != null) { - builder.sslSocketFactory(sslSocketFactory); - } - if (sslContext != null) { - builder.sslContext(sslContext); - } - if (workingDirectory != null) { - builder.workingDirectory(workingDirectory); - } - if (environment != null) { - builder.environment(environment); - } - if (failureRetryPolicy != null) { - builder.failureRetryPolicy(failureRetryPolicy); - } - if (context != null) { - builder.context(context); - } - if (requestNewKerberosTicket) { - builder.requestNewKerberosTicket(requestNewKerberosTicket); - } - builder.payloadEncryptionMode(payloadEncryptionMode); WinRmToolResponse winRmToolResponse; - try(WinRmClient client = builder.build()) { + try(WinRmClient client = buildClient(out, err)) { try (ShellCommand shell = client.createShell()) { int code = shell.execute(command, args, skipCommandShell, out, err); winRmToolResponse = new WinRmToolResponse(out.toString(), err.toString(), code);