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..302cc4e2 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; } /** @@ -388,6 +430,7 @@ public WinRmToolResponse executeCommand(String command, List args, Boole builder.requestNewKerberosTicket(requestNewKerberosTicket); } builder.payloadEncryptionMode(payloadEncryptionMode); + builder.codePage(codePage); WinRmToolResponse winRmToolResponse; @@ -421,7 +464,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 + " > NUL & powershell", Arrays.asList("-encodedcommand", compileBase64(psCommand)), skipCommandShell, out, err); } /**