From 635f445fcce11a32f301a750199d8a75abc2d38a Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Fri, 24 Jul 2026 01:09:28 +0200 Subject: [PATCH 1/3] Replace prettier-maven-plugin with formatter-maven-plugin (#110) Format Java sources with net.revelc.code.formatter:formatter-maven-plugin 2.29.0, validated during the validate phase, as in Jawk and maven-skin-tools. The Eclipse profile is the shared MetricsHub profile with a few adjustments to preserve the effective prettier conventions: one-tab continuation indentation, closing parenthesis on its own line when wrapped, compact wrapping for invocation selectors and throws clauses, and leading ?/: in wrapped ternaries. Co-Authored-By: Claude Fable 5 --- .prettierrc.yaml | 12 - metricshub-eclipse-formatter.xml | 401 +++++++++++++++++++++++++++++++ pom.xml | 29 +-- 3 files changed, 416 insertions(+), 26 deletions(-) delete mode 100644 .prettierrc.yaml create mode 100644 metricshub-eclipse-formatter.xml diff --git a/.prettierrc.yaml b/.prettierrc.yaml deleted file mode 100644 index 969e79a..0000000 --- a/.prettierrc.yaml +++ /dev/null @@ -1,12 +0,0 @@ -overrides: - - files: - - "**/*.java" - options: - trailingComma: none - useTabs: true - tabWidth: 2 - semi: false - singleQuote: false - printWidth: 120 - arrowParens: avoid - endOfLine: auto \ No newline at end of file diff --git a/metricshub-eclipse-formatter.xml b/metricshub-eclipse-formatter.xml new file mode 100644 index 0000000..54662fb --- /dev/null +++ b/metricshub-eclipse-formatter.xml @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 456a252..57347a0 100644 --- a/pom.xml +++ b/pom.xml @@ -121,28 +121,29 @@ - + - com.hubspot.maven.plugins - prettier-maven-plugin - 0.22 - - 2.5.0 - false - false - - src/main/java/**/*.java - src/test/java/**/*.java - - + net.revelc.code.formatter + formatter-maven-plugin + 2.29.0 validate - check + validate + + + ${project.build.sourceDirectory} + ${project.build.testSourceDirectory} + + + **/*.java + + metricshub-eclipse-formatter.xml + From 7690bd4b877846de5813b89762907f4f7eaaed0b Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Fri, 24 Jul 2026 01:09:38 +0200 Subject: [PATCH 2/3] Reformat sources with the Eclipse formatter profile (#110) Migration-only reformatting produced by mvn formatter:format; no functional changes. Co-Authored-By: Claude Fable 5 --- .../org/metricshub/winrm/TimeoutHelper.java | 6 +- src/main/java/org/metricshub/winrm/Utils.java | 2 +- .../winrm/WindowsRemoteCommandResult.java | 4 + .../winrm/WindowsRemoteExecutor.java | 13 +- .../winrm/WindowsRemoteProcessUtils.java | 16 +- .../metricshub/winrm/WindowsTempShare.java | 41 ++--- .../java/org/metricshub/winrm/WmiHelper.java | 25 +-- .../java/org/metricshub/winrm/WqlQuery.java | 24 ++- .../winrm/command/WinRMCommandExecutor.java | 13 +- .../exceptions/WqlQuerySyntaxException.java | 1 - .../metricshub/winrm/light/AuthScheme.java | 4 +- .../winrm/light/ByteArrayUtils.java | 3 +- .../org/metricshub/winrm/light/CipherGen.java | 64 +++---- .../org/metricshub/winrm/light/Envelopes.java | 65 +++---- .../winrm/light/FallbackAuthScheme.java | 6 +- .../org/metricshub/winrm/light/HMACMD5.java | 1 - .../winrm/light/KerberosAuthScheme.java | 28 +-- .../org/metricshub/winrm/light/LightTls.java | 4 +- .../winrm/light/LightWinRMService.java | 18 +- .../java/org/metricshub/winrm/light/MD4.java | 4 +- .../winrm/light/NTLMEngineUtils.java | 16 +- .../metricshub/winrm/light/NTLMMessage.java | 24 +-- .../winrm/light/NtlmAuthScheme.java | 9 +- .../metricshub/winrm/light/NtlmCrypto.java | 13 +- .../metricshub/winrm/light/Type1Message.java | 29 +-- .../metricshub/winrm/light/Type2Message.java | 1 - .../metricshub/winrm/light/Type3Message.java | 57 +++--- .../metricshub/winrm/light/WinRMSession.java | 16 +- .../metricshub/winrm/light/WsmanClient.java | 7 +- .../winrm/service/WinRMEndpoint.java | 25 ++- .../winrm/service/WinRMExecutorFactory.java | 12 +- .../client/auth/AuthenticationEnum.java | 1 - .../metricshub/winrm/shares/SmbTempShare.java | 8 +- .../winrm/wql/WinRMWqlExecutor.java | 8 +- .../org/metricshub/winrm/WinRMLiveTest.java | 15 +- .../command/WinRMCommandExecutorTest.java | 168 +++++++++--------- .../winrm/light/FakeWsmanServer.java | 41 +++-- .../winrm/light/WsmanClientParityTest.java | 153 ++++++++-------- .../winrm/light/WsmanProtocolTest.java | 88 ++++----- .../winrm/service/WinRMEndpointTest.java | 4 +- .../service/WinRMExecutorFactoryTest.java | 57 +++--- .../winrm/shares/SmbTempShareTest.java | 5 +- 42 files changed, 500 insertions(+), 599 deletions(-) diff --git a/src/main/java/org/metricshub/winrm/TimeoutHelper.java b/src/main/java/org/metricshub/winrm/TimeoutHelper.java index c791d1d..35092f3 100644 --- a/src/main/java/org/metricshub/winrm/TimeoutHelper.java +++ b/src/main/java/org/metricshub/winrm/TimeoutHelper.java @@ -24,7 +24,6 @@ /** * for stagged timeout handling. - * */ public class TimeoutHelper { @@ -49,7 +48,8 @@ public static long getRemainingTime(final long timeout, final long start, final /** * Sub to make the thread sleep in a stepped delay. - *

Delay: + *

+ * Delay: *

    *
  • 50 milliseconds for time from start < 500 milliseconds
  • *
  • 100 milliseconds for time from start between 500 milliseconds and 1 second
  • @@ -62,9 +62,7 @@ public static long getRemainingTime(final long timeout, final long start, final * @param timeout Timeout in milliseconds * @param start start time in milliseconds * @param message The timeout error message - * * @throws TimeoutException when times out - * */ public static void stagedSleep(final long timeout, final long start, final String message) throws TimeoutException { // Check we still have some time on our hands diff --git a/src/main/java/org/metricshub/winrm/Utils.java b/src/main/java/org/metricshub/winrm/Utils.java index 4de0a4f..0e3d31f 100644 --- a/src/main/java/org/metricshub/winrm/Utils.java +++ b/src/main/java/org/metricshub/winrm/Utils.java @@ -107,6 +107,7 @@ public static void checkArgumentNotZeroOrNegative(final long argument, final Str /** * Get the current time in Milliseconds. + * * @return the current time in Milliseconds. */ public static long getCurrentTimeMillis() { @@ -133,7 +134,6 @@ public static void checkNonBlank(final String argument, final String name) { * @param callable * @param timeout * @return {@link T} result returned by the callable - * * @throws InterruptedException * @throws ExecutionException * @throws TimeoutException diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java b/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java index 33042ab..4fe76ac 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java @@ -49,6 +49,7 @@ public WindowsRemoteCommandResult( /** * Get the stdout of the command. + * * @return */ public String getStdout() { @@ -57,6 +58,7 @@ public String getStdout() { /** * Get the stderr of the command. + * * @return */ public String getStderr() { @@ -65,6 +67,7 @@ public String getStderr() { /** * Get the execution time of the command in seconds. + * * @return */ public float getExecutionTime() { @@ -73,6 +76,7 @@ public float getExecutionTime() { /** * Get the return status code of the command + * * @return */ public int getStatusCode() { diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java b/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java index ce87c62..b815a56 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java @@ -29,14 +29,14 @@ public interface WindowsRemoteExecutor extends AutoCloseable { /** - *

    Execute a WQL query and process its result.

    + *

    + * Execute a WQL query and process its result. + *

    * * @param wqlQuery the WQL query (required) * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) - * * @return a list of result rows. A result row is a Map(LinkedHashMap to preserve the query order) of - * properties/values. - * + * properties/values. * @throws TimeoutException to notify userName of timeout. * @throws WqlQuerySyntaxException if WQL query syntax is invalid * @throws WindowsRemoteException For any problem encountered @@ -51,9 +51,7 @@ public List> executeWql(final String wqlQuery, final long ti * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null) * @param charset The charset * @param timeout Timeout in milliseconds - * * @return The command result - * * @throws WindowsRemoteException For any problem encountered * @throws TimeoutException To notify userName of timeout. */ @@ -66,18 +64,21 @@ public WindowsRemoteCommandResult executeCommand( /** * Get the hostname. + * * @return */ public String getHostname(); /** * Get the username. + * * @return */ public String getUsername(); /** * Get the password. + * * @return */ public char[] getPassword(); diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java index 6587f71..563cd80 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java @@ -51,8 +51,7 @@ private WindowsRemoteProcessUtils() {} * * @see Windows code page * @see - * Supported Encodings - * + * Supported Encodings */ private static final Map CODESET_MAP; @@ -87,16 +86,12 @@ private WindowsRemoteProcessUtils() {} * * @param windowsRemoteExecutor WindowsRemoteExecutor instance * @param timeout Timeout in milliseconds. - * * @return the encoding charset from Win32_OperatingSystem - * * @throws TimeoutException To notify userName of timeout * @throws WqlQuerySyntaxException On WQL syntax errors * @throws WindowsRemoteException For any problem encountered on remote - * * @see - * Win32_OperatingSystem class - * + * Win32_OperatingSystem class */ public static Charset getWindowsEncodingCharset( final WindowsRemoteExecutor windowsRemoteExecutor, @@ -143,9 +138,7 @@ public static String buildNewOutputFileName() { * @param localFiles The local files to copy list * @param uncSharePath The UNC path of the share * @param remotePath The remote path - * * @return The updated command. - * * @throws IOException If an I/O error occurs. */ public static String copyLocalFilesToShare( @@ -189,7 +182,6 @@ public static String copyLocalFilesToShare( /** * Copy a file to the share. - * * If the same file is already present on the share, the copy is not performed. * The "last-modified" time is used to determine whether the file needs to be * copied or not. @@ -197,9 +189,7 @@ public static String copyLocalFilesToShare( * @param localFilePath The path to the file to copy * @param uncSharePath The UNC path of the share * @param remotePath The remote path - * * @return the path to the copied file, as seen in the remote system - * * @throws IOException If an I/O error occurs. */ static Path copyToShare(final Path localFilePath, final String uncSharePath, final String remotePath) @@ -226,13 +216,11 @@ static Path copyToShare(final Path localFilePath, final String uncSharePath, fin /** * Perform a case-insensitive replace of all occurrences of target string with * specified replacement - * * Similar to String.replace(target, replacement) * * @param string The string to parse * @param target The string to replace * @param replacement The replacement string - * * @return updated string */ static String caseInsensitiveReplace(final String string, final String target, final String replacement) { diff --git a/src/main/java/org/metricshub/winrm/WindowsTempShare.java b/src/main/java/org/metricshub/winrm/WindowsTempShare.java index 09358f3..e734599 100644 --- a/src/main/java/org/metricshub/winrm/WindowsTempShare.java +++ b/src/main/java/org/metricshub/winrm/WindowsTempShare.java @@ -34,7 +34,7 @@ public class WindowsTempShare { /** The UNC path of the share. */ private final String uncSharePath; - /** The remote path.*/ + /** The remote path. */ private final String remotePath; /** The WindowsRemoteExecutor instance */ @@ -44,7 +44,7 @@ public class WindowsTempShare { * Constructor of WindowsTempShare * * @param windowsRemoteExecutor the WindowsRemoteExecutor instance - * connected to the remote host (mandatory) + * connected to the remote host (mandatory) * @param shareNameOrUnc The name of the share, or its full UNC path (mandatory) * @param remotePath The path on the remote system of the directory being shared */ @@ -75,9 +75,7 @@ public WindowsTempShare( * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) * @param shareRemoteDirectory ShareRemoteDirectoryConsumer function (mandatory) - * * @return The remote path. - * * @throws TimeoutException To notify userName of timeout. * @throws WindowsRemoteException For any problem encountered. */ @@ -130,15 +128,11 @@ public static WindowsTempShare getOrCreateShare( * * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) - * * @return The Windows directory. - * * @throws WindowsRemoteException For any problem encountered. * @throws TimeoutException To notify userName of timeout. - * * @see - * Win32_OperatingSystem class - * + * Win32_OperatingSystem class */ public static String getWindowsDirectory(final WindowsRemoteExecutor windowsRemoteExecutor, final long timeout) throws WindowsRemoteException, TimeoutException { @@ -154,8 +148,8 @@ public static String getWindowsDirectory(final WindowsRemoteExecutor windowsRemo .map(row -> (String) row.get("WindowsDirectory")) .filter(Objects::nonNull) .findFirst() - .orElseThrow(() -> - new WindowsRemoteException( + .orElseThrow( + () -> new WindowsRemoteException( String.format("Couldn't identify the Windows root directory on %s.", windowsRemoteExecutor.getHostname()) ) ); @@ -171,7 +165,6 @@ public static String getWindowsDirectory(final WindowsRemoteExecutor windowsRemo * @param remotePath The remote path to create * @param timeout Timeout in milliseconds * @param start start time in milliseconds. - * * @throws WindowsRemoteException For any problem encountered. * @throws TimeoutException To notify userName of timeout. */ @@ -188,12 +181,10 @@ public static void createRemoteDirectory( /** * Build a UNC path from hostname and share name. - * * Note: This method ensures compatibility with IPv6 hosts * * @param hostname Host to connect to. (mandatory) * @param share The share - * * @return The UNC path to the share (\\unc(host)\share) */ static String buildUncPath(final String hostname, final String share) { @@ -208,7 +199,6 @@ static String buildUncPath(final String hostname, final String share) { * Create the Windows command for a directory creation. * * @param remotePath The remote path to create. (mandatory) - * * @return The command to execute. */ static String buildCreateRemoteDirectoryCommand(final String remotePath) { @@ -219,7 +209,6 @@ static String buildCreateRemoteDirectoryCommand(final String remotePath) { /** * @param path Root path of the temporary directory that will be used in a cluster. (mandatory) - * * @return Path to the temporary directory */ static String buildPathOnCluster(final String path) { @@ -233,7 +222,6 @@ static String buildPathOnCluster(final String path) { * * @param folder The folder name. (mandatory) * @param shareName The Share Name. (mandatory) - * * @return The remote directory path. (folder\Temp\shareName) */ static String buildRemotePath(final String folder, final String shareName) { @@ -254,15 +242,12 @@ static String buildShareName() { /** * Retrieve an "Admin Share" (like D$, E$, etc.) that is exposed by a cluster. - * * If the targeted system is not a cluster, returns an empty optional. * * @param windowsRemoteExecutor WindowsRemoteExecutor instance. * @param timeout Timeout in milliseconds. * @param start start time in milliseconds. - * * @return An optional Map with 2 entries: "Name" and "Path" - * * @throws TimeoutException To notify userName of timeout. * @throws WindowsRemoteException For any problem encountered. */ @@ -275,17 +260,17 @@ static Optional getClusterShare( final Optional clusterShare = windowsRemoteExecutor .executeWql( "SELECT Name,Path FROM Win32_ClusterShare WHERE " + - "ServerName <> '*' AND (Type = 2147483648 OR Type = 3221225472) AND Name LIKE '%\\\\_$'", + "ServerName <> '*' AND (Type = 2147483648 OR Type = 3221225472) AND Name LIKE '%\\\\_$'", timeout ) .stream() .limit(1) .map(row -> // We return a TempShare instance pointing to a subdirectory in this share - new WindowsTempShare( - windowsRemoteExecutor, - buildPathOnCluster((String) row.get("Name")), - buildPathOnCluster((String) row.get("Path")) - ) + new WindowsTempShare( + windowsRemoteExecutor, + buildPathOnCluster((String) row.get("Name")), + buildPathOnCluster((String) row.get("Path")) + ) ) .findFirst(); @@ -306,9 +291,7 @@ static Optional getClusterShare( * @param windowsRemoteExecutor WindowsRemoteExecutor instance * @param shareName The share name * @param timeout Timeout in milliseconds - * * @return An optional TempShare instance - * * @throws TimeoutException To notify userName of timeout * @throws WindowsRemoteException For any problem encountered */ @@ -336,9 +319,7 @@ static Optional getShare( * @param shareName The Share Name. * @param timeout Timeout in milliseconds. * @param shareRemoteDirectory shareRemoteDirectory function - * * @return A TempShare instance - * * @throws WindowsRemoteException For any problem encountered. * @throws TimeoutException To notify userName of timeout. */ diff --git a/src/main/java/org/metricshub/winrm/WmiHelper.java b/src/main/java/org/metricshub/winrm/WmiHelper.java index 91ac4d3..249e1cd 100644 --- a/src/main/java/org/metricshub/winrm/WmiHelper.java +++ b/src/main/java/org/metricshub/winrm/WmiHelper.java @@ -74,20 +74,23 @@ public static String createNetworkResource(final String hostname, final String n */ public static boolean isLocalNetworkResource(final String networkResource) { Utils.checkNonNull(networkResource, "networkResource"); - return ( - !networkResource.startsWith("\\\\") || - networkResource.startsWith("\\\\localhost\\") || - networkResource.startsWith("\\\\127.0.0.1\\") || - networkResource.startsWith("\\\\0:0:0:0:0:0:0:1\\") || - networkResource.startsWith("\\\\::1\\") || - networkResource.startsWith("\\\\0000:0000:0000:0000:0000:0000:0000:0001\\") || - networkResource.toLowerCase().startsWith("\\\\" + Utils.getComputerName().toLowerCase() + "\\") - ); + return (!networkResource.startsWith("\\\\") + || + networkResource.startsWith("\\\\localhost\\") + || + networkResource.startsWith("\\\\127.0.0.1\\") + || + networkResource.startsWith("\\\\0:0:0:0:0:0:0:1\\") + || + networkResource.startsWith("\\\\::1\\") + || + networkResource.startsWith("\\\\0000:0000:0000:0000:0000:0000:0000:0001\\") + || + networkResource.toLowerCase().startsWith("\\\\" + Utils.getComputerName().toLowerCase() + "\\")); } /** * Extract the exact name of the properties from a WMI result. - * * The interest is to retrieve the exact case of the property names, instead of * the lowercase that we have at this stage. * @@ -106,10 +109,8 @@ public static List extractPropertiesFromResult(final List *
  • SELECT * FROM Class @@ -79,6 +78,7 @@ private WqlQuery( *
  • SELECT * FROM ASSOCIATORS OF { objectId } WHERE condition *
  • SELECT PropA, PropB FROM ASSOCIATORS OF { objectId } WHERE condition *
+ * * @param wql The WQL query to parse * @return a new WqlQuery instance * @throws WqlQuerySyntaxException when the specified WQL is invalid and cannot be parsed @@ -115,6 +115,7 @@ public static WqlQuery newInstance(CharSequence wql) throws WqlQuerySyntaxExcept /** * Note: All properties are converted to lower case + * * @param propertiesFragment Comma-separated list of properties * @return a cleaned-up array of the properties */ @@ -127,12 +128,9 @@ static List buildSelectedProperties(String propertiesFragment) { /** * Build a Map of subproperties to retrieve inside properties - * * Example: - * * Input: * PropA, PropB.Sub1, PropB.Sub2 - * * Output: *
    *
  • PropA => emptySet() @@ -180,18 +178,17 @@ static Map> buildSupPropertiesMap(final List propert /** * Build a strict WQL query from the "dirty" one we have - * * By strict we mean a syntax that can be executed by the WMI provider.
    * By dirty we mean the extra sugar-coated syntax we're allowing in Metricshub products, * like subproperties, and SELECT prop FROM ASSOCIATORS OF... - * * Examples: *
      *
    • SELECT PropA.Name FROM Win32_Class
      - * => SELECT PropA FROM Win32_Class + * => SELECT PropA FROM Win32_Class *
    • SELECT Temperature FROM ASSOCIATORS OF { Win32_Class.Id=1 }
      - * => ASSOCIATORS OF { Win32_Class.Id=1 } + * => ASSOCIATORS OF { Win32_Class.Id=1 } *
    + * * @param associatorsFragment The extracted ASSOCIATORS OF... fragment * @param subPropertiesMap The map built with {@link WqlQuery#buildSupPropertiesMap(String[])} * @param classFragment The extracted class fragment @@ -210,12 +207,11 @@ static String buildCleanWql( if (subPropertiesMap.keySet().isEmpty()) { cleanWql = "SELECT * FROM " + classFragment; } else { - cleanWql = - String.format( - "SELECT %s FROM %s", - subPropertiesMap.keySet().stream().collect(Collectors.joining(",")), - classFragment - ); + cleanWql = String.format( + "SELECT %s FROM %s", + subPropertiesMap.keySet().stream().collect(Collectors.joining(",")), + classFragment + ); } } else { cleanWql = associatorsFragment; diff --git a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java index 25deae4..9464f57 100644 --- a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java +++ b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java @@ -46,22 +46,17 @@ private WinRMCommandExecutor() {} /** * Execute a command on a remote Windows system and return an object with * the output of the command. - * * You can specify local files to be copied to the remote system before executing the command. * If the command contains references to these local files, it will be updated to reference the * path on the remote system where the files have been copied. - * * Example: - * * * WinRemoteCommandExecutor.execute( * "CSCRIPT c:\\MyScript.vbs", null, "remote-srv", null, null, null, 30000, Arrays.asList("c:\\MyScript.vbs"), false); * - * * This will copy c:\\MyScript.vbs to remote-srv, typically in * C:\\Windows\\Temp\\SEN_ShareFor_MYHOST and the command that is executed will therefore * become: - * * CSCRIPT "C:\\Windows\\Temp\\SEN_ShareFor_MYHOST\\MyScript.vbs" * * @param command The command to execute. (Mandatory) @@ -75,9 +70,7 @@ private WinRMCommandExecutor() {} * @param localFileToCopyList List of local files to copy to the remote before the execution * @param ticketCache The Ticket Cache path * @param authentications List of authentications. only NTLM if absent - * * @return an instance of WindowsRemoteCommandResult with the result of the command - * * @throws IOException If an I/O error occurs. * @throws TimeoutException To notify userName of timeout * @throws WindowsRemoteException For any problem encountered on remote @@ -109,8 +102,7 @@ public static WindowsRemoteCommandResult execute( timeout, ticketCache, authentications - ) - ) { + )) { final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset( winRMService, TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set") @@ -128,8 +120,7 @@ public static WindowsRemoteCommandResult execute( timeout, ticketCache, authentications - ) - ) { + )) { smbTempShare.checkConnectedFirst(); final List localFiles = localFileToCopyList diff --git a/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java b/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java index 23d978a..51926b6 100644 --- a/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java +++ b/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java @@ -22,7 +22,6 @@ /** * Exception for a syntax error in the WQL Query parameter. - * */ public class WqlQuerySyntaxException extends Exception { diff --git a/src/main/java/org/metricshub/winrm/light/AuthScheme.java b/src/main/java/org/metricshub/winrm/light/AuthScheme.java index 6562785..f170e3f 100644 --- a/src/main/java/org/metricshub/winrm/light/AuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/AuthScheme.java @@ -26,8 +26,8 @@ * differ between NTLM and Kerberos. {@link WsmanClient} is otherwise mechanism-agnostic and just * delegates to the scheme, so a new mechanism is added by implementing this interface rather than * branching the client. - * - *

    All methods are called while {@code WsmanClient} holds its operation lock, so implementations + *

    + * All methods are called while {@code WsmanClient} holds its operation lock, so implementations * need no internal synchronization. */ interface AuthScheme { diff --git a/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java b/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java index d468a14..6884632 100644 --- a/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java +++ b/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java @@ -66,7 +66,8 @@ public static String formatHexDump(final byte[] array) { final byte c = array[rowOffset + index]; builder.append((c >= 20 && c < 127) ? (char) c : '.'); - if (index % 8 == 7) builder.append(" "); + if (index % 8 == 7) + builder.append(" "); } } } diff --git a/src/main/java/org/metricshub/winrm/light/CipherGen.java b/src/main/java/org/metricshub/winrm/light/CipherGen.java index 378670a..605282b 100644 --- a/src/main/java/org/metricshub/winrm/light/CipherGen.java +++ b/src/main/java/org/metricshub/winrm/light/CipherGen.java @@ -189,10 +189,9 @@ private byte[] getNTLMv2Blob() { * client challenge. * * @param targetInformation - * The target information block from the Type 2 message. + * The target information block from the Type 2 message. * @param clientChallenge - * The random 8-byte client challenge. - * + * The random 8-byte client challenge. * @return The blob, used in the calculation of the NTLMv2 Response. */ private static byte[] createBlob( @@ -205,12 +204,12 @@ private static byte[] createBlob( final byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; final byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; final byte[] blob = new byte[blobSignature.length + - reserved.length + - timestamp.length + - 8 + - unknown1.length + - targetInformation.length + - unknown2.length]; + reserved.length + + timestamp.length + + 8 + + unknown1.length + + targetInformation.length + + unknown2.length]; int offset = 0; System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); offset += blobSignature.length; @@ -261,8 +260,8 @@ public byte[] getNTLM2SessionResponse() throws NtlmException { * @param challenge * @param clientChallenge * @return The NTLM2 Session Response. This is placed in the NTLM response - * field of the Type 3 message; the LM response field contains the - * client challenge, null-padded to 24 bytes. + * field of the Type 3 message; the LM response field contains the + * client challenge, null-padded to 24 bytes. */ private static byte[] ntlm2SessionResponse( final byte[] ntlmHash, @@ -289,10 +288,9 @@ private static byte[] ntlm2SessionResponse( * Creates the LM Response from the given hash and Type 2 challenge. * * @param hash - * The LM or NTLM Hash. + * The LM or NTLM Hash. * @param challenge - * The server challenge from the Type 2 message. - * + * The server challenge from the Type 2 message. * @return The response (either LM or NTLM, depending on the provided hash). */ private static byte[] lmResponse(final byte[] hash, final byte[] challenge) throws NtlmException { @@ -323,10 +321,9 @@ private static byte[] lmResponse(final byte[] hash, final byte[] challenge) thro * Creates the LM Hash of the user's password. * * @param password - * The password. - * + * The password. * @return The LM Hash of the given password, used in the calculation of the - * LM Response. + * LM Response. */ private static byte[] lmHash(final String password) throws NtlmException { try { @@ -356,13 +353,12 @@ private static byte[] lmHash(final String password) throws NtlmException { * Creates a DES encryption key from the given key material. * * @param bytes - * A byte array containing the DES key material. + * A byte array containing the DES key material. * @param offset - * The offset in the given byte array at which the 7-byte key - * material starts. - * + * The offset in the given byte array at which the 7-byte key + * material starts. * @return A DES encryption key created from the key material starting at - * the specified offset in the given byte array. + * the specified offset in the given byte array. */ private static Key createDESKey(final byte[] bytes, final int offset) { final byte[] keyBytes = new byte[7]; @@ -384,13 +380,13 @@ private static Key createDESKey(final byte[] bytes, final int offset) { * Applies odd parity to the given byte array. * * @param bytes - * The data whose parity bits are to be adjusted for odd parity. + * The data whose parity bits are to be adjusted for odd parity. */ private static void oddParity(final byte[] bytes) { for (int i = 0; i < bytes.length; i++) { final byte b = bytes[i]; - final boolean needsParity = - (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; + final boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) + & 0x01) == 0; if (needsParity) { bytes[i] |= (byte) 0x01; } else { @@ -484,10 +480,9 @@ public byte[] getLanManagerSessionKey() throws NtlmException { * Creates the NTLM Hash of the user's password. * * @param password - * The password. - * + * The password. * @return The NTLM Hash of the given password, used in the calculation of - * the NTLM Response and the NTLMv2 and LMv2 Hashes. + * the NTLM Response and the NTLMv2 and LMv2 Hashes. */ private static byte[] ntlmHash(final String password) throws NtlmException { if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) { @@ -503,7 +498,7 @@ private static byte[] ntlmHash(final String password) throws NtlmException { * Creates the LMv2 Hash of the user's password. * * @return The LMv2 Hash, used in the calculation of the NTLMv2 and LMv2 - * Responses. + * Responses. */ private static byte[] lmv2Hash(final String domain, final String user, final byte[] ntlmHash) throws NtlmException { if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) { @@ -522,7 +517,7 @@ private static byte[] lmv2Hash(final String domain, final String user, final byt * Creates the NTLMv2 Hash of the user's password. * * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 and LMv2 - * Responses. + * Responses. */ private static byte[] ntlmv2Hash(final String domain, final String user, final byte[] ntlmHash) throws NtlmException { if (NTLMEngineUtils.UNICODE_LITTLE_UNMARKED == null) { @@ -542,14 +537,13 @@ private static byte[] ntlmv2Hash(final String domain, final String user, final b * challenge. * * @param hash - * The NTLMv2 Hash. + * The NTLMv2 Hash. * @param clientData - * The client data (blob or client challenge). + * The client data (blob or client challenge). * @param challenge - * The server challenge from the Type 2 message. - * + * The server challenge from the Type 2 message. * @return The response (either NTLMv2 or LMv2, depending on the client - * data). + * data). */ private static byte[] lmv2Response(final byte[] hash, final byte[] challenge, final byte[] clientData) { final HMACMD5 hmacMD5 = new HMACMD5(hash); diff --git a/src/main/java/org/metricshub/winrm/light/Envelopes.java b/src/main/java/org/metricshub/winrm/light/Envelopes.java index 001e924..491bd71 100644 --- a/src/main/java/org/metricshub/winrm/light/Envelopes.java +++ b/src/main/java/org/metricshub/winrm/light/Envelopes.java @@ -59,8 +59,7 @@ private Envelopes() {} // --- WQL --------------------------------------------------------------- static String enumerateWql(final String url, final String namespace, final String wql, final long timeoutMs) { - return ( - envelopeOpen(false) + + return (envelopeOpen(false) + header(url, wmiResourceUri(namespace), ACTION_ENUMERATE, timeoutMs, null, null) + "" + "" + @@ -68,88 +67,73 @@ static String enumerateWql(final String url, final String namespace, final Strin "" + escape(wql) + "" + - "" - ); + ""); } static String pull(final String url, final String namespace, final String context, final long timeoutMs) { - return ( - envelopeOpen(false) + + return (envelopeOpen(false) + header(url, wmiResourceUri(namespace), ACTION_PULL, timeoutMs, null, null) + "" + "" + escape(context) + "" + "32000" + - "" - ); + ""); } // --- Command shell ----------------------------------------------------- static String createShell(final String url, final String workingDirectory, final long timeoutMs) { - final String optionSet = - "" + + final String optionSet = "" + "TRUE" + "437" + ""; final String workingDir = (workingDirectory == null || workingDirectory.trim().isEmpty()) ? "" : "" + escape(workingDirectory) + ""; - return ( - envelopeOpen(true) + + return (envelopeOpen(true) + header(url, SHELL_RESOURCE_URI, ACTION_CREATE, timeoutMs, null, optionSet) + "" + "stdin" + "stdout stderr" + workingDir + - "" - ); + ""); } static String command(final String url, final String shellId, final String commandLine, final long timeoutMs) { - final String optionSet = - "" + + final String optionSet = "" + "TRUE" + "FALSE" + ""; - return ( - envelopeOpen(true) + + return (envelopeOpen(true) + header(url, SHELL_RESOURCE_URI, ACTION_COMMAND, timeoutMs, shellSelector(shellId), optionSet) + "" + escape(commandLine) + - "" - ); + ""); } static String receive(final String url, final String shellId, final String commandId, final long timeoutMs) { - return ( - envelopeOpen(true) + + return (envelopeOpen(true) + header(url, SHELL_RESOURCE_URI, ACTION_RECEIVE, timeoutMs, shellSelector(shellId), null) + "stdout stderr" - ); + "\">stdout stderr"); } static String signal(final String url, final String shellId, final String commandId, final long timeoutMs) { - return ( - envelopeOpen(true) + + return (envelopeOpen(true) + header(url, SHELL_RESOURCE_URI, ACTION_SIGNAL, timeoutMs, shellSelector(shellId), null) + "" + TERMINATE_CODE + - "" - ); + ""); } static String deleteShell(final String url, final String shellId, final long timeoutMs) { - return ( - envelopeOpen(true) + + return (envelopeOpen(true) + header(url, SHELL_RESOURCE_URI, ACTION_DELETE, timeoutMs, shellSelector(shellId), null) + - "" - ); + ""); } // --- helpers ----------------------------------------------------------- @@ -159,9 +143,8 @@ private static String wmiResourceUri(final String namespace) { } private static String shellSelector(final String shellId) { - return ( - "" + escape(shellId) + "" - ); + return ("" + escape(shellId) + + ""); } /** @@ -182,8 +165,7 @@ private static String header( final String selectorSet, final String optionSet ) { - return ( - "" + + return ("" + "" + url + "" + @@ -208,13 +190,11 @@ private static String header( "" + operationTimeout(timeoutMs) + "" + - "" - ); + ""); } private static String envelopeOpen(final boolean shell) { - return ( - "" - ); + ">"); } private static String escape(final String s) { diff --git a/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java b/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java index efe7039..95b7042 100644 --- a/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java @@ -26,8 +26,8 @@ * Tries several {@link AuthScheme}s in the caller's order, using the first whose handshake succeeds * (e.g. {@code [KERBEROS, NTLM]}: attempt Kerberos, fall back to NTLM). Once a scheme authenticates * it becomes the active one for the rest of the connection; on reconnect the fallback runs again. - * - *

    Fallback triggers on a failed handshake — the common case being Kerberos unavailable (no TGT, + *

    + * Fallback triggers on a failed handshake — the common case being Kerberos unavailable (no TGT, * no reachable KDC, unconfigured realm), which fails client-side before any SOAP is sent. */ final class FallbackAuthScheme implements AuthScheme { @@ -70,7 +70,7 @@ public String authenticate(final HttpTransport transport) throws Exception { } throw new IllegalStateException( "All requested authentication schemes failed" + - (lastFailure == null ? "" : " (last: " + lastFailure.getMessage() + ")"), + (lastFailure == null ? "" : " (last: " + lastFailure.getMessage() + ")"), lastFailure ); } diff --git a/src/main/java/org/metricshub/winrm/light/HMACMD5.java b/src/main/java/org/metricshub/winrm/light/HMACMD5.java index 319dbf4..f3b200f 100644 --- a/src/main/java/org/metricshub/winrm/light/HMACMD5.java +++ b/src/main/java/org/metricshub/winrm/light/HMACMD5.java @@ -25,7 +25,6 @@ /** * Cryptography support - HMACMD5 - algorithmically based on various web * resources by Karl Wright - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 diff --git a/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java b/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java index ac3f30f..a7ee1e9 100644 --- a/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java @@ -45,11 +45,11 @@ * obtains a TGT via JAAS ({@code Krb5LoginModule}) from a username+password (or a ticket cache), * then a service ticket for {@code HTTP/} and emits the AP-REQ under the {@code Negotiate} * header. - * - *

    HTTPS only. Like the CXF backend (which never implemented Kerberos message encryption over + *

    + * HTTPS only. Like the CXF backend (which never implemented Kerberos message encryption over * HTTP), the SOAP travels plaintext inside TLS, so {@link #wrap}/{@link #unwrap} are pass-throughs. - * - *

    Realm and KDC resolution is left to the ambient Kerberos configuration (a {@code krb5.conf} or + *

    + * Realm and KDC resolution is left to the ambient Kerberos configuration (a {@code krb5.conf} or * the {@code java.security.krb5.*} system properties), exactly as the CXF path did — the library * sets none itself. */ @@ -69,11 +69,11 @@ final class KerberosAuthScheme implements AuthScheme { /** * @param servicePrincipalHost the host whose {@code HTTP/} SPN to target — must be the FQDN - * the KDC knows (never an IP) - * @param username the account name (without any {@code DOMAIN\} prefix) - * @param password the account password (unused when {@code ticketCache} is set) - * @param ticketCache a Kerberos credential cache to reuse, or {@code null} to log in with - * the password + * the KDC knows (never an IP) + * @param username the account name (without any {@code DOMAIN\} prefix) + * @param password the account password (unused when {@code ticketCache} is set) + * @param ticketCache a Kerberos credential cache to reuse, or {@code null} to log in with + * the password */ KerberosAuthScheme( final String servicePrincipalHost, @@ -182,11 +182,11 @@ public AppConfigurationEntry[] getAppConfigurationEntry(final String name) { options.put("doNotPrompt", "false"); } return new AppConfigurationEntry[] { - new AppConfigurationEntry( - "com.sun.security.auth.module.Krb5LoginModule", - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, - options - ) + new AppConfigurationEntry( + "com.sun.security.auth.module.Krb5LoginModule", + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + options + ) }; } }; diff --git a/src/main/java/org/metricshub/winrm/light/LightTls.java b/src/main/java/org/metricshub/winrm/light/LightTls.java index a42413e..b4e5147 100644 --- a/src/main/java/org/metricshub/winrm/light/LightTls.java +++ b/src/main/java/org/metricshub/winrm/light/LightTls.java @@ -29,8 +29,8 @@ /** * TLS setup for the light backend's HTTPS transport. - * - *

    Unlike the legacy CXF path (which trusts every certificate), the light backend validates by + *

    + * Unlike the legacy CXF path (which trusts every certificate), the light backend validates by * default: it uses the JDK default {@link SSLSocketFactory}, so the platform trust store (and any * {@code -Djavax.net.ssl.trustStore}) applies and the server hostname is verified during the * handshake. Setting the system property {@value #INSECURE_PROPERTY} to {@code true} opts out — diff --git a/src/main/java/org/metricshub/winrm/light/LightWinRMService.java b/src/main/java/org/metricshub/winrm/light/LightWinRMService.java index 7ced0fb..f672919 100644 --- a/src/main/java/org/metricshub/winrm/light/LightWinRMService.java +++ b/src/main/java/org/metricshub/winrm/light/LightWinRMService.java @@ -45,8 +45,8 @@ * replacement for the CXF-based {@code WinRMService} that shipped before 2.0.0: same public * behaviour, no Apache CXF / JAX-WS / JAXB stack, and immune by construction to JAXP * {@code ServiceLoader} poisoning (it uses the JDK-default XML factories). - * - *

    Supports NTLM over HTTP (with message encryption) and over HTTPS (plaintext SOAP inside TLS, + *

    + * Supports NTLM over HTTP (with message encryption) and over HTTPS (plaintext SOAP inside TLS, * validating the server certificate by default; see {@link LightTls}), and Kerberos over HTTPS * (SPNEGO via the JDK GSS-API; see {@link KerberosAuthScheme}). A multi-scheme request such as * {@code [KERBEROS, NTLM]} is tried in order with fallback. @@ -65,12 +65,12 @@ private LightWinRMService(final WinRMEndpoint winRMEndpoint, final WsmanClient c /** * Create a light WinRM executor. * - * @param winRMEndpoint endpoint with credentials (mandatory) - * @param timeout timeout in milliseconds (must be > 0) - * @param ticketCache Kerberos ticket cache path (used by the Kerberos scheme; {@code null} logs - * in with the password) + * @param winRMEndpoint endpoint with credentials (mandatory) + * @param timeout timeout in milliseconds (must be > 0) + * @param ticketCache Kerberos ticket cache path (used by the Kerberos scheme; {@code null} logs + * in with the password) * @param authentications requested authentication schemes, tried in order (NTLM and/or Kerberos); - * {@code null}/empty means NTLM only + * {@code null}/empty means NTLM only * @return a new {@code LightWinRMService} * @throws WinRMException on invalid arguments or an unsupported authentication request */ @@ -148,8 +148,8 @@ private static AuthScheme resolveAuthScheme( // e.g. Kerberos requested over plain HTTP with no other scheme to fall back to. throw new WinRMException( "Kerberos over WinRM requires HTTPS (endpoint was " + - winRMEndpoint.getEndpoint() + - "): there is no Kerberos message encryption over plain HTTP. Use HTTPS, or add NTLM to the authentication list." + winRMEndpoint.getEndpoint() + + "): there is no Kerberos message encryption over plain HTTP. Use HTTPS, or add NTLM to the authentication list." ); } return schemes.size() == 1 ? schemes.get(0) : new FallbackAuthScheme(schemes); diff --git a/src/main/java/org/metricshub/winrm/light/MD4.java b/src/main/java/org/metricshub/winrm/light/MD4.java index e323247..ddb7f5e 100644 --- a/src/main/java/org/metricshub/winrm/light/MD4.java +++ b/src/main/java/org/metricshub/winrm/light/MD4.java @@ -26,7 +26,6 @@ * Code correctness was verified by looking at MD4.java from the jcifs * library (http://jcifs.samba.org). It was massaged extensively to the * final form found here by Karl Wright (kwright@metacarta.com). - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 @@ -106,8 +105,7 @@ private void processBuffer() { final int[] d = new int[16]; for (int i = 0; i < 16; i++) { - d[i] = - (dataBuffer[i * 4] & 0xff) + + d[i] = (dataBuffer[i * 4] & 0xff) + ((dataBuffer[i * 4 + 1] & 0xff) << 8) + ((dataBuffer[i * 4 + 2] & 0xff) << 16) + ((dataBuffer[i * 4 + 3] & 0xff) << 24); diff --git a/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java b/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java index 3eb5b5e..df77b87 100644 --- a/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java +++ b/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java @@ -62,13 +62,18 @@ static String convertDomain(final String domain) { // http://msdn.microsoft.com/en-us/library/cc236650%28v=prot.20%29.aspx // [MS-NLMP] section 2.2.2.5 static final int FLAG_REQUEST_UNICODE_ENCODING = 0x00000001; // Unicode string encoding requested - static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE message. + static final int FLAG_REQUEST_SIGN = 0x00000010; // Requests all messages have a signature attached, in NEGOTIATE + // message. static final int FLAG_REQUEST_LAN_MANAGER_KEY = 0x00000080; // Request Lan Manager key instead of user session key - static final int FLAG_REQUEST_NTLM_V1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE both - static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by REQUEST_SIGN and REQUEST_SEAL. - static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session security + static final int FLAG_REQUEST_NTLM_V1 = 0x00000200; // Request NTLMv1 security. MUST be set in NEGOTIATE and CHALLENGE + // both + static final int FLAG_REQUEST_ALWAYS_SIGN = 0x00008000; // Requests a signature block on all messages. Overridden by + // REQUEST_SIGN and REQUEST_SEAL. + static final int FLAG_REQUEST_NTLM2_SESSION = 0x00080000; // From server in challenge, requesting NTLM2 session + // security static final int FLAG_REQUEST_VERSION = 0x02000000; // Request protocol version - static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is present + static final int FLAG_TARGETINFO_PRESENT = 0x00800000; // From server in challenge message, indicating targetinfo is + // present static final int FLAG_REQUEST_128BIT_KEY_EXCH = 0x20000000; // Request explicit 128-bit key exchange static final int FLAG_REQUEST_EXPLICIT_KEY_EXCH = 0x40000000; // Request explicit key exchange static final int FLAG_REQUEST_56BIT_ENCRYPTION = 0x80000000; // Must be used in conjunction with SEAL @@ -88,6 +93,7 @@ static String convertDomain(final String domain) { /** * Find the character set based on the flags. + * * @param flags is the flags. * @return the character set. */ diff --git a/src/main/java/org/metricshub/winrm/light/NTLMMessage.java b/src/main/java/org/metricshub/winrm/light/NTLMMessage.java index 10bdd54..71814a6 100644 --- a/src/main/java/org/metricshub/winrm/light/NTLMMessage.java +++ b/src/main/java/org/metricshub/winrm/light/NTLMMessage.java @@ -22,7 +22,6 @@ /** * NTLM message generation, base class - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 @@ -83,21 +82,22 @@ static int readULong(final byte[] src, final int index) { if (src.length < index + 4) { return 0; } - return ( - (src[index] & 0xff) | - ((src[index + 1] & 0xff) << 8) | - ((src[index + 2] & 0xff) << 16) | - ((src[index + 3] & 0xff) << 24) - ); + return ((src[index] & 0xff) + | + ((src[index + 1] & 0xff) << 8) + | + ((src[index + 2] & 0xff) << 16) + | + ((src[index + 3] & 0xff) << 24)); } /** * Prepares the object to create a response of the given length. * * @param maxlength - * the maximum length of the response to prepare, - * including the type and the signature (which this method - * adds). + * the maximum length of the response to prepare, + * including the type and the signature (which this method + * adds). */ void prepareResponse(final int maxlength, final int messageType) { messageContents = new byte[maxlength]; @@ -110,7 +110,7 @@ void prepareResponse(final int maxlength, final int messageType) { * Adds the given byte to the response. * * @param b - * the byte to add. + * the byte to add. */ private void addByte(final byte b) { messageContents[currentOutputPosition] = b; @@ -121,7 +121,7 @@ private void addByte(final byte b) { * Adds the given bytes to the response. * * @param bytes - * the bytes to add. + * the bytes to add. */ void addBytes(final byte[] bytes) { if (bytes == null) { diff --git a/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java b/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java index a81ca80..3f43a24 100644 --- a/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java @@ -32,9 +32,12 @@ final class NtlmAuthScheme implements AuthScheme { // Type 1 flags over plain HTTP: engine defaults + SIGN | SEAL | KEY_EXCH (matches // NtlmMasqAsSpnegoScheme). Message sealing is what protects the SOAP over an unencrypted transport. - private static final int TYPE1_FLAGS_ENCRYPTED = (int) (Type1Message.getDefaultFlags() | - NTLMEngineUtils.NTLMSSP_NEGOTIATE_SIGN | - NTLMEngineUtils.NTLMSSP_NEGOTIATE_SEAL | + private static final int TYPE1_FLAGS_ENCRYPTED = (int) (Type1Message.getDefaultFlags() + | + NTLMEngineUtils.NTLMSSP_NEGOTIATE_SIGN + | + NTLMEngineUtils.NTLMSSP_NEGOTIATE_SEAL + | NTLMEngineUtils.NTLMSSP_NEGOTIATE_KEY_EXCH); // Type 1 flags over HTTPS: engine defaults only. TLS already provides confidentiality/integrity, so diff --git a/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java b/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java index f32c5ee..f1a18dc 100644 --- a/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java +++ b/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java @@ -33,8 +33,7 @@ */ final class NtlmCrypto { - static final String ENCRYPTED_CONTENT_TYPE = - "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""; + static final String ENCRYPTED_CONTENT_TYPE = "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""; private static final String BOUNDARY_CR = "--Encrypted Boundary\r\n"; private static final String BOUNDARY_END = "--Encrypted Boundary--\r\n"; @@ -94,9 +93,9 @@ private static void verify(final WinRMSession session, final byte[] unsealed, fi if (!Arrays.equals(checksum, expectedChecksum)) { throw new IllegalStateException( "Checksum mismatch\n" + - ByteArrayUtils.formatHexDump(checksum) + - "--\n" + - ByteArrayUtils.formatHexDump(expectedChecksum) + ByteArrayUtils.formatHexDump(checksum) + + "--\n" + + ByteArrayUtils.formatHexDump(expectedChecksum) ); } if (expectedSeqNum != seqNum) { @@ -107,7 +106,7 @@ private static void verify(final WinRMSession session, final byte[] unsealed, fi /** * @param outgoing true to sign an outgoing message (client signing key + client sealing stream), - * false to verify an incoming one (server signing key + server sealing stream). + * false to verify an incoming one (server signing key + server sealing stream). */ private static void calculateSignature( final WinRMSession session, @@ -182,7 +181,7 @@ void skipOver(final String s) { void skipUntil(final String s) { final byte[] expected = s.getBytes(StandardCharsets.US_ASCII); int next = index; - outer:while (true) { + outer: while (true) { for (int i = 0; i < expected.length; i++) { if (next + i >= bytes.length) { throw new IllegalStateException("Encrypted-response framing terminated early looking for delimiter"); diff --git a/src/main/java/org/metricshub/winrm/light/Type1Message.java b/src/main/java/org/metricshub/winrm/light/Type1Message.java index cfa3115..4c110b9 100644 --- a/src/main/java/org/metricshub/winrm/light/Type1Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type1Message.java @@ -24,7 +24,6 @@ /** * Type 1 message assembly class - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 @@ -45,22 +44,26 @@ class Type1Message extends NTLMMessage { final String unqualifiedDomain = NTLMEngineUtils.convertDomain(domain); hostBytes = unqualifiedHost != null ? unqualifiedHost.getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED) : null; - domainBytes = - unqualifiedDomain != null - ? unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED) - : null; + domainBytes = unqualifiedDomain != null + ? unqualifiedDomain.toUpperCase(Locale.ROOT).getBytes(NTLMEngineUtils.UNICODE_LITTLE_UNMARKED) + : null; } static int getDefaultFlags() { return ( // Required flags - NTLMEngineUtils.FLAG_REQUEST_NTLM_V1 | - NTLMEngineUtils.FLAG_REQUEST_NTLM2_SESSION | - NTLMEngineUtils.FLAG_REQUEST_VERSION | - NTLMEngineUtils.FLAG_REQUEST_ALWAYS_SIGN | - NTLMEngineUtils.FLAG_REQUEST_128BIT_KEY_EXCH | - NTLMEngineUtils.FLAG_REQUEST_56BIT_ENCRYPTION | - NTLMEngineUtils.FLAG_REQUEST_UNICODE_ENCODING - ); + NTLMEngineUtils.FLAG_REQUEST_NTLM_V1 + | + NTLMEngineUtils.FLAG_REQUEST_NTLM2_SESSION + | + NTLMEngineUtils.FLAG_REQUEST_VERSION + | + NTLMEngineUtils.FLAG_REQUEST_ALWAYS_SIGN + | + NTLMEngineUtils.FLAG_REQUEST_128BIT_KEY_EXCH + | + NTLMEngineUtils.FLAG_REQUEST_56BIT_ENCRYPTION + | + NTLMEngineUtils.FLAG_REQUEST_UNICODE_ENCODING); } /** diff --git a/src/main/java/org/metricshub/winrm/light/Type2Message.java b/src/main/java/org/metricshub/winrm/light/Type2Message.java index 904f8d1..30b655c 100644 --- a/src/main/java/org/metricshub/winrm/light/Type2Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type2Message.java @@ -22,7 +22,6 @@ /** * Type 2 message class - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 diff --git a/src/main/java/org/metricshub/winrm/light/Type3Message.java b/src/main/java/org/metricshub/winrm/light/Type3Message.java index bcaca9c..df76f69 100644 --- a/src/main/java/org/metricshub/winrm/light/Type3Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type3Message.java @@ -26,7 +26,6 @@ /** * Type 3 message assembly class - * * Code from io.cloudsoft.winrm4j.client.ntlm.forks.httpclient.NTLMEngineImpl * release 0.12.3 @link https://github.com/cloudsoft/winrm4j * io.cloudsoft.winrm4j.client.ntlm.forks.httpclient is a fork of apache-httpclient 4.5.13 @@ -56,7 +55,8 @@ public class Type3Message extends NTLMMessage { private final byte[] sessionKey; private final byte[] exportedSessionKey; - /** More primitive constructor: don't include cert or previous messages. + /** + * More primitive constructor: don't include cert or previous messages. */ Type3Message( final String domain, @@ -85,7 +85,7 @@ public class Type3Message extends NTLMMessage { byte[] responseTargetInformation = targetInformation; - // Create a cipher generator class. Use domain BEFORE it gets modified! + // Create a cipher generator class. Use domain BEFORE it gets modified! final CipherGen gen = new CipherGen( random, currentTime, @@ -103,9 +103,9 @@ public class Type3Message extends NTLMMessage { try { // This conditional may not work on Windows Server 2008 R2 and above, where it has not yet // been tested - if ( - ((type2Flags & NTLMEngineUtils.FLAG_TARGETINFO_PRESENT) != 0) && targetInformation != null && target != null - ) { + if (((type2Flags & NTLMEngineUtils.FLAG_TARGETINFO_PRESENT) != 0) + && targetInformation != null + && target != null) { // NTLMv2 ntResp = gen.getNTLMv2Response(); lmResp = gen.getLMv2Response(); @@ -250,31 +250,26 @@ protected void buildMessage() { // Flags. addULong( /* - //FLAG_WORKSTATION_PRESENT | - //FLAG_DOMAIN_PRESENT | - - // Required flags - (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | - (type2Flags & FLAG_REQUEST_NTLMv1) | - (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | - - // Protocol version request - FLAG_REQUEST_VERSION | - - // Recommended privacy settings - (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | - (type2Flags & FLAG_REQUEST_SEAL) | - (type2Flags & FLAG_REQUEST_SIGN) | - - // These must be set according to documentation, based on use of SEAL above - (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | - (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | - (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | - - (type2Flags & FLAG_TARGETINFO_PRESENT) | - (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | - (type2Flags & FLAG_REQUEST_TARGET) - */ + * //FLAG_WORKSTATION_PRESENT | + * //FLAG_DOMAIN_PRESENT | + * // Required flags + * (type2Flags & FLAG_REQUEST_LAN_MANAGER_KEY) | + * (type2Flags & FLAG_REQUEST_NTLMv1) | + * (type2Flags & FLAG_REQUEST_NTLM2_SESSION) | + * // Protocol version request + * FLAG_REQUEST_VERSION | + * // Recommended privacy settings + * (type2Flags & FLAG_REQUEST_ALWAYS_SIGN) | + * (type2Flags & FLAG_REQUEST_SEAL) | + * (type2Flags & FLAG_REQUEST_SIGN) | + * // These must be set according to documentation, based on use of SEAL above + * (type2Flags & FLAG_REQUEST_128BIT_KEY_EXCH) | + * (type2Flags & FLAG_REQUEST_56BIT_ENCRYPTION) | + * (type2Flags & FLAG_REQUEST_EXPLICIT_KEY_EXCH) | + * (type2Flags & FLAG_TARGETINFO_PRESENT) | + * (type2Flags & FLAG_REQUEST_UNICODE_ENCODING) | + * (type2Flags & FLAG_REQUEST_TARGET) + */ type2Flags ); diff --git a/src/main/java/org/metricshub/winrm/light/WinRMSession.java b/src/main/java/org/metricshub/winrm/light/WinRMSession.java index 6f61260..b898255 100644 --- a/src/main/java/org/metricshub/winrm/light/WinRMSession.java +++ b/src/main/java/org/metricshub/winrm/light/WinRMSession.java @@ -35,14 +35,14 @@ final class WinRMSession { // Protocol-defined constants: they MUST encode to the same bytes on every JVM, so pin US-ASCII // rather than relying on the platform default charset (which could differ, e.g. UTF-16). - private static final byte[] CLIENT_SIGNING = - "session key to client-to-server signing key magic constant\0".getBytes(StandardCharsets.US_ASCII); - private static final byte[] SERVER_SIGNING = - "session key to server-to-client signing key magic constant\0".getBytes(StandardCharsets.US_ASCII); - private static final byte[] CLIENT_SEALING = - "session key to client-to-server sealing key magic constant\0".getBytes(StandardCharsets.US_ASCII); - private static final byte[] SERVER_SEALING = - "session key to server-to-client sealing key magic constant\0".getBytes(StandardCharsets.US_ASCII); + private static final byte[] CLIENT_SIGNING = "session key to client-to-server signing key magic constant\0" + .getBytes(StandardCharsets.US_ASCII); + private static final byte[] SERVER_SIGNING = "session key to server-to-client signing key magic constant\0" + .getBytes(StandardCharsets.US_ASCII); + private static final byte[] CLIENT_SEALING = "session key to client-to-server sealing key magic constant\0" + .getBytes(StandardCharsets.US_ASCII); + private static final byte[] SERVER_SEALING = "session key to server-to-client sealing key magic constant\0" + .getBytes(StandardCharsets.US_ASCII); private final String domain; private final String workstation; diff --git a/src/main/java/org/metricshub/winrm/light/WsmanClient.java b/src/main/java/org/metricshub/winrm/light/WsmanClient.java index 3a8b399..f9ef229 100644 --- a/src/main/java/org/metricshub/winrm/light/WsmanClient.java +++ b/src/main/java/org/metricshub/winrm/light/WsmanClient.java @@ -350,10 +350,9 @@ private static String text(final Document doc, final String localName) { * backend does. */ static boolean hasEnumerationElement(final Document doc, final String localName) { - return ( - doc.getElementsByTagNameNS(WS_ENUMERATION_NS, localName).getLength() > 0 || - doc.getElementsByTagNameNS(WSMAN_NS, localName).getLength() > 0 - ); + return (doc.getElementsByTagNameNS(WS_ENUMERATION_NS, localName).getLength() > 0 + || + doc.getElementsByTagNameNS(WSMAN_NS, localName).getLength() > 0); } /** First text content of an element matched by both namespace and local name. */ diff --git a/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java b/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java index 81aa82f..cc508f1 100644 --- a/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java +++ b/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java @@ -139,7 +139,6 @@ public int getPort() { * @param protocol The HTTP protocol * @param hostname The host name * @param port The port - * * @return the endpoint URL in form of protocol://hostname:port (with protocol in HTTP or HTTPS). */ public static String buildEndpointUrl( @@ -153,15 +152,15 @@ public static String buildEndpointUrl( } /** - * Get the endpoint port: - *

      + * Get the endpoint port: + *
        *
      • The port if provided
      • *
      • 5986 if the protocol is HTTPS
      • *
      • 5985 otherwise
      • - *
      + *
    + * * @param protocol The HTTP protocol * @param port The port - * * @return The endpoint port */ public static int getEndpointPort(final WinRMHttpProtocolEnum protocol, final Integer port) { @@ -174,8 +173,8 @@ public static int getEndpointPort(final WinRMHttpProtocolEnum protocol, final In /** * Get the namespace and replace '\' by '/' if necessary * default: {@value WmiHelper#DEFAULT_NAMESPACE} if null - * @param namespace the provided namespace * + * @param namespace the provided namespace * @return */ public static String buildNamespace(final String namespace) { @@ -190,7 +189,6 @@ public static String buildNamespace(final String namespace) { * Build the WSMan URL endpoint. * * @param endpoint the endpoint in form of protocol://hostname:port (with protocol in HTTP or HTTPS). - * * @return The external form of the endpoint URL */ private static String buildWSManEndpoint(final String endpoint) { @@ -222,12 +220,13 @@ public boolean equals(final Object obj) { return false; } final WinRMEndpoint other = (WinRMEndpoint) obj; - return ( - Objects.equals(endpoint, other.endpoint) && - Objects.equals(namespace, other.namespace) && - Arrays.equals(password, other.password) && - Objects.equals(rawUsername, other.rawUsername) - ); + return (Objects.equals(endpoint, other.endpoint) + && + Objects.equals(namespace, other.namespace) + && + Arrays.equals(password, other.password) + && + Objects.equals(rawUsername, other.rawUsername)); } @Override diff --git a/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java b/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java index d63f227..4690dd8 100644 --- a/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java +++ b/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java @@ -47,13 +47,13 @@ private WinRMExecutorFactory() {} /** * Create a {@link WindowsRemoteExecutor} (light backend). * - * @param winRMEndpoint endpoint with credentials (mandatory) - * @param timeout timeout in milliseconds (must be > 0) - * @param ticketCache Kerberos ticket cache path (may be {@code null}) + * @param winRMEndpoint endpoint with credentials (mandatory) + * @param timeout timeout in milliseconds (must be > 0) + * @param ticketCache Kerberos ticket cache path (may be {@code null}) * @param authentications requested authentication schemes (may be {@code null}) * @return a light-backed executor * @throws WinRMException for any problem creating the executor, or when {@value #BACKEND_PROPERTY} - * requests the removed CXF backend or an unknown value + * requests the removed CXF backend or an unknown value */ public static WindowsRemoteExecutor createInstance( final WinRMEndpoint winRMEndpoint, @@ -70,8 +70,8 @@ public static WindowsRemoteExecutor createInstance( // switched to another implementation. throw new WinRMException( "The CXF WinRM backend was removed in winrm-java 2.0.0; remove the " + - BACKEND_PROPERTY + - " system property to use the light backend (or stay on winrm-java 1.x)." + BACKEND_PROPERTY + + " system property to use the light backend (or stay on winrm-java 1.x)." ); } throw new WinRMException( diff --git a/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java b/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java index c1a5533..744616a 100644 --- a/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java +++ b/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java @@ -38,7 +38,6 @@ public enum AuthenticationEnum { * Get the value of the enum Name regardless the cardinality. * * @param name The enum value name - * * @return An optional with the enum value if found empty otherwise */ public static Optional getValueOf(final String name) { diff --git a/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java b/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java index ee153ef..21dd1f1 100644 --- a/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java +++ b/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java @@ -93,9 +93,7 @@ private SmbTempShare( * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) * @param ticketCache The Ticket Cache path * @param authentications List of authentications. only NTLM if absent - * * @return SmbTempShare instance - * * @throws IOException If an I/O error occurred * @throws WinRMException For any problem encountered * @throws TimeoutException To notify userName of timeout. @@ -124,8 +122,8 @@ public static SmbTempShare createInstance( try { // Honour the backend toggle: SMB file transfer is always smbj, but the WinRM command // orchestration follows the selected backend (so "light" does not fall back to CXF). - windowsRemoteExecutor = - WinRMExecutorFactory.createInstance(winRMEndpoint, timeout, ticketCache, authentications); + windowsRemoteExecutor = WinRMExecutorFactory + .createInstance(winRMEndpoint, timeout, ticketCache, authentications); final WindowsTempShare windowsTempShare = getOrCreateShare( windowsRemoteExecutor, @@ -291,10 +289,8 @@ public synchronized void close() throws IOException { * @param remotePath The remote path. * @param shareName The Share Name. * @param timeout Timeout in milliseconds. - * * @throws TimeoutException To notify userName of timeout. * @throws WindowsRemoteException For any problem encountered - * */ private static void shareRemoteDirectory( final WindowsRemoteExecutor windowsRemoteExecutor, diff --git a/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java b/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java index e16e8e3..38211a7 100644 --- a/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java +++ b/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java @@ -57,6 +57,7 @@ public WinRMWqlExecutor(final long executionTime, final List headers, fi /** * Get the execution time of the query. + * * @return */ public long getExecutionTime() { @@ -65,6 +66,7 @@ public long getExecutionTime() { /** * Get the headers of the query. + * * @return */ public List getHeaders() { @@ -73,6 +75,7 @@ public List getHeaders() { /** * Get the result rows of the query. + * * @return */ public List> getRows() { @@ -92,9 +95,7 @@ public List> getRows() { * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero) * @param ticketCache The Ticket Cache path * @param authentications List of authentications. only NTLM if absent - * * @return WinRMWqlExecutor result instance with header, rows and execution time. - * * @throws WinRMException For any problem encountered on remote * @throws WqlQuerySyntaxException On WQL syntax errors * @throws TimeoutException To notify userName of timeout @@ -124,8 +125,7 @@ public static WinRMWqlExecutor executeWql( timeout, ticketCache, authentications - ) - ) { + )) { final List> result = winRMService.executeWql(wqlQuery, timeout); // Extract the list of properties from the result, with same order as in the WQL query diff --git a/src/test/java/org/metricshub/winrm/WinRMLiveTest.java b/src/test/java/org/metricshub/winrm/WinRMLiveTest.java index b2c7984..9ca595a 100644 --- a/src/test/java/org/metricshub/winrm/WinRMLiveTest.java +++ b/src/test/java/org/metricshub/winrm/WinRMLiveTest.java @@ -19,8 +19,8 @@ * differential harness (the CXF baseline was removed with the backend; result parity was * proven and gated before removal). Disabled unless {@code winrm.live.host} is set, so it * never runs in CI. - * - *

    One-command run against a lab host: + *

    + * One-command run against a lab host: * *

      * mvn test -Dtest=WinRMLiveTest \
    @@ -29,8 +29,8 @@
      *   -Dwinrm.live.username='MYDOMAIN\myuser' \
      *   -Dwinrm.live.password-file=/path/to/password.txt
      * 
    - * - *

    Optional properties: {@code winrm.live.port} (defaults to 5985/5986 by protocol), + *

    + * Optional properties: {@code winrm.live.port} (defaults to 5985/5986 by protocol), * {@code winrm.live.password} (inline, instead of the file), {@code winrm.live.namespace}, * {@code winrm.live.wql}, {@code winrm.live.command}, and {@code winrm.live.tls.insecure=true} * to skip TLS validation for hosts with self-signed certificates. @@ -50,10 +50,9 @@ class WinRMLiveTest { @BeforeAll static void readConfiguration() throws Exception { host = System.getProperty("winrm.live.host"); - protocol = - "https".equalsIgnoreCase(System.getProperty("winrm.live.protocol", "http")) - ? WinRMHttpProtocolEnum.HTTPS - : WinRMHttpProtocolEnum.HTTP; + protocol = "https".equalsIgnoreCase(System.getProperty("winrm.live.protocol", "http")) + ? WinRMHttpProtocolEnum.HTTPS + : WinRMHttpProtocolEnum.HTTP; final String portProperty = System.getProperty("winrm.live.port"); port = portProperty == null ? null : Integer.valueOf(portProperty); username = System.getProperty("winrm.live.username"); diff --git a/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java b/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java index ef9f08e..5d657eb 100644 --- a/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java +++ b/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java @@ -52,110 +52,104 @@ void testExecute() throws Exception { // check arguments assertThrows( IllegalArgumentException.class, - () -> - execute( - null, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + null, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) ); assertThrows( IllegalArgumentException.class, - () -> - execute( - command, - HTTPS, - null, - 5986, - username, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + command, + HTTPS, + null, + 5986, + username, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) ); assertThrows( IllegalArgumentException.class, - () -> - execute( - command, - HTTPS, - hostname, - 5986, - null, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + command, + HTTPS, + hostname, + 5986, + null, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) ); assertThrows( IllegalArgumentException.class, - () -> - execute( - command, - HTTPS, - hostname, - 5986, - username, - null, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + null, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) ); assertThrows( IllegalArgumentException.class, - () -> - execute( - command, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - -1L, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + -1L, + localFileToCopyList, + ticketCache, + authentications + ) ); assertThrows( IllegalArgumentException.class, - () -> - execute( - command, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - 0L, - localFileToCopyList, - ticketCache, - authentications - ) + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + 0L, + localFileToCopyList, + ticketCache, + authentications + ) ); // case localFileToCopyList null or empty @@ -163,8 +157,7 @@ void testExecute() throws Exception { final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( WindowsRemoteProcessUtils.class ); - final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class) - ) { + final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class)) { mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); @@ -191,8 +184,7 @@ void testExecute() throws Exception { final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( WindowsRemoteProcessUtils.class ); - final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class) - ) { + final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class)) { mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); mockedWindowsRemoteProcessUtils diff --git a/src/test/java/org/metricshub/winrm/light/FakeWsmanServer.java b/src/test/java/org/metricshub/winrm/light/FakeWsmanServer.java index bc4d62d..3b50799 100644 --- a/src/test/java/org/metricshub/winrm/light/FakeWsmanServer.java +++ b/src/test/java/org/metricshub/winrm/light/FakeWsmanServer.java @@ -27,8 +27,8 @@ * client's own crypto primitives with a mirrored {@link WinRMSession} — then serves scripted * SOAP response bodies. This exercises the client's full protocol path (transport, handshake * orchestration, sealing, multipart framing, decryption, XML handling) without a Windows host. - * - *

    The NTLMv2 verification is real: a client that derives a wrong hash (e.g. a domain-case + *

    + * The NTLMv2 verification is real: a client that derives a wrong hash (e.g. a domain-case * regression) fails authentication here just like against a real host. */ final class FakeWsmanServer implements AutoCloseable { @@ -49,21 +49,26 @@ static final class Scripted { // Fixed 8-byte server challenge — "recorded exchange" determinism. private static final byte[] SERVER_CHALLENGE = { - 0x01, - 0x23, - 0x45, - 0x67, - (byte) 0x89, - (byte) 0xab, - (byte) 0xcd, - (byte) 0xef + 0x01, + 0x23, + 0x45, + 0x67, + (byte) 0x89, + (byte) 0xab, + (byte) 0xcd, + (byte) 0xef }; // Type 2 flags: UNICODE | SIGN | SEAL | EXTENDED_SESSIONSECURITY | TARGETINFO | 128 | KEY_EXCH — // what a real WinRM host negotiates for encrypted HTTP, and what drives the client down the // NTLMv2 + explicit-key-exchange + extended-session-security path. - private static final int TYPE2_FLAGS = - 0x00000001 | 0x00000010 | 0x00000020 | 0x00080000 | 0x00800000 | 0x20000000 | 0x40000000; + private static final int TYPE2_FLAGS = 0x00000001 + | 0x00000010 + | 0x00000020 + | 0x00080000 + | 0x00800000 + | 0x20000000 + | 0x40000000; private final String expectedDomain; private final String expectedUser; @@ -193,13 +198,12 @@ private void serveScripted(final OutputStream out, final WinRMSession session, f } if (next == null) { // Loud, decryptable failure so an over-consuming test fails on an assertion, not a hang. - next = - new Scripted( - 500, - "" + + next = new Scripted( + 500, + "" + "FakeWsmanServer: no scripted response left" + "" - ); + ); } final byte[] sealed = NtlmCrypto.encryptAndSign(session, next.soapBody.getBytes(StandardCharsets.UTF_8)); respond(out, next.status, null, NtlmCrypto.ENCRYPTED_CONTENT_TYPE, sealed); @@ -220,7 +224,8 @@ private static byte[] buildType2() { final byte[] targetInfo = concat( avPair(2, "FAKE"), // NetBIOS domain avPair(1, "FAKESRV"), // NetBIOS computer - new byte[] { 0, 0, 0, 0 } // terminator + new byte[] + { 0, 0, 0, 0 } // terminator ); final ByteArrayOutputStream msg = new ByteArrayOutputStream(); writeBytes(msg, "NTLMSSP\0".getBytes(StandardCharsets.US_ASCII)); diff --git a/src/test/java/org/metricshub/winrm/light/WsmanClientParityTest.java b/src/test/java/org/metricshub/winrm/light/WsmanClientParityTest.java index 4ccda7e..66d1f09 100644 --- a/src/test/java/org/metricshub/winrm/light/WsmanClientParityTest.java +++ b/src/test/java/org/metricshub/winrm/light/WsmanClientParityTest.java @@ -31,11 +31,11 @@ private static Document parse(final String xml) throws Exception { void endOfSequenceDetectedInWsEnumerationNamespace() throws Exception { final Document doc = parse( "" + - "" + - "" + - "" + "" + + "" + + "" ); assertTrue(WsmanClient.hasEnumerationElement(doc, "EndOfSequence")); } @@ -46,13 +46,13 @@ void endOfSequenceDetectedInWsmanNamespaceVariant() throws Exception { // backend accepts both variants, so the light backend must too. final Document doc = parse( "" + - "" + - "" + - "" + "" + + "" + + "" ); assertTrue(WsmanClient.hasEnumerationElement(doc, "EndOfSequence")); } @@ -63,17 +63,17 @@ void wmiPropertyNamedEndOfSequenceIsNotMistakenForTheMarker() throws Exception { // and must not terminate the enumeration. final Document doc = parse( "" + - "" + - "" + - "" + - "oops" + - "" + - "" + - "" + "" + + "" + + "" + + "oops" + + "" + + "" + + "" ); assertFalse(WsmanClient.hasEnumerationElement(doc, "EndOfSequence")); } @@ -83,32 +83,32 @@ void wmiPropertyNamedEndOfSequenceIsNotMistakenForTheMarker() throws Exception { @Test void itemsCollectedFromBothNamespaceVariants() throws Exception { // EnumerateResponse carries wsen:Items, PullResponse carries wsman:Items; both must yield rows. - final String instance = - "" + + final String instance = "" + + "Spooler" + "Running" + ""; final Document wsenItems = parse( "" + - "" + - "" + - instance + - "" + - "" + "" + + "" + + instance + + "" + + "" ); final Document wsmanItems = parse( "" + - "" + - "" + - instance + - "" + - "" + "" + + "" + + instance + + "" + + "" ); for (final Document doc : List.of(wsenItems, wsmanItems)) { @@ -126,18 +126,18 @@ void wmiPropertyNamedItemsIsNotMistakenForTheWrapper() throws Exception { // second Items wrapper producing phantom rows. final Document doc = parse( "" + - "" + - "" + - "" + - "real-row" + - "phantom" + - "" + - "" + - "" + "" + + "" + + "" + + "real-row" + + "phantom" + + "" + + "" + + "" ); final List> rows = new ArrayList<>(); WsmanClient.collectItems(doc, rows); @@ -153,14 +153,15 @@ void faultSummaryCarriesWsmanFaultDetailWithWbemMnemonic() throws Exception { // matching WBEM_E_* in the exception message — the detail Message text must surface. final Document doc = parse( "" + - "" + - "The WS-Management service cannot process the request." + - "" + - "" + - "The WMI service or the WMI provider returned an unknown error: WBEM_E_INVALID_CLASS" + - "" + - "" + - "" + "" + + "The WS-Management service cannot process the request." + + + "" + + "" + + "The WMI service or the WMI provider returned an unknown error: WBEM_E_INVALID_CLASS" + + "" + + "" + + "" ); final String summary = WsmanClient.faultSummary(500, doc); assertTrue(summary.contains("HTTP 500"), summary); @@ -175,18 +176,18 @@ void faultSummaryDoesNotDuplicateDetailAlreadyInReason() throws Exception { final String message = "The WMI service or the WMI provider returned an unknown error: WBEM_E_INVALID_NAMESPACE"; final Document doc = parse( "" + - "" + - "" + - message + - " " + - "" + - "" + - "" + - message + - " " + - "" + - "" + - "" + "" + + "" + + message + + " " + + "" + + "" + + "" + + message + + " " + + "" + + "" + + "" ); final String summary = WsmanClient.faultSummary(500, doc); final int first = summary.indexOf("WBEM_E_INVALID_NAMESPACE"); @@ -199,9 +200,9 @@ void faultSummaryDoesNotDuplicateDetailAlreadyInReason() throws Exception { void faultSummaryWithoutWsmanFaultDetailStillReadable() throws Exception { final Document doc = parse( "" + - "" + - "Some transport-level failure" + - "" + "" + + "Some transport-level failure" + + "" ); assertEquals("HTTP 500: Some transport-level failure", WsmanClient.faultSummary(500, doc)); } diff --git a/src/test/java/org/metricshub/winrm/light/WsmanProtocolTest.java b/src/test/java/org/metricshub/winrm/light/WsmanProtocolTest.java index 3b236cb..9a275ba 100644 --- a/src/test/java/org/metricshub/winrm/light/WsmanProtocolTest.java +++ b/src/test/java/org/metricshub/winrm/light/WsmanProtocolTest.java @@ -70,41 +70,41 @@ void wqlPagesAcrossEnumerateAndPullsOverEncryptedNtlm() throws Exception { 200, envelope( "" + - "uuid:CTX-1" + - "" + - service("Spooler", "Running") + - "" + - "" + WSEN + + "\" xmlns:wsman=\"" + + WSMAN + + "\">" + + "uuid:CTX-1" + + "" + + service("Spooler", "Running") + + "" + + "" ) ) .enqueue( 200, envelope( "" + - "uuid:CTX-2" + - "" + - service("WinRM", "Running") + - service("Wecsvc", "Stopped") + - "" + - "" + WSEN + + "\">" + + "uuid:CTX-2" + + "" + + service("WinRM", "Running") + + service("Wecsvc", "Stopped") + + "" + + "" ) ) .enqueue( 200, envelope( "" + - "" + - "" + WSEN + + "\" xmlns:wsman=\"" + + WSMAN + + "\">" + + "" + + "" ) ); @@ -290,19 +290,16 @@ private static String envelope(final String body) { } private static String service(final String name, final String state) { - return ( - "" + + return ("" + "" + name + "" + state + - "" - ); + ""); } private static String resourceCreated(final String shellId) { - return ( - "" + @@ -314,51 +311,42 @@ private static String resourceCreated(final String shellId) { "" + shellId + "" + - "" - ); + ""); } private static String commandResponse(final String commandId) { - return ( - "" + commandId + - "" - ); + ""); } private static String receiveResponse(final String commandId, final String streams, final String commandState) { - return ( - "" + streams + (commandState == null ? "" : commandState) + - "" - ); + ""); } private static String stream(final String name, final byte[] content) { - return ( - "" + Base64.getEncoder().encodeToString(content) + - "" - ); + ""); } private static String done(final String commandId, final int exitCode) { - return ( - "" + exitCode + - "" - ); + ""); } private static String fault(final String code, final String reason) { @@ -366,8 +354,7 @@ private static String fault(final String code, final String reason) { } private static String fault(final String code, final String reason, final String detailMessage) { - return ( - "" + "s:Receiver" + @@ -382,7 +369,6 @@ private static String fault(final String code, final String reason, final String "" + (detailMessage == null ? reason : detailMessage) + "" + - "" - ); + ""); } } diff --git a/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java b/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java index 17122a1..5db2a3f 100644 --- a/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java +++ b/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java @@ -22,7 +22,7 @@ class WinRMEndpointTest { @Test void testWinRMEndpoint() { - //check arguments + // check arguments assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, null, 5985, USER, PASSWORD, null)); assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, HOSTNAME, 5985, null, PASSWORD, null)); @@ -130,7 +130,7 @@ void testBuildNamespace() { assertEquals("ROOT/CIMV2", buildNamespace(EMPTY)); assertEquals("ROOT/CIMV2", buildNamespace(" \t\r\n ")); - //case namespace provided + // case namespace provided assertEquals("root", buildNamespace("root")); assertEquals("root/cimv2", buildNamespace(" \t\r\n root \t\r\n / \t\r\n cimv2 \t\r\n ")); assertEquals("Srv/StandardCimv2", buildNamespace("Srv\\StandardCimv2")); diff --git a/src/test/java/org/metricshub/winrm/service/WinRMExecutorFactoryTest.java b/src/test/java/org/metricshub/winrm/service/WinRMExecutorFactoryTest.java index 554afb5..29baec9 100644 --- a/src/test/java/org/metricshub/winrm/service/WinRMExecutorFactoryTest.java +++ b/src/test/java/org/metricshub/winrm/service/WinRMExecutorFactoryTest.java @@ -35,8 +35,7 @@ void lightBackendSelectedForHttpNtlm() throws Exception { 30000L, null, List.of(AuthenticationEnum.NTLM) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); assertEquals("testhost", executor.getHostname()); } @@ -52,8 +51,7 @@ void defaultBackendIsLight() throws Exception { 30000L, null, List.of(AuthenticationEnum.NTLM) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); } } @@ -65,13 +63,12 @@ void cxfBackendRejectedWithRemovalMessage() { System.setProperty(WinRMExecutorFactory.BACKEND_PROPERTY, "cxf"); final WinRMException e = assertThrows( WinRMException.class, - () -> - WinRMExecutorFactory.createInstance( - endpoint(WinRMHttpProtocolEnum.HTTP), - 30000L, - null, - List.of(AuthenticationEnum.NTLM) - ) + () -> WinRMExecutorFactory.createInstance( + endpoint(WinRMHttpProtocolEnum.HTTP), + 30000L, + null, + List.of(AuthenticationEnum.NTLM) + ) ); assertTrue(e.getMessage().contains("removed in winrm-java 2.0.0"), e.getMessage()); } @@ -86,8 +83,7 @@ void defaultBackendAcceptsHttps() throws Exception { 30000L, null, List.of(AuthenticationEnum.NTLM) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); } } @@ -101,8 +97,7 @@ void lightBackendAcceptsHttps() throws Exception { 30000L, null, List.of(AuthenticationEnum.NTLM) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); } } @@ -114,13 +109,12 @@ void kerberosOnlyOverHttpRejected() { System.setProperty(WinRMExecutorFactory.BACKEND_PROPERTY, "light"); assertThrows( WinRMException.class, - () -> - WinRMExecutorFactory.createInstance( - endpoint(WinRMHttpProtocolEnum.HTTP), - 30000L, - null, - List.of(AuthenticationEnum.KERBEROS) - ) + () -> WinRMExecutorFactory.createInstance( + endpoint(WinRMHttpProtocolEnum.HTTP), + 30000L, + null, + List.of(AuthenticationEnum.KERBEROS) + ) ); } @@ -135,8 +129,7 @@ void mixedKerberosNtlmFallsBackToNtlmOverHttp() throws Exception { 30000L, null, List.of(AuthenticationEnum.KERBEROS, AuthenticationEnum.NTLM) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); } } @@ -152,8 +145,7 @@ void kerberosOverHttpsAccepted() throws Exception { 30000L, null, List.of(AuthenticationEnum.KERBEROS) - ) - ) { + )) { assertInstanceOf(LightWinRMService.class, executor); } } @@ -165,13 +157,12 @@ void unsupportedBackendValueRejected() { System.setProperty(WinRMExecutorFactory.BACKEND_PROPERTY, "cxff"); assertThrows( WinRMException.class, - () -> - WinRMExecutorFactory.createInstance( - endpoint(WinRMHttpProtocolEnum.HTTP), - 30000L, - null, - List.of(AuthenticationEnum.NTLM) - ) + () -> WinRMExecutorFactory.createInstance( + endpoint(WinRMHttpProtocolEnum.HTTP), + 30000L, + null, + List.of(AuthenticationEnum.NTLM) + ) ); } diff --git a/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java b/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java index 7896093..f807b5f 100644 --- a/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java +++ b/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java @@ -53,7 +53,7 @@ void testCreateInstance() throws Exception { final Path ticketCache = get("path"); final List authentications = singletonList(NTLM); - //check arguments + // check arguments assertThrows(IllegalArgumentException.class, () -> createInstance(null, timeout, ticketCache, authentications)); assertThrows( @@ -67,8 +67,7 @@ void testCreateInstance() throws Exception { final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class); final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class); final MockedStatic mockedWindowsTempShare = mockStatic(WindowsTempShare.class); - final MockedStatic mockedSmbConfig = mockStatic(SmbConfig.class) - ) { + final MockedStatic mockedSmbConfig = mockStatic(SmbConfig.class)) { final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); mockedFactory .when(() -> WinRMExecutorFactory.createInstance(winRMEndpoint, timeout, null, null)) From 32d07ce47425e6e6ff36281c9d9b59a222480164 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Fri, 24 Jul 2026 01:41:26 +0200 Subject: [PATCH 3/3] Normalize line endings and make the formatter line-ending agnostic (#110) The repository mixed CRLF and LF text files, and formatter-maven-plugin's default lineEnding AUTO enforces the current platform's separator, so validation passed on Windows but failed on the Linux CI runners (and vice versa). Store all text files with LF via .gitattributes, renormalize the affected files, and set lineEnding=KEEP so validation accepts the checked-out line endings on every platform, as Prettier's endOfLine=auto did. Also refresh the license headers with license:update-file-header (copyright years and organization name). Co-Authored-By: Claude Fable 5 --- .gitattributes | 2 + pom.xml | 1 + .../winrm/ShareRemoteDirectoryConsumer.java | 68 +- .../org/metricshub/winrm/TimeoutHelper.java | 180 ++--- src/main/java/org/metricshub/winrm/Utils.java | 342 ++++---- .../winrm/WinRMHttpProtocolEnum.java | 58 +- .../winrm/WindowsRemoteCommandResult.java | 198 ++--- .../winrm/WindowsRemoteExecutor.java | 186 ++--- .../winrm/WindowsRemoteProcessUtils.java | 468 +++++------ .../metricshub/winrm/WindowsTempShare.java | 742 +++++++++--------- .../java/org/metricshub/winrm/WmiHelper.java | 308 ++++---- .../java/org/metricshub/winrm/WqlQuery.java | 482 ++++++------ .../winrm/command/WinRMCommandExecutor.java | 312 ++++---- .../winrm/exceptions/WinRMException.java | 84 +- .../exceptions/WindowsRemoteException.java | 92 +-- .../exceptions/WqlQuerySyntaxException.java | 74 +- .../metricshub/winrm/light/AuthScheme.java | 2 +- .../winrm/light/ByteArrayUtils.java | 2 +- .../org/metricshub/winrm/light/CipherGen.java | 2 +- .../winrm/light/EncryptionUtils.java | 2 +- .../org/metricshub/winrm/light/Envelopes.java | 2 +- .../winrm/light/FallbackAuthScheme.java | 2 +- .../org/metricshub/winrm/light/HMACMD5.java | 2 +- .../metricshub/winrm/light/HttpTransport.java | 2 +- .../winrm/light/KerberosAuthScheme.java | 2 +- .../org/metricshub/winrm/light/LightTls.java | 2 +- .../winrm/light/LightWinRMService.java | 2 +- .../java/org/metricshub/winrm/light/MD4.java | 2 +- .../winrm/light/NTLMEngineUtils.java | 2 +- .../metricshub/winrm/light/NTLMMessage.java | 2 +- .../winrm/light/NtlmAuthScheme.java | 2 +- .../metricshub/winrm/light/NtlmCrypto.java | 2 +- .../metricshub/winrm/light/NtlmException.java | 2 +- .../metricshub/winrm/light/Type1Message.java | 2 +- .../metricshub/winrm/light/Type2Message.java | 2 +- .../metricshub/winrm/light/Type3Message.java | 2 +- .../metricshub/winrm/light/WinRMSession.java | 2 +- .../metricshub/winrm/light/WsmanClient.java | 2 +- .../winrm/service/WinRMEndpoint.java | 494 ++++++------ .../winrm/service/WinRMExecutorFactory.java | 2 +- .../client/auth/AuthenticationEnum.java | 92 +-- .../metricshub/winrm/shares/SmbTempShare.java | 628 +++++++-------- .../winrm/wql/WinRMWqlExecutor.java | 296 +++---- .../command/WinRMCommandExecutorTest.java | 432 +++++----- .../winrm/service/WinRMEndpointTest.java | 276 +++---- .../client/auth/AuthenticationEnumTest.java | 54 +- .../winrm/shares/SmbTempShareTest.java | 278 +++---- .../winrm/wql/WinRMWqlExecutorTest.java | 240 +++--- 48 files changed, 3218 insertions(+), 3215 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dd6e652 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize line endings: text files are stored with LF in the repository. +* text=auto diff --git a/pom.xml b/pom.xml index 57347a0..0d0f597 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,7 @@ **/*.java metricshub-eclipse-formatter.xml + KEEP diff --git a/src/main/java/org/metricshub/winrm/ShareRemoteDirectoryConsumer.java b/src/main/java/org/metricshub/winrm/ShareRemoteDirectoryConsumer.java index ca98277..cc52bae 100644 --- a/src/main/java/org/metricshub/winrm/ShareRemoteDirectoryConsumer.java +++ b/src/main/java/org/metricshub/winrm/ShareRemoteDirectoryConsumer.java @@ -1,34 +1,34 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -@FunctionalInterface -public interface ShareRemoteDirectoryConsumer { - /** - * Share the remote directory on the host. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. - * @param remotePath The remote path. - * @param shareName The Share Name. - * @param timeout Timeout in milliseconds. - */ - public void apply(final W windowsRemoteExecutor, final R remotePath, final S shareName, final T timeout); -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +@FunctionalInterface +public interface ShareRemoteDirectoryConsumer { + /** + * Share the remote directory on the host. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. + * @param remotePath The remote path. + * @param shareName The Share Name. + * @param timeout Timeout in milliseconds. + */ + public void apply(final W windowsRemoteExecutor, final R remotePath, final S shareName, final T timeout); +} diff --git a/src/main/java/org/metricshub/winrm/TimeoutHelper.java b/src/main/java/org/metricshub/winrm/TimeoutHelper.java index 35092f3..8fe13ef 100644 --- a/src/main/java/org/metricshub/winrm/TimeoutHelper.java +++ b/src/main/java/org/metricshub/winrm/TimeoutHelper.java @@ -1,90 +1,90 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.concurrent.TimeoutException; - -/** - * for stagged timeout handling. - */ -public class TimeoutHelper { - - private TimeoutHelper() {} - - /** - * Get the remaining time before timeout. - * - * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) - * @param start start time in milliseconds. (throws an IllegalArgumentException if negative or zero) - * @return The remaining time - * @throws TimeoutException if there's no time left - */ - public static long getRemainingTime(final long timeout, final long start, final String message) - throws TimeoutException { - long remaining = start + timeout - Utils.getCurrentTimeMillis(); - if (remaining < 0) { - throw new TimeoutException(message); - } - return remaining; - } - - /** - * Sub to make the thread sleep in a stepped delay. - *

    - * Delay: - *

      - *
    • 50 milliseconds for time from start < 500 milliseconds
    • - *
    • 100 milliseconds for time from start between 500 milliseconds and 1 second
    • - *
    • 250 milliseconds for time from start between 1 second and 5 seconds
    • - *
    • 500 milliseconds for time from start between 5 seconds and 10 seconds
    • - *
    • 1 second for time from start between 10 seconds and 30 seconds
    • - *
    • 3 seconds otherwise
    • - *
    - * - * @param timeout Timeout in milliseconds - * @param start start time in milliseconds - * @param message The timeout error message - * @throws TimeoutException when times out - */ - public static void stagedSleep(final long timeout, final long start, final String message) throws TimeoutException { - // Check we still have some time on our hands - getRemainingTime(timeout, start, message); - - final long elapsedTime = Utils.getCurrentTimeMillis() - start; - try { - if (elapsedTime < 500) { - Utils.sleep(50); - } else if (elapsedTime < 1000) { - Utils.sleep(100); - } else if (elapsedTime < 5000) { - Utils.sleep(250); - } else if (elapsedTime < 10000) { - Utils.sleep(500); - } else if (elapsedTime < 30000) { - Utils.sleep(1000); - } else { - Utils.sleep(3000); - } - } catch (InterruptedException e) { - throw new TimeoutException(message); - } - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.concurrent.TimeoutException; + +/** + * for stagged timeout handling. + */ +public class TimeoutHelper { + + private TimeoutHelper() {} + + /** + * Get the remaining time before timeout. + * + * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) + * @param start start time in milliseconds. (throws an IllegalArgumentException if negative or zero) + * @return The remaining time + * @throws TimeoutException if there's no time left + */ + public static long getRemainingTime(final long timeout, final long start, final String message) + throws TimeoutException { + long remaining = start + timeout - Utils.getCurrentTimeMillis(); + if (remaining < 0) { + throw new TimeoutException(message); + } + return remaining; + } + + /** + * Sub to make the thread sleep in a stepped delay. + *

    + * Delay: + *

      + *
    • 50 milliseconds for time from start < 500 milliseconds
    • + *
    • 100 milliseconds for time from start between 500 milliseconds and 1 second
    • + *
    • 250 milliseconds for time from start between 1 second and 5 seconds
    • + *
    • 500 milliseconds for time from start between 5 seconds and 10 seconds
    • + *
    • 1 second for time from start between 10 seconds and 30 seconds
    • + *
    • 3 seconds otherwise
    • + *
    + * + * @param timeout Timeout in milliseconds + * @param start start time in milliseconds + * @param message The timeout error message + * @throws TimeoutException when times out + */ + public static void stagedSleep(final long timeout, final long start, final String message) throws TimeoutException { + // Check we still have some time on our hands + getRemainingTime(timeout, start, message); + + final long elapsedTime = Utils.getCurrentTimeMillis() - start; + try { + if (elapsedTime < 500) { + Utils.sleep(50); + } else if (elapsedTime < 1000) { + Utils.sleep(100); + } else if (elapsedTime < 5000) { + Utils.sleep(250); + } else if (elapsedTime < 10000) { + Utils.sleep(500); + } else if (elapsedTime < 30000) { + Utils.sleep(1000); + } else { + Utils.sleep(3000); + } + } catch (InterruptedException e) { + throw new TimeoutException(message); + } + } +} diff --git a/src/main/java/org/metricshub/winrm/Utils.java b/src/main/java/org/metricshub/winrm/Utils.java index 0e3d31f..cf7e320 100644 --- a/src/main/java/org/metricshub/winrm/Utils.java +++ b/src/main/java/org/metricshub/winrm/Utils.java @@ -1,171 +1,171 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -public class Utils { - - public static final String EMPTY = ""; - public static final String NEW_LINE = "\n"; - - /** - * Check if the required argument is not null. - * - * @param argument - * @param name - * @throws IllegalArgumentException if the argument is null - */ - public static void checkNonNull(final T argument, final String name) { - if (argument == null) { - throw new IllegalArgumentException(name + " must not be null."); - } - } - - /** - * @param value The value to check - * @return whether the value is null, empty or contains only blank chars - */ - public static boolean isBlank(String value) { - return value == null || isEmpty(value); - } - - /** - * @param value The value to check - * @return whether the value is empty of non-blank chars - * @throws NullPointerException if value is null - */ - public static boolean isEmpty(String value) { - return value.trim().isEmpty(); - } - - /** - * @param value The value to check - * @return whether the value is not null, nor empty nor contains only blank chars - */ - public static boolean isNotBlank(final String value) { - return !isBlank(value); - } - - /** - * @return the name of the local computer (or "localhost" if it can't be determined) - */ - public static String getComputerName() { - final String computerName = System.getenv("COMPUTERNAME"); - if (computerName == null) { - return "localhost"; - } - return computerName; - } - - /** - * Wrapper for Thread.sleep(millis) - * - * @param millis Time to sleep (in milliseconds) - * @throws InterruptedException - */ - public static void sleep(final long millis) throws InterruptedException { - Thread.sleep(millis); - } - - /** - * Check if the required argument is not negative or zero. - * - * @param argument - * @param name - * @throws IllegalArgumentException if the argument is null - */ - public static void checkArgumentNotZeroOrNegative(final long argument, final String name) { - if (argument <= 0) { - throw new IllegalArgumentException(String.format("%s=%d must not be negative or zero.", name, argument)); - } - } - - /** - * Get the current time in Milliseconds. - * - * @return the current time in Milliseconds. - */ - public static long getCurrentTimeMillis() { - return System.currentTimeMillis(); - } - - /** - * Check if the required argument is not blank (null or empty). - * - * @param argument - * @param name - * @throws IllegalArgumentException if the argument is null - */ - public static void checkNonBlank(final String argument, final String name) { - if (isBlank(argument)) { - throw new IllegalArgumentException(name + " must not be null or empty."); - } - } - - /** - * Run the given {@link Callable} using the passed timeout in seconds. - * - * @param - * @param callable - * @param timeout - * @return {@link T} result returned by the callable - * @throws InterruptedException - * @throws ExecutionException - * @throws TimeoutException - */ - public static T execute(Callable callable, long timeout) - throws InterruptedException, ExecutionException, TimeoutException { - ExecutorService executorService = Executors.newSingleThreadExecutor(); - Future future = executorService.submit(callable); - - try { - return future.get(timeout, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw e; - } catch (TimeoutException e) { - future.cancel(true); - throw e; - } finally { - executorService.shutdownNow(); - } - } - - /** - * Check if the required field is not null. - * - * @param field - * @param name - * @throws IllegalStateException if the argument is null - */ - public static void checkNonNullField(final T field, final String name) { - if (field == null) { - throw new IllegalStateException(name + " must not be null."); - } - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class Utils { + + public static final String EMPTY = ""; + public static final String NEW_LINE = "\n"; + + /** + * Check if the required argument is not null. + * + * @param argument + * @param name + * @throws IllegalArgumentException if the argument is null + */ + public static void checkNonNull(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " must not be null."); + } + } + + /** + * @param value The value to check + * @return whether the value is null, empty or contains only blank chars + */ + public static boolean isBlank(String value) { + return value == null || isEmpty(value); + } + + /** + * @param value The value to check + * @return whether the value is empty of non-blank chars + * @throws NullPointerException if value is null + */ + public static boolean isEmpty(String value) { + return value.trim().isEmpty(); + } + + /** + * @param value The value to check + * @return whether the value is not null, nor empty nor contains only blank chars + */ + public static boolean isNotBlank(final String value) { + return !isBlank(value); + } + + /** + * @return the name of the local computer (or "localhost" if it can't be determined) + */ + public static String getComputerName() { + final String computerName = System.getenv("COMPUTERNAME"); + if (computerName == null) { + return "localhost"; + } + return computerName; + } + + /** + * Wrapper for Thread.sleep(millis) + * + * @param millis Time to sleep (in milliseconds) + * @throws InterruptedException + */ + public static void sleep(final long millis) throws InterruptedException { + Thread.sleep(millis); + } + + /** + * Check if the required argument is not negative or zero. + * + * @param argument + * @param name + * @throws IllegalArgumentException if the argument is null + */ + public static void checkArgumentNotZeroOrNegative(final long argument, final String name) { + if (argument <= 0) { + throw new IllegalArgumentException(String.format("%s=%d must not be negative or zero.", name, argument)); + } + } + + /** + * Get the current time in Milliseconds. + * + * @return the current time in Milliseconds. + */ + public static long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + + /** + * Check if the required argument is not blank (null or empty). + * + * @param argument + * @param name + * @throws IllegalArgumentException if the argument is null + */ + public static void checkNonBlank(final String argument, final String name) { + if (isBlank(argument)) { + throw new IllegalArgumentException(name + " must not be null or empty."); + } + } + + /** + * Run the given {@link Callable} using the passed timeout in seconds. + * + * @param + * @param callable + * @param timeout + * @return {@link T} result returned by the callable + * @throws InterruptedException + * @throws ExecutionException + * @throws TimeoutException + */ + public static T execute(Callable callable, long timeout) + throws InterruptedException, ExecutionException, TimeoutException { + ExecutorService executorService = Executors.newSingleThreadExecutor(); + Future future = executorService.submit(callable); + + try { + return future.get(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw e; + } catch (TimeoutException e) { + future.cancel(true); + throw e; + } finally { + executorService.shutdownNow(); + } + } + + /** + * Check if the required field is not null. + * + * @param field + * @param name + * @throws IllegalStateException if the argument is null + */ + public static void checkNonNullField(final T field, final String name) { + if (field == null) { + throw new IllegalStateException(name + " must not be null."); + } + } +} diff --git a/src/main/java/org/metricshub/winrm/WinRMHttpProtocolEnum.java b/src/main/java/org/metricshub/winrm/WinRMHttpProtocolEnum.java index ad517d2..3aacc55 100644 --- a/src/main/java/org/metricshub/winrm/WinRMHttpProtocolEnum.java +++ b/src/main/java/org/metricshub/winrm/WinRMHttpProtocolEnum.java @@ -1,29 +1,29 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -/** - * Enumeration for protocol to use. - */ -public enum WinRMHttpProtocolEnum { - HTTP, - HTTPS -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +/** + * Enumeration for protocol to use. + */ +public enum WinRMHttpProtocolEnum { + HTTP, + HTTPS +} diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java b/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java index 4fe76ac..c89d7aa 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteCommandResult.java @@ -1,99 +1,99 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -public class WindowsRemoteCommandResult { - - private final String stdout; - private final String stderr; - private final float executionTime; - private final int statusCode; - - /** - * Command Result constructor - * - * @param stdout The stdout of the command - * @param stderr The stderr of the command - * @param executionTime The execution time of the command in milliseconds - * @param statusCode The command return status code - */ - public WindowsRemoteCommandResult( - final String stdout, - final String stderr, - final float executionTime, - final int statusCode - ) { - this.stdout = stdout; - this.stderr = stderr; - this.executionTime = executionTime; - this.statusCode = statusCode; - } - - /** - * Get the stdout of the command. - * - * @return - */ - public String getStdout() { - return stdout; - } - - /** - * Get the stderr of the command. - * - * @return - */ - public String getStderr() { - return stderr; - } - - /** - * Get the execution time of the command in seconds. - * - * @return - */ - public float getExecutionTime() { - return executionTime; - } - - /** - * Get the return status code of the command - * - * @return - */ - public int getStatusCode() { - return statusCode; - } - - @Override - public String toString() { - return new StringBuilder() - .append("WindowsRemoteCommandResult:\nstdout:\n") - .append(stdout) - .append("\nstderr:\n") - .append(stderr) - .append("\nexecutionTime = ") - .append(executionTime) - .append("\nstatusCode = ") - .append(statusCode) - .toString(); - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +public class WindowsRemoteCommandResult { + + private final String stdout; + private final String stderr; + private final float executionTime; + private final int statusCode; + + /** + * Command Result constructor + * + * @param stdout The stdout of the command + * @param stderr The stderr of the command + * @param executionTime The execution time of the command in milliseconds + * @param statusCode The command return status code + */ + public WindowsRemoteCommandResult( + final String stdout, + final String stderr, + final float executionTime, + final int statusCode + ) { + this.stdout = stdout; + this.stderr = stderr; + this.executionTime = executionTime; + this.statusCode = statusCode; + } + + /** + * Get the stdout of the command. + * + * @return + */ + public String getStdout() { + return stdout; + } + + /** + * Get the stderr of the command. + * + * @return + */ + public String getStderr() { + return stderr; + } + + /** + * Get the execution time of the command in seconds. + * + * @return + */ + public float getExecutionTime() { + return executionTime; + } + + /** + * Get the return status code of the command + * + * @return + */ + public int getStatusCode() { + return statusCode; + } + + @Override + public String toString() { + return new StringBuilder() + .append("WindowsRemoteCommandResult:\nstdout:\n") + .append(stdout) + .append("\nstderr:\n") + .append(stderr) + .append("\nexecutionTime = ") + .append(executionTime) + .append("\nstatusCode = ") + .append(statusCode) + .toString(); + } +} diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java b/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java index b815a56..671e343 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteExecutor.java @@ -1,93 +1,93 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeoutException; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; - -public interface WindowsRemoteExecutor extends AutoCloseable { - /** - *

    - * Execute a WQL query and process its result. - *

    - * - * @param wqlQuery the WQL query (required) - * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) - * @return a list of result rows. A result row is a Map(LinkedHashMap to preserve the query order) of - * properties/values. - * @throws TimeoutException to notify userName of timeout. - * @throws WqlQuerySyntaxException if WQL query syntax is invalid - * @throws WindowsRemoteException For any problem encountered - */ - public List> executeWql(final String wqlQuery, final long timeout) - throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException; - - /** - * Execute the command on the remote - * - * @param command The command to execute - * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null) - * @param charset The charset - * @param timeout Timeout in milliseconds - * @return The command result - * @throws WindowsRemoteException For any problem encountered - * @throws TimeoutException To notify userName of timeout. - */ - public WindowsRemoteCommandResult executeCommand( - final String command, - final String workingDirectory, - final Charset charset, - final long timeout - ) throws WindowsRemoteException, TimeoutException; - - /** - * Get the hostname. - * - * @return - */ - public String getHostname(); - - /** - * Get the username. - * - * @return - */ - public String getUsername(); - - /** - * Get the password. - * - * @return - */ - public char[] getPassword(); - - /** - * Close the executor and release its resources. Narrows {@link AutoCloseable#close()} so it does - * not declare a checked exception, letting callers use try-with-resources without catching - * {@link Exception}. - */ - @Override - public void close(); -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright (C) 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; + +public interface WindowsRemoteExecutor extends AutoCloseable { + /** + *

    + * Execute a WQL query and process its result. + *

    + * + * @param wqlQuery the WQL query (required) + * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) + * @return a list of result rows. A result row is a Map(LinkedHashMap to preserve the query order) of + * properties/values. + * @throws TimeoutException to notify userName of timeout. + * @throws WqlQuerySyntaxException if WQL query syntax is invalid + * @throws WindowsRemoteException For any problem encountered + */ + public List> executeWql(final String wqlQuery, final long timeout) + throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException; + + /** + * Execute the command on the remote + * + * @param command The command to execute + * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null) + * @param charset The charset + * @param timeout Timeout in milliseconds + * @return The command result + * @throws WindowsRemoteException For any problem encountered + * @throws TimeoutException To notify userName of timeout. + */ + public WindowsRemoteCommandResult executeCommand( + final String command, + final String workingDirectory, + final Charset charset, + final long timeout + ) throws WindowsRemoteException, TimeoutException; + + /** + * Get the hostname. + * + * @return + */ + public String getHostname(); + + /** + * Get the username. + * + * @return + */ + public String getUsername(); + + /** + * Get the password. + * + * @return + */ + public char[] getPassword(); + + /** + * Close the executor and release its resources. Narrows {@link AutoCloseable#close()} so it does + * not declare a checked exception, letting callers use try-with-resources without catching + * {@link Exception}. + */ + @Override + public void close(); +} diff --git a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java index 563cd80..9420f79 100644 --- a/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java +++ b/src/main/java/org/metricshub/winrm/WindowsRemoteProcessUtils.java @@ -1,234 +1,234 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.TimeoutException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; - -public class WindowsRemoteProcessUtils { - - private WindowsRemoteProcessUtils() {} - - private static final String DEFAULT_CODESET = "1252"; - private static final Charset DEFAULT_CHARSET = Charset.forName("windows-1252"); - - /** - * Windows CodeSet to java.nio.charset Charset Code map. - * - * @see Windows code page - * @see - * Supported Encodings - */ - private static final Map CODESET_MAP; - - static { - final Map map = new HashMap<>(); - map.put("1250", Charset.forName("windows-1250")); - map.put("1251", Charset.forName("windows-1251")); - map.put("1252", DEFAULT_CHARSET); - map.put("1253", Charset.forName("windows-1253")); - map.put("1254", Charset.forName("windows-1254")); - map.put("1255", Charset.forName("windows-1255")); - map.put("1256", Charset.forName("windows-1256")); - map.put("1257", Charset.forName("windows-1257")); - map.put("1258", Charset.forName("windows-1258")); - map.put("874", Charset.forName("x-windows-874")); - map.put("932", Charset.forName("Shift_JIS")); - map.put("936", Charset.forName("GBK")); - map.put("949", Charset.forName("EUC-KR")); - map.put("950", Charset.forName("Big5")); - map.put("951", Charset.forName("Big5-HKSCS")); - map.put("28591", StandardCharsets.ISO_8859_1); - map.put("20127", StandardCharsets.US_ASCII); - map.put("65001", StandardCharsets.UTF_8); - map.put("1200", StandardCharsets.UTF_16LE); - map.put("1201", StandardCharsets.UTF_16BE); - - CODESET_MAP = Collections.unmodifiableMap(map); - } - - /** - * Get the CharSet from the Win32_OperatingSystem CodeSet. (if not found by default Latin-1 windows-1252) - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance - * @param timeout Timeout in milliseconds. - * @return the encoding charset from Win32_OperatingSystem - * @throws TimeoutException To notify userName of timeout - * @throws WqlQuerySyntaxException On WQL syntax errors - * @throws WindowsRemoteException For any problem encountered on remote - * @see - * Win32_OperatingSystem class - */ - public static Charset getWindowsEncodingCharset( - final WindowsRemoteExecutor windowsRemoteExecutor, - final long timeout - ) throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException { - if (windowsRemoteExecutor == null || timeout < 1) { - return DEFAULT_CHARSET; - } - - final List> result = windowsRemoteExecutor.executeWql( - "SELECT CodeSet FROM Win32_OperatingSystem", - timeout - ); - - final String codeSet = result - .stream() - .map(row -> (String) row.get("CodeSet")) - .filter(Objects::nonNull) - .findFirst() - .orElse(DEFAULT_CODESET); - - return CODESET_MAP.getOrDefault(codeSet, DEFAULT_CHARSET); - } - - /** - * Builds a new output file name, with 99.9999999% chances of being unique - * on the remote system - * - * @return file name - */ - public static String buildNewOutputFileName() { - return String.format( - "SEN_%s_%d_%d", - Utils.getComputerName(), - Utils.getCurrentTimeMillis(), - (long) (Math.random() * 1000000) - ); - } - - /** - * Copy the local files to the share and update the command with their path as seen in the remote system. - * - * @param command The command (mandatory) - * @param localFiles The local files to copy list - * @param uncSharePath The UNC path of the share - * @param remotePath The remote path - * @return The updated command. - * @throws IOException If an I/O error occurs. - */ - public static String copyLocalFilesToShare( - final String command, - final List localFiles, - final String uncSharePath, - final String remotePath - ) throws IOException { - Utils.checkNonNull(command, "command"); - - if (localFiles == null || localFiles.isEmpty()) { - return command; - } - - Utils.checkNonNull(uncSharePath, "uncSharePath"); - Utils.checkNonNull(remotePath, "remotePath"); - - try { - return localFiles - .stream() - .reduce( - command, - (cmd, localFile) -> { - try { - final Path localFilePath = Paths.get(localFile); - final Path remoteFilePath = copyToShare(localFilePath, uncSharePath, remotePath); - - return caseInsensitiveReplace(cmd, localFile, remoteFilePath.toString()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - ); - } catch (final Exception e) { - if (e.getCause() instanceof IOException) { - throw (IOException) e.getCause(); - } - throw e; - } - } - - /** - * Copy a file to the share. - * If the same file is already present on the share, the copy is not performed. - * The "last-modified" time is used to determine whether the file needs to be - * copied or not. - * - * @param localFilePath The path to the file to copy - * @param uncSharePath The UNC path of the share - * @param remotePath The remote path - * @return the path to the copied file, as seen in the remote system - * @throws IOException If an I/O error occurs. - */ - static Path copyToShare(final Path localFilePath, final String uncSharePath, final String remotePath) - throws IOException { - final Path targetUncPath = Paths.get(uncSharePath, localFilePath.getFileName().toString()); - final Path targetRemotePath = Paths.get(remotePath, localFilePath.getFileName().toString()); - - if (Files.exists(targetUncPath)) { - final FileTime sourceFileTime = Files.getLastModifiedTime(localFilePath); - final FileTime targetFileTime = Files.getLastModifiedTime(targetUncPath); - if (sourceFileTime.compareTo(targetFileTime) <= 0) { - // File is already present on the target, simply skip the copy operation - return targetRemotePath; - } - } - - // Copy - Files.copy(localFilePath, targetUncPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); - - // Return the path to the copied file, as seen in the remote system - return targetRemotePath; - } - - /** - * Perform a case-insensitive replace of all occurrences of target string with - * specified replacement - * Similar to String.replace(target, replacement) - * - * @param string The string to parse - * @param target The string to replace - * @param replacement The replacement string - * @return updated string - */ - static String caseInsensitiveReplace(final String string, final String target, final String replacement) { - return string == null || target == null - ? string - : Pattern - .compile(target, Pattern.LITERAL | Pattern.CASE_INSENSITIVE) - .matcher(string) - .replaceAll(Matcher.quoteReplacement(replacement == null ? Utils.EMPTY : replacement)); - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; + +public class WindowsRemoteProcessUtils { + + private WindowsRemoteProcessUtils() {} + + private static final String DEFAULT_CODESET = "1252"; + private static final Charset DEFAULT_CHARSET = Charset.forName("windows-1252"); + + /** + * Windows CodeSet to java.nio.charset Charset Code map. + * + * @see Windows code page + * @see + * Supported Encodings + */ + private static final Map CODESET_MAP; + + static { + final Map map = new HashMap<>(); + map.put("1250", Charset.forName("windows-1250")); + map.put("1251", Charset.forName("windows-1251")); + map.put("1252", DEFAULT_CHARSET); + map.put("1253", Charset.forName("windows-1253")); + map.put("1254", Charset.forName("windows-1254")); + map.put("1255", Charset.forName("windows-1255")); + map.put("1256", Charset.forName("windows-1256")); + map.put("1257", Charset.forName("windows-1257")); + map.put("1258", Charset.forName("windows-1258")); + map.put("874", Charset.forName("x-windows-874")); + map.put("932", Charset.forName("Shift_JIS")); + map.put("936", Charset.forName("GBK")); + map.put("949", Charset.forName("EUC-KR")); + map.put("950", Charset.forName("Big5")); + map.put("951", Charset.forName("Big5-HKSCS")); + map.put("28591", StandardCharsets.ISO_8859_1); + map.put("20127", StandardCharsets.US_ASCII); + map.put("65001", StandardCharsets.UTF_8); + map.put("1200", StandardCharsets.UTF_16LE); + map.put("1201", StandardCharsets.UTF_16BE); + + CODESET_MAP = Collections.unmodifiableMap(map); + } + + /** + * Get the CharSet from the Win32_OperatingSystem CodeSet. (if not found by default Latin-1 windows-1252) + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance + * @param timeout Timeout in milliseconds. + * @return the encoding charset from Win32_OperatingSystem + * @throws TimeoutException To notify userName of timeout + * @throws WqlQuerySyntaxException On WQL syntax errors + * @throws WindowsRemoteException For any problem encountered on remote + * @see + * Win32_OperatingSystem class + */ + public static Charset getWindowsEncodingCharset( + final WindowsRemoteExecutor windowsRemoteExecutor, + final long timeout + ) throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException { + if (windowsRemoteExecutor == null || timeout < 1) { + return DEFAULT_CHARSET; + } + + final List> result = windowsRemoteExecutor.executeWql( + "SELECT CodeSet FROM Win32_OperatingSystem", + timeout + ); + + final String codeSet = result + .stream() + .map(row -> (String) row.get("CodeSet")) + .filter(Objects::nonNull) + .findFirst() + .orElse(DEFAULT_CODESET); + + return CODESET_MAP.getOrDefault(codeSet, DEFAULT_CHARSET); + } + + /** + * Builds a new output file name, with 99.9999999% chances of being unique + * on the remote system + * + * @return file name + */ + public static String buildNewOutputFileName() { + return String.format( + "SEN_%s_%d_%d", + Utils.getComputerName(), + Utils.getCurrentTimeMillis(), + (long) (Math.random() * 1000000) + ); + } + + /** + * Copy the local files to the share and update the command with their path as seen in the remote system. + * + * @param command The command (mandatory) + * @param localFiles The local files to copy list + * @param uncSharePath The UNC path of the share + * @param remotePath The remote path + * @return The updated command. + * @throws IOException If an I/O error occurs. + */ + public static String copyLocalFilesToShare( + final String command, + final List localFiles, + final String uncSharePath, + final String remotePath + ) throws IOException { + Utils.checkNonNull(command, "command"); + + if (localFiles == null || localFiles.isEmpty()) { + return command; + } + + Utils.checkNonNull(uncSharePath, "uncSharePath"); + Utils.checkNonNull(remotePath, "remotePath"); + + try { + return localFiles + .stream() + .reduce( + command, + (cmd, localFile) -> { + try { + final Path localFilePath = Paths.get(localFile); + final Path remoteFilePath = copyToShare(localFilePath, uncSharePath, remotePath); + + return caseInsensitiveReplace(cmd, localFile, remoteFilePath.toString()); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + ); + } catch (final Exception e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw e; + } + } + + /** + * Copy a file to the share. + * If the same file is already present on the share, the copy is not performed. + * The "last-modified" time is used to determine whether the file needs to be + * copied or not. + * + * @param localFilePath The path to the file to copy + * @param uncSharePath The UNC path of the share + * @param remotePath The remote path + * @return the path to the copied file, as seen in the remote system + * @throws IOException If an I/O error occurs. + */ + static Path copyToShare(final Path localFilePath, final String uncSharePath, final String remotePath) + throws IOException { + final Path targetUncPath = Paths.get(uncSharePath, localFilePath.getFileName().toString()); + final Path targetRemotePath = Paths.get(remotePath, localFilePath.getFileName().toString()); + + if (Files.exists(targetUncPath)) { + final FileTime sourceFileTime = Files.getLastModifiedTime(localFilePath); + final FileTime targetFileTime = Files.getLastModifiedTime(targetUncPath); + if (sourceFileTime.compareTo(targetFileTime) <= 0) { + // File is already present on the target, simply skip the copy operation + return targetRemotePath; + } + } + + // Copy + Files.copy(localFilePath, targetUncPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + + // Return the path to the copied file, as seen in the remote system + return targetRemotePath; + } + + /** + * Perform a case-insensitive replace of all occurrences of target string with + * specified replacement + * Similar to String.replace(target, replacement) + * + * @param string The string to parse + * @param target The string to replace + * @param replacement The replacement string + * @return updated string + */ + static String caseInsensitiveReplace(final String string, final String target, final String replacement) { + return string == null || target == null + ? string + : Pattern + .compile(target, Pattern.LITERAL | Pattern.CASE_INSENSITIVE) + .matcher(string) + .replaceAll(Matcher.quoteReplacement(replacement == null ? Utils.EMPTY : replacement)); + } +} diff --git a/src/main/java/org/metricshub/winrm/WindowsTempShare.java b/src/main/java/org/metricshub/winrm/WindowsTempShare.java index e734599..5bb74d4 100644 --- a/src/main/java/org/metricshub/winrm/WindowsTempShare.java +++ b/src/main/java/org/metricshub/winrm/WindowsTempShare.java @@ -1,371 +1,371 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeoutException; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; - -public class WindowsTempShare { - - /** The share name */ - private final String shareName; - - /** The UNC path of the share. */ - private final String uncSharePath; - - /** The remote path. */ - private final String remotePath; - - /** The WindowsRemoteExecutor instance */ - private final WindowsRemoteExecutor windowsRemoteExecutor; - - /** - * Constructor of WindowsTempShare - * - * @param windowsRemoteExecutor the WindowsRemoteExecutor instance - * connected to the remote host (mandatory) - * @param shareNameOrUnc The name of the share, or its full UNC path (mandatory) - * @param remotePath The path on the remote system of the directory being shared - */ - public WindowsTempShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final String shareNameOrUnc, - final String remotePath - ) { - Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); - Utils.checkNonNull(shareNameOrUnc, "shareNameOrUnc"); - - if (shareNameOrUnc.startsWith("\\\\")) { - this.uncSharePath = shareNameOrUnc; - final String[] uncElements = shareNameOrUnc.split("\\\\"); - this.shareName = uncElements[3]; - } else { - this.uncSharePath = buildUncPath(windowsRemoteExecutor.getHostname(), shareNameOrUnc); - this.shareName = shareNameOrUnc; - } - - this.remotePath = remotePath; - this.windowsRemoteExecutor = windowsRemoteExecutor; - } - - /** - * Get the existing share on the host or create it if absent. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) - * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) - * @param shareRemoteDirectory ShareRemoteDirectoryConsumer function (mandatory) - * @return The remote path. - * @throws TimeoutException To notify userName of timeout. - * @throws WindowsRemoteException For any problem encountered. - */ - public static WindowsTempShare getOrCreateShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final long timeout, - final ShareRemoteDirectoryConsumer shareRemoteDirectory - ) throws TimeoutException, WindowsRemoteException { - Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); - Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); - Utils.checkNonNull(shareRemoteDirectory, "shareRemoteDirectory"); - - final long start = Utils.getCurrentTimeMillis(); - - // Are we targeting a Windows cluster? - // (in which case, things are a bit different) - - // Try to get a clustered share - final Optional clusterShare = getClusterShare( - windowsRemoteExecutor, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to check for cluster share"), - start - ); - - if (clusterShare.isPresent()) { - return clusterShare.get(); - } - - // Normal case (non-cluster) - final String shareName = buildShareName(); - final Optional share = getShare( - windowsRemoteExecutor, - shareName, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to get a normal temporary share") - ); - if (share.isPresent()) { - return share.get(); - } - - return createTempShare( - windowsRemoteExecutor, - shareName, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary share"), - shareRemoteDirectory - ); - } - - /** - * Get the Windows directory. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) - * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) - * @return The Windows directory. - * @throws WindowsRemoteException For any problem encountered. - * @throws TimeoutException To notify userName of timeout. - * @see - * Win32_OperatingSystem class - */ - public static String getWindowsDirectory(final WindowsRemoteExecutor windowsRemoteExecutor, final long timeout) - throws WindowsRemoteException, TimeoutException { - Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); - Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); - - try { - // Extract the WindowsDirectory property from the first instance and return it (or throw an exception) - return windowsRemoteExecutor - .executeWql("SELECT WindowsDirectory FROM Win32_OperatingSystem", timeout) - .stream() - .limit(1) - .map(row -> (String) row.get("WindowsDirectory")) - .filter(Objects::nonNull) - .findFirst() - .orElseThrow( - () -> new WindowsRemoteException( - String.format("Couldn't identify the Windows root directory on %s.", windowsRemoteExecutor.getHostname()) - ) - ); - } catch (final WqlQuerySyntaxException e) { - throw new WindowsRemoteException(e); // Impossible - } - } - - /** - * Create the remote directory. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) - * @param remotePath The remote path to create - * @param timeout Timeout in milliseconds - * @param start start time in milliseconds. - * @throws WindowsRemoteException For any problem encountered. - * @throws TimeoutException To notify userName of timeout. - */ - public static void createRemoteDirectory( - final WindowsRemoteExecutor windowsRemoteExecutor, - final String remotePath, - final long timeout, - final long start - ) throws WindowsRemoteException, TimeoutException { - Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); - - windowsRemoteExecutor.executeCommand(buildCreateRemoteDirectoryCommand(remotePath), null, null, timeout); - } - - /** - * Build a UNC path from hostname and share name. - * Note: This method ensures compatibility with IPv6 hosts - * - * @param hostname Host to connect to. (mandatory) - * @param share The share - * @return The UNC path to the share (\\unc(host)\share) - */ - static String buildUncPath(final String hostname, final String share) { - Utils.checkNonNull(hostname, "hostname"); - - return hostname.contains(":") - ? String.format("\\\\%s.ipv6-literal.net\\%s", hostname.replace(":", "-").replace("%", "s"), share) - : String.format("\\\\%s\\%s", hostname, share); - } - - /** - * Create the Windows command for a directory creation. - * - * @param remotePath The remote path to create. (mandatory) - * @return The command to execute. - */ - static String buildCreateRemoteDirectoryCommand(final String remotePath) { - Utils.checkNonBlank(remotePath, "remotePath"); - - return String.format("CMD.EXE /C IF NOT EXIST \"%s\" MKDIR %s", remotePath, remotePath); - } - - /** - * @param path Root path of the temporary directory that will be used in a cluster. (mandatory) - * @return Path to the temporary directory - */ - static String buildPathOnCluster(final String path) { - Utils.checkNonNull(path, "path"); - - return String.format("%s\\Temp\\SEN_TempFor_%s", path, Utils.getComputerName()); - } - - /** - * Build the remote temp path name with the folder name. - * - * @param folder The folder name. (mandatory) - * @param shareName The Share Name. (mandatory) - * @return The remote directory path. (folder\Temp\shareName) - */ - static String buildRemotePath(final String folder, final String shareName) { - Utils.checkNonNull(folder, "folder"); - Utils.checkNonBlank(shareName, "shareName"); - - return String.format("%s\\Temp\\%s", folder, shareName); - } - - /** - * Build the Share name with the computer name. - * - * @return The share name. - */ - static String buildShareName() { - return String.format("SEN_ShareFor_%s$", Utils.getComputerName()); - } - - /** - * Retrieve an "Admin Share" (like D$, E$, etc.) that is exposed by a cluster. - * If the targeted system is not a cluster, returns an empty optional. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. - * @param timeout Timeout in milliseconds. - * @param start start time in milliseconds. - * @return An optional Map with 2 entries: "Name" and "Path" - * @throws TimeoutException To notify userName of timeout. - * @throws WindowsRemoteException For any problem encountered. - */ - static Optional getClusterShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final long timeout, - final long start - ) throws TimeoutException, WindowsRemoteException { - try { - final Optional clusterShare = windowsRemoteExecutor - .executeWql( - "SELECT Name,Path FROM Win32_ClusterShare WHERE " + - "ServerName <> '*' AND (Type = 2147483648 OR Type = 3221225472) AND Name LIKE '%\\\\_$'", - timeout - ) - .stream() - .limit(1) - .map(row -> // We return a TempShare instance pointing to a subdirectory in this share - new WindowsTempShare( - windowsRemoteExecutor, - buildPathOnCluster((String) row.get("Name")), - buildPathOnCluster((String) row.get("Path")) - ) - ) - .findFirst(); - - if (clusterShare.isPresent()) { - // We create the subdirectory (if necessary) - createRemoteDirectory(windowsRemoteExecutor, clusterShare.get().getRemotePath(), timeout, start); - } - - return clusterShare; - } catch (final WqlQuerySyntaxException e) { - throw new WindowsRemoteException(e); // Impossible - } - } - - /** - * Retrieve the specified share. - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance - * @param shareName The share name - * @param timeout Timeout in milliseconds - * @return An optional TempShare instance - * @throws TimeoutException To notify userName of timeout - * @throws WindowsRemoteException For any problem encountered - */ - static Optional getShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final String shareName, - final long timeout - ) throws TimeoutException, WindowsRemoteException { - try { - return windowsRemoteExecutor - .executeWql(String.format("SELECT Name,Path FROM Win32_Share WHERE Name = '%s'", shareName), timeout) - .stream() - .limit(1) - .map(row -> new WindowsTempShare(windowsRemoteExecutor, (String) row.get("Name"), (String) row.get("Path"))) - .findFirst(); - } catch (final WqlQuerySyntaxException e) { - throw new WindowsRemoteException(e); // Impossible - } - } - - /** - * Create a shared temporary folder on the remote - * - * @param windowsRemoteExecutor WindowsRemoteExecutor instance. - * @param shareName The Share Name. - * @param timeout Timeout in milliseconds. - * @param shareRemoteDirectory shareRemoteDirectory function - * @return A TempShare instance - * @throws WindowsRemoteException For any problem encountered. - * @throws TimeoutException To notify userName of timeout. - */ - static WindowsTempShare createTempShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final String shareName, - final long timeout, - final ShareRemoteDirectoryConsumer shareRemoteDirectory - ) throws WindowsRemoteException, TimeoutException { - final long start = Utils.getCurrentTimeMillis(); - - // Find where Windows is installed on the remote system. We will create the share under %WINDIR%\Temp. - final String folder = getWindowsDirectory(windowsRemoteExecutor, timeout); - - // Create the folder on the remote system - final String remotePath = buildRemotePath(folder, shareName); - createRemoteDirectory( - windowsRemoteExecutor, - remotePath, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary directory"), - start - ); - - // Create the share - shareRemoteDirectory.apply(windowsRemoteExecutor, remotePath, shareName, timeout); - - return new WindowsTempShare(windowsRemoteExecutor, shareName, remotePath); - } - - /** Get the share name */ - public String getShareName() { - return shareName; - } - - /** Get the UNC path of the share */ - public String getUncSharePath() { - return uncSharePath; - } - - /** Get the remote path */ - public String getRemotePath() { - return remotePath; - } - - /** Get the WindowsRemoteExecutor instance */ - public WindowsRemoteExecutor getWindowsRemoteExecutor() { - return windowsRemoteExecutor; - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeoutException; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; + +public class WindowsTempShare { + + /** The share name */ + private final String shareName; + + /** The UNC path of the share. */ + private final String uncSharePath; + + /** The remote path. */ + private final String remotePath; + + /** The WindowsRemoteExecutor instance */ + private final WindowsRemoteExecutor windowsRemoteExecutor; + + /** + * Constructor of WindowsTempShare + * + * @param windowsRemoteExecutor the WindowsRemoteExecutor instance + * connected to the remote host (mandatory) + * @param shareNameOrUnc The name of the share, or its full UNC path (mandatory) + * @param remotePath The path on the remote system of the directory being shared + */ + public WindowsTempShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final String shareNameOrUnc, + final String remotePath + ) { + Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); + Utils.checkNonNull(shareNameOrUnc, "shareNameOrUnc"); + + if (shareNameOrUnc.startsWith("\\\\")) { + this.uncSharePath = shareNameOrUnc; + final String[] uncElements = shareNameOrUnc.split("\\\\"); + this.shareName = uncElements[3]; + } else { + this.uncSharePath = buildUncPath(windowsRemoteExecutor.getHostname(), shareNameOrUnc); + this.shareName = shareNameOrUnc; + } + + this.remotePath = remotePath; + this.windowsRemoteExecutor = windowsRemoteExecutor; + } + + /** + * Get the existing share on the host or create it if absent. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) + * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) + * @param shareRemoteDirectory ShareRemoteDirectoryConsumer function (mandatory) + * @return The remote path. + * @throws TimeoutException To notify userName of timeout. + * @throws WindowsRemoteException For any problem encountered. + */ + public static WindowsTempShare getOrCreateShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final long timeout, + final ShareRemoteDirectoryConsumer shareRemoteDirectory + ) throws TimeoutException, WindowsRemoteException { + Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); + Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); + Utils.checkNonNull(shareRemoteDirectory, "shareRemoteDirectory"); + + final long start = Utils.getCurrentTimeMillis(); + + // Are we targeting a Windows cluster? + // (in which case, things are a bit different) + + // Try to get a clustered share + final Optional clusterShare = getClusterShare( + windowsRemoteExecutor, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to check for cluster share"), + start + ); + + if (clusterShare.isPresent()) { + return clusterShare.get(); + } + + // Normal case (non-cluster) + final String shareName = buildShareName(); + final Optional share = getShare( + windowsRemoteExecutor, + shareName, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to get a normal temporary share") + ); + if (share.isPresent()) { + return share.get(); + } + + return createTempShare( + windowsRemoteExecutor, + shareName, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary share"), + shareRemoteDirectory + ); + } + + /** + * Get the Windows directory. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) + * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero) + * @return The Windows directory. + * @throws WindowsRemoteException For any problem encountered. + * @throws TimeoutException To notify userName of timeout. + * @see + * Win32_OperatingSystem class + */ + public static String getWindowsDirectory(final WindowsRemoteExecutor windowsRemoteExecutor, final long timeout) + throws WindowsRemoteException, TimeoutException { + Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); + Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); + + try { + // Extract the WindowsDirectory property from the first instance and return it (or throw an exception) + return windowsRemoteExecutor + .executeWql("SELECT WindowsDirectory FROM Win32_OperatingSystem", timeout) + .stream() + .limit(1) + .map(row -> (String) row.get("WindowsDirectory")) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow( + () -> new WindowsRemoteException( + String.format("Couldn't identify the Windows root directory on %s.", windowsRemoteExecutor.getHostname()) + ) + ); + } catch (final WqlQuerySyntaxException e) { + throw new WindowsRemoteException(e); // Impossible + } + } + + /** + * Create the remote directory. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory) + * @param remotePath The remote path to create + * @param timeout Timeout in milliseconds + * @param start start time in milliseconds. + * @throws WindowsRemoteException For any problem encountered. + * @throws TimeoutException To notify userName of timeout. + */ + public static void createRemoteDirectory( + final WindowsRemoteExecutor windowsRemoteExecutor, + final String remotePath, + final long timeout, + final long start + ) throws WindowsRemoteException, TimeoutException { + Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor"); + + windowsRemoteExecutor.executeCommand(buildCreateRemoteDirectoryCommand(remotePath), null, null, timeout); + } + + /** + * Build a UNC path from hostname and share name. + * Note: This method ensures compatibility with IPv6 hosts + * + * @param hostname Host to connect to. (mandatory) + * @param share The share + * @return The UNC path to the share (\\unc(host)\share) + */ + static String buildUncPath(final String hostname, final String share) { + Utils.checkNonNull(hostname, "hostname"); + + return hostname.contains(":") + ? String.format("\\\\%s.ipv6-literal.net\\%s", hostname.replace(":", "-").replace("%", "s"), share) + : String.format("\\\\%s\\%s", hostname, share); + } + + /** + * Create the Windows command for a directory creation. + * + * @param remotePath The remote path to create. (mandatory) + * @return The command to execute. + */ + static String buildCreateRemoteDirectoryCommand(final String remotePath) { + Utils.checkNonBlank(remotePath, "remotePath"); + + return String.format("CMD.EXE /C IF NOT EXIST \"%s\" MKDIR %s", remotePath, remotePath); + } + + /** + * @param path Root path of the temporary directory that will be used in a cluster. (mandatory) + * @return Path to the temporary directory + */ + static String buildPathOnCluster(final String path) { + Utils.checkNonNull(path, "path"); + + return String.format("%s\\Temp\\SEN_TempFor_%s", path, Utils.getComputerName()); + } + + /** + * Build the remote temp path name with the folder name. + * + * @param folder The folder name. (mandatory) + * @param shareName The Share Name. (mandatory) + * @return The remote directory path. (folder\Temp\shareName) + */ + static String buildRemotePath(final String folder, final String shareName) { + Utils.checkNonNull(folder, "folder"); + Utils.checkNonBlank(shareName, "shareName"); + + return String.format("%s\\Temp\\%s", folder, shareName); + } + + /** + * Build the Share name with the computer name. + * + * @return The share name. + */ + static String buildShareName() { + return String.format("SEN_ShareFor_%s$", Utils.getComputerName()); + } + + /** + * Retrieve an "Admin Share" (like D$, E$, etc.) that is exposed by a cluster. + * If the targeted system is not a cluster, returns an empty optional. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. + * @param timeout Timeout in milliseconds. + * @param start start time in milliseconds. + * @return An optional Map with 2 entries: "Name" and "Path" + * @throws TimeoutException To notify userName of timeout. + * @throws WindowsRemoteException For any problem encountered. + */ + static Optional getClusterShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final long timeout, + final long start + ) throws TimeoutException, WindowsRemoteException { + try { + final Optional clusterShare = windowsRemoteExecutor + .executeWql( + "SELECT Name,Path FROM Win32_ClusterShare WHERE " + + "ServerName <> '*' AND (Type = 2147483648 OR Type = 3221225472) AND Name LIKE '%\\\\_$'", + timeout + ) + .stream() + .limit(1) + .map(row -> // We return a TempShare instance pointing to a subdirectory in this share + new WindowsTempShare( + windowsRemoteExecutor, + buildPathOnCluster((String) row.get("Name")), + buildPathOnCluster((String) row.get("Path")) + ) + ) + .findFirst(); + + if (clusterShare.isPresent()) { + // We create the subdirectory (if necessary) + createRemoteDirectory(windowsRemoteExecutor, clusterShare.get().getRemotePath(), timeout, start); + } + + return clusterShare; + } catch (final WqlQuerySyntaxException e) { + throw new WindowsRemoteException(e); // Impossible + } + } + + /** + * Retrieve the specified share. + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance + * @param shareName The share name + * @param timeout Timeout in milliseconds + * @return An optional TempShare instance + * @throws TimeoutException To notify userName of timeout + * @throws WindowsRemoteException For any problem encountered + */ + static Optional getShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final String shareName, + final long timeout + ) throws TimeoutException, WindowsRemoteException { + try { + return windowsRemoteExecutor + .executeWql(String.format("SELECT Name,Path FROM Win32_Share WHERE Name = '%s'", shareName), timeout) + .stream() + .limit(1) + .map(row -> new WindowsTempShare(windowsRemoteExecutor, (String) row.get("Name"), (String) row.get("Path"))) + .findFirst(); + } catch (final WqlQuerySyntaxException e) { + throw new WindowsRemoteException(e); // Impossible + } + } + + /** + * Create a shared temporary folder on the remote + * + * @param windowsRemoteExecutor WindowsRemoteExecutor instance. + * @param shareName The Share Name. + * @param timeout Timeout in milliseconds. + * @param shareRemoteDirectory shareRemoteDirectory function + * @return A TempShare instance + * @throws WindowsRemoteException For any problem encountered. + * @throws TimeoutException To notify userName of timeout. + */ + static WindowsTempShare createTempShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final String shareName, + final long timeout, + final ShareRemoteDirectoryConsumer shareRemoteDirectory + ) throws WindowsRemoteException, TimeoutException { + final long start = Utils.getCurrentTimeMillis(); + + // Find where Windows is installed on the remote system. We will create the share under %WINDIR%\Temp. + final String folder = getWindowsDirectory(windowsRemoteExecutor, timeout); + + // Create the folder on the remote system + final String remotePath = buildRemotePath(folder, shareName); + createRemoteDirectory( + windowsRemoteExecutor, + remotePath, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary directory"), + start + ); + + // Create the share + shareRemoteDirectory.apply(windowsRemoteExecutor, remotePath, shareName, timeout); + + return new WindowsTempShare(windowsRemoteExecutor, shareName, remotePath); + } + + /** Get the share name */ + public String getShareName() { + return shareName; + } + + /** Get the UNC path of the share */ + public String getUncSharePath() { + return uncSharePath; + } + + /** Get the remote path */ + public String getRemotePath() { + return remotePath; + } + + /** Get the WindowsRemoteExecutor instance */ + public WindowsRemoteExecutor getWindowsRemoteExecutor() { + return windowsRemoteExecutor; + } +} diff --git a/src/main/java/org/metricshub/winrm/WmiHelper.java b/src/main/java/org/metricshub/winrm/WmiHelper.java index 249e1cd..7db501c 100644 --- a/src/main/java/org/metricshub/winrm/WmiHelper.java +++ b/src/main/java/org/metricshub/winrm/WmiHelper.java @@ -1,154 +1,154 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; - -public abstract class WmiHelper { - - /** - * Private constructor, as this class cannot be instantiated (it's pure static) - */ - private WmiHelper() {} - - public static final String DEFAULT_NAMESPACE = "ROOT\\CIMV2"; - - /** - * Pattern to detect a simple WQL select query. - */ - private static final Pattern WQL_SIMPLE_SELECT_PATTERN = Pattern.compile( - "^\\s*SELECT\\s+(\\*|(?!SELECT|FROM|WHERE)[a-z0-9._]+|((?!SELECT|FROM|WHERE)[a-z0-9._]+\\s*,\\s*)+((?!SELECT|FROM|WHERE)[a-z0-9._]+))\\s+FROM\\s+((?!WHERE|FROM)\\w+)\\s*(WHERE\\s+.*)?$", - Pattern.CASE_INSENSITIVE - ); - - /** - * Check if the WQL Query respect the simple syntax in the form of - * Select * from (where) or Select a,b,c from (where) - * is valid. - * - * @param wqlQuery - * @return whether specified WQL query's syntax is valid or not - */ - public static boolean isValidWql(final String wqlQuery) { - return WQL_SIMPLE_SELECT_PATTERN.matcher(wqlQuery).find(); - } - - /** - * The "network resource" is either just the namespace (for localhost), or \\hostname\\namespace. - * - * @param hostname Host to connect to. - * @param namespace The Namespace. - * @return resource - */ - public static String createNetworkResource(final String hostname, final String namespace) { - Utils.checkNonNull(namespace, "namespace"); - return hostname == null || hostname.isEmpty() ? namespace : String.format("\\\\%s\\%s", hostname, namespace); - } - - /** - * @param networkResource Network resource string to test - * @return whether specified networkResource is local or not - */ - public static boolean isLocalNetworkResource(final String networkResource) { - Utils.checkNonNull(networkResource, "networkResource"); - return (!networkResource.startsWith("\\\\") - || - networkResource.startsWith("\\\\localhost\\") - || - networkResource.startsWith("\\\\127.0.0.1\\") - || - networkResource.startsWith("\\\\0:0:0:0:0:0:0:1\\") - || - networkResource.startsWith("\\\\::1\\") - || - networkResource.startsWith("\\\\0000:0000:0000:0000:0000:0000:0000:0001\\") - || - networkResource.toLowerCase().startsWith("\\\\" + Utils.getComputerName().toLowerCase() + "\\")); - } - - /** - * Extract the exact name of the properties from a WMI result. - * The interest is to retrieve the exact case of the property names, instead of - * the lowercase that we have at this stage. - * - * @param resultRows The result whose first row will be parsed - * @param wql The WQL query that was used (so we make sure to return the properties in the same order) - * @return a list of property names - * @throws IllegalStateException if the specified WQL is invalid - */ - public static List extractPropertiesFromResult(final List> resultRows, final String wql) { - try { - return extractPropertiesFromResult(resultRows, WqlQuery.newInstance(wql)); - } catch (WqlQuerySyntaxException e) { - throw new IllegalStateException(e); - } - } - - /** - * Extract the exact name of the properties from a WMI result. - * The interest is to retrieve the exact case of the property names, instead of - * the lowercase that we have at this stage. - * Note: The exact case cannot be retrieved if result is empty, in which case all - * names are reported in lower case - * - * @param resultRows The result whose first row will be parsed - * @param wqlQuery The WQL query that was used (so we make sure to return the properties in the same order) - * @return a list of property names - */ - public static List extractPropertiesFromResult( - final List> resultRows, - final WqlQuery wqlQuery - ) { - // If resultRows is empty, we won't be able to retrieve the actual property names - // with the correct case. So, we simply return the list of specified properties in the - // WQL query - if (resultRows.isEmpty()) { - return wqlQuery.getSelectedProperties(); - } - - // Extract the actual property names - final String[] resultPropertyArray = resultRows.get(0).keySet().toArray(new String[0]); - - // First case: we don't have any specified properties in the WQL Query, so we just - // return the properties from the result set in alphabetical order - if (wqlQuery.getSelectedProperties().isEmpty()) { - Arrays.sort(resultPropertyArray, String.CASE_INSENSITIVE_ORDER); - return Arrays.asList(resultPropertyArray); - } - - // Create a new list based on queryPropertyArray (with its order), but with the values - // from resultPropertyArray - final List queryProperties = wqlQuery.getSelectedProperties(); - final Map resultProperties = Arrays - .asList(resultPropertyArray) - .stream() - .collect(Collectors.toMap(String::toLowerCase, property -> property)); - return queryProperties - .stream() - .map(property -> resultProperties.getOrDefault(property.toLowerCase(), property)) - .collect(Collectors.toList()); - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; + +public abstract class WmiHelper { + + /** + * Private constructor, as this class cannot be instantiated (it's pure static) + */ + private WmiHelper() {} + + public static final String DEFAULT_NAMESPACE = "ROOT\\CIMV2"; + + /** + * Pattern to detect a simple WQL select query. + */ + private static final Pattern WQL_SIMPLE_SELECT_PATTERN = Pattern.compile( + "^\\s*SELECT\\s+(\\*|(?!SELECT|FROM|WHERE)[a-z0-9._]+|((?!SELECT|FROM|WHERE)[a-z0-9._]+\\s*,\\s*)+((?!SELECT|FROM|WHERE)[a-z0-9._]+))\\s+FROM\\s+((?!WHERE|FROM)\\w+)\\s*(WHERE\\s+.*)?$", + Pattern.CASE_INSENSITIVE + ); + + /** + * Check if the WQL Query respect the simple syntax in the form of + * Select * from (where) or Select a,b,c from (where) + * is valid. + * + * @param wqlQuery + * @return whether specified WQL query's syntax is valid or not + */ + public static boolean isValidWql(final String wqlQuery) { + return WQL_SIMPLE_SELECT_PATTERN.matcher(wqlQuery).find(); + } + + /** + * The "network resource" is either just the namespace (for localhost), or \\hostname\\namespace. + * + * @param hostname Host to connect to. + * @param namespace The Namespace. + * @return resource + */ + public static String createNetworkResource(final String hostname, final String namespace) { + Utils.checkNonNull(namespace, "namespace"); + return hostname == null || hostname.isEmpty() ? namespace : String.format("\\\\%s\\%s", hostname, namespace); + } + + /** + * @param networkResource Network resource string to test + * @return whether specified networkResource is local or not + */ + public static boolean isLocalNetworkResource(final String networkResource) { + Utils.checkNonNull(networkResource, "networkResource"); + return (!networkResource.startsWith("\\\\") + || + networkResource.startsWith("\\\\localhost\\") + || + networkResource.startsWith("\\\\127.0.0.1\\") + || + networkResource.startsWith("\\\\0:0:0:0:0:0:0:1\\") + || + networkResource.startsWith("\\\\::1\\") + || + networkResource.startsWith("\\\\0000:0000:0000:0000:0000:0000:0000:0001\\") + || + networkResource.toLowerCase().startsWith("\\\\" + Utils.getComputerName().toLowerCase() + "\\")); + } + + /** + * Extract the exact name of the properties from a WMI result. + * The interest is to retrieve the exact case of the property names, instead of + * the lowercase that we have at this stage. + * + * @param resultRows The result whose first row will be parsed + * @param wql The WQL query that was used (so we make sure to return the properties in the same order) + * @return a list of property names + * @throws IllegalStateException if the specified WQL is invalid + */ + public static List extractPropertiesFromResult(final List> resultRows, final String wql) { + try { + return extractPropertiesFromResult(resultRows, WqlQuery.newInstance(wql)); + } catch (WqlQuerySyntaxException e) { + throw new IllegalStateException(e); + } + } + + /** + * Extract the exact name of the properties from a WMI result. + * The interest is to retrieve the exact case of the property names, instead of + * the lowercase that we have at this stage. + * Note: The exact case cannot be retrieved if result is empty, in which case all + * names are reported in lower case + * + * @param resultRows The result whose first row will be parsed + * @param wqlQuery The WQL query that was used (so we make sure to return the properties in the same order) + * @return a list of property names + */ + public static List extractPropertiesFromResult( + final List> resultRows, + final WqlQuery wqlQuery + ) { + // If resultRows is empty, we won't be able to retrieve the actual property names + // with the correct case. So, we simply return the list of specified properties in the + // WQL query + if (resultRows.isEmpty()) { + return wqlQuery.getSelectedProperties(); + } + + // Extract the actual property names + final String[] resultPropertyArray = resultRows.get(0).keySet().toArray(new String[0]); + + // First case: we don't have any specified properties in the WQL Query, so we just + // return the properties from the result set in alphabetical order + if (wqlQuery.getSelectedProperties().isEmpty()) { + Arrays.sort(resultPropertyArray, String.CASE_INSENSITIVE_ORDER); + return Arrays.asList(resultPropertyArray); + } + + // Create a new list based on queryPropertyArray (with its order), but with the values + // from resultPropertyArray + final List queryProperties = wqlQuery.getSelectedProperties(); + final Map resultProperties = Arrays + .asList(resultPropertyArray) + .stream() + .collect(Collectors.toMap(String::toLowerCase, property -> property)); + return queryProperties + .stream() + .map(property -> resultProperties.getOrDefault(property.toLowerCase(), property)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/org/metricshub/winrm/WqlQuery.java b/src/main/java/org/metricshub/winrm/WqlQuery.java index a6fb59e..bbc8bbc 100644 --- a/src/main/java/org/metricshub/winrm/WqlQuery.java +++ b/src/main/java/org/metricshub/winrm/WqlQuery.java @@ -1,241 +1,241 @@ -package org.metricshub.winrm; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; - -public class WqlQuery { - - /** - * Pattern to parse a WQL query - *
      - *
    • group(1) = SELECT ... FROM - *
    • group(2) = Properties in the SELECT statement (but not '*') - *
    • group(3) = ASSOCIATORS OF { object ID } - *
    • group(4) = class in the FROM statement - *
    • group(5) = Rest of the WQL statement (WHERE, etc.) - */ - private static final Pattern WQL_PATTERN = Pattern.compile( - "^\\s*(SELECT\\s+(?:\\*|([a-z0-9._]+(?:\\s*,\\s*[a-z0-9._]+)*))\\s+FROM\\s+)?(?:((?:ASSOCIATORS|REFERENCES)\\s+OF\\s+\\{.*\\})|([a-z0-9_]+))(\\s+WHERE\\s*+.+)?\\s*$", - Pattern.CASE_INSENSITIVE | Pattern.DOTALL - ); - - private String wql; - private List selectedProperties; - private Map> subPropertiesMap; - private String cleanWql; - - private WqlQuery( - String wql, - List selectedProperties, - Map> subPropertiesMap, - String cleanWql - ) { - this.wql = wql; - this.selectedProperties = selectedProperties; - this.subPropertiesMap = subPropertiesMap; - this.cleanWql = cleanWql; - } - - /** - * Parses the specified WQL query and returns a new instance of WqlQuery - * Supported WQL syntaxes: - *
        - *
      • SELECT * FROM Class - *
      • SELECT PropA, PropB FROM Class - *
      • SELECT PropA, PropB FROM Class WHERE condition - *
      • ASSOCIATORS OF { objectId } - *
      • ASSOCIATORS OF { objectId } WHERE condition - *
      • SELECT * FROM ASSOCIATORS OF { objectId } WHERE condition - *
      • SELECT PropA, PropB FROM ASSOCIATORS OF { objectId } WHERE condition - *
      - * - * @param wql The WQL query to parse - * @return a new WqlQuery instance - * @throws WqlQuerySyntaxException when the specified WQL is invalid and cannot be parsed - * @throws IllegalArgumentException if wql is null - */ - public static WqlQuery newInstance(CharSequence wql) throws WqlQuerySyntaxException { - Utils.checkNonNull(wql, "wql"); - - Matcher wqlMatcher = WQL_PATTERN.matcher(wql); - - // No match: invalid WQL - if (!wqlMatcher.find()) { - throw new WqlQuerySyntaxException(wql.toString()); - } - - // Extract the different fragments of the parsed WQL - String selectFragment = wqlMatcher.group(1); - String propertiesFragment = wqlMatcher.group(2); - String associatorsFragment = wqlMatcher.group(3); - String classFragment = wqlMatcher.group(4); - String restFragment = wqlMatcher.group(5); - - // If there is no `SELECT` and no `ASSOCIATORS OF`, it's no valid WQL - if (selectFragment == null && associatorsFragment == null) { - throw new WqlQuerySyntaxException(wql.toString()); - } - - List properties = buildSelectedProperties(propertiesFragment); - Map> subPropertiesMap = buildSupPropertiesMap(properties); - String cleanWql = buildCleanWql(associatorsFragment, subPropertiesMap, classFragment, restFragment); - - return new WqlQuery(wql.toString(), properties, subPropertiesMap, cleanWql); - } - - /** - * Note: All properties are converted to lower case - * - * @param propertiesFragment Comma-separated list of properties - * @return a cleaned-up array of the properties - */ - static List buildSelectedProperties(String propertiesFragment) { - if (Utils.isNotBlank(propertiesFragment)) { - return Arrays.asList(propertiesFragment.trim().toLowerCase().split("\\s*,\\s*")); - } - return new ArrayList<>(); - } - - /** - * Build a Map of subproperties to retrieve inside properties - * Example: - * Input: - * PropA, PropB.Sub1, PropB.Sub2 - * Output: - *
        - *
      • PropA => emptySet() - *
      • PropB => { "Sub1", "Sub2" } - *
      - * - * @param properties Selected properties (that may include subproperties) - * @return The map as described above - */ - static Map> buildSupPropertiesMap(final List properties) { - // Empty or null? - if (properties == null || properties.isEmpty()) { - return new HashMap<>(); - } - - Map> subPropertiesMap = new LinkedHashMap<>(); - properties - .stream() - .filter(Utils::isNotBlank) - .forEachOrdered(property -> { - // Split the property into fragments: - // propA => ["propA"] - // propA.subProp => ["propA", "subProp"] - String[] propertyFragmentArray = property.toLowerCase().split("\\.", 2); - String mainProperty = propertyFragmentArray[0]; - String subProperty = propertyFragmentArray.length == 2 ? propertyFragmentArray[1] : null; - - // Add this entry to the map - subPropertiesMap.compute( - mainProperty, - (key, subPropertiesSet) -> { - if (subPropertiesSet == null) { - subPropertiesSet = new HashSet<>(); - } - if (subProperty != null) { - subPropertiesSet.add(subProperty); - } - return subPropertiesSet; - } - ); - }); - - return subPropertiesMap; - } - - /** - * Build a strict WQL query from the "dirty" one we have - * By strict we mean a syntax that can be executed by the WMI provider.
      - * By dirty we mean the extra sugar-coated syntax we're allowing in Metricshub products, - * like subproperties, and SELECT prop FROM ASSOCIATORS OF... - * Examples: - *
        - *
      • SELECT PropA.Name FROM Win32_Class
        - * => SELECT PropA FROM Win32_Class - *
      • SELECT Temperature FROM ASSOCIATORS OF { Win32_Class.Id=1 }
        - * => ASSOCIATORS OF { Win32_Class.Id=1 } - *
      - * - * @param associatorsFragment The extracted ASSOCIATORS OF... fragment - * @param subPropertiesMap The map built with {@link WqlQuery#buildSupPropertiesMap(String[])} - * @param classFragment The extracted class fragment - * @param restFragment The rest (WHERE...) - * @return a clean and strict WQL statement - */ - static String buildCleanWql( - String associatorsFragment, - Map> subPropertiesMap, - String classFragment, - String restFragment - ) { - String cleanWql; - - if (associatorsFragment == null) { - if (subPropertiesMap.keySet().isEmpty()) { - cleanWql = "SELECT * FROM " + classFragment; - } else { - cleanWql = String.format( - "SELECT %s FROM %s", - subPropertiesMap.keySet().stream().collect(Collectors.joining(",")), - classFragment - ); - } - } else { - cleanWql = associatorsFragment; - } - if (restFragment != null) { - cleanWql = cleanWql + restFragment; - } - return cleanWql; - } - - public List getSelectedProperties() { - return selectedProperties; - } - - public Map> getSubPropertiesMap() { - return subPropertiesMap; - } - - public String getCleanWql() { - return cleanWql; - } - - @Override - public String toString() { - return wql; - } -} +package org.metricshub.winrm; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; + +public class WqlQuery { + + /** + * Pattern to parse a WQL query + *
        + *
      • group(1) = SELECT ... FROM + *
      • group(2) = Properties in the SELECT statement (but not '*') + *
      • group(3) = ASSOCIATORS OF { object ID } + *
      • group(4) = class in the FROM statement + *
      • group(5) = Rest of the WQL statement (WHERE, etc.) + */ + private static final Pattern WQL_PATTERN = Pattern.compile( + "^\\s*(SELECT\\s+(?:\\*|([a-z0-9._]+(?:\\s*,\\s*[a-z0-9._]+)*))\\s+FROM\\s+)?(?:((?:ASSOCIATORS|REFERENCES)\\s+OF\\s+\\{.*\\})|([a-z0-9_]+))(\\s+WHERE\\s*+.+)?\\s*$", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL + ); + + private String wql; + private List selectedProperties; + private Map> subPropertiesMap; + private String cleanWql; + + private WqlQuery( + String wql, + List selectedProperties, + Map> subPropertiesMap, + String cleanWql + ) { + this.wql = wql; + this.selectedProperties = selectedProperties; + this.subPropertiesMap = subPropertiesMap; + this.cleanWql = cleanWql; + } + + /** + * Parses the specified WQL query and returns a new instance of WqlQuery + * Supported WQL syntaxes: + *
          + *
        • SELECT * FROM Class + *
        • SELECT PropA, PropB FROM Class + *
        • SELECT PropA, PropB FROM Class WHERE condition + *
        • ASSOCIATORS OF { objectId } + *
        • ASSOCIATORS OF { objectId } WHERE condition + *
        • SELECT * FROM ASSOCIATORS OF { objectId } WHERE condition + *
        • SELECT PropA, PropB FROM ASSOCIATORS OF { objectId } WHERE condition + *
        + * + * @param wql The WQL query to parse + * @return a new WqlQuery instance + * @throws WqlQuerySyntaxException when the specified WQL is invalid and cannot be parsed + * @throws IllegalArgumentException if wql is null + */ + public static WqlQuery newInstance(CharSequence wql) throws WqlQuerySyntaxException { + Utils.checkNonNull(wql, "wql"); + + Matcher wqlMatcher = WQL_PATTERN.matcher(wql); + + // No match: invalid WQL + if (!wqlMatcher.find()) { + throw new WqlQuerySyntaxException(wql.toString()); + } + + // Extract the different fragments of the parsed WQL + String selectFragment = wqlMatcher.group(1); + String propertiesFragment = wqlMatcher.group(2); + String associatorsFragment = wqlMatcher.group(3); + String classFragment = wqlMatcher.group(4); + String restFragment = wqlMatcher.group(5); + + // If there is no `SELECT` and no `ASSOCIATORS OF`, it's no valid WQL + if (selectFragment == null && associatorsFragment == null) { + throw new WqlQuerySyntaxException(wql.toString()); + } + + List properties = buildSelectedProperties(propertiesFragment); + Map> subPropertiesMap = buildSupPropertiesMap(properties); + String cleanWql = buildCleanWql(associatorsFragment, subPropertiesMap, classFragment, restFragment); + + return new WqlQuery(wql.toString(), properties, subPropertiesMap, cleanWql); + } + + /** + * Note: All properties are converted to lower case + * + * @param propertiesFragment Comma-separated list of properties + * @return a cleaned-up array of the properties + */ + static List buildSelectedProperties(String propertiesFragment) { + if (Utils.isNotBlank(propertiesFragment)) { + return Arrays.asList(propertiesFragment.trim().toLowerCase().split("\\s*,\\s*")); + } + return new ArrayList<>(); + } + + /** + * Build a Map of subproperties to retrieve inside properties + * Example: + * Input: + * PropA, PropB.Sub1, PropB.Sub2 + * Output: + *
          + *
        • PropA => emptySet() + *
        • PropB => { "Sub1", "Sub2" } + *
        + * + * @param properties Selected properties (that may include subproperties) + * @return The map as described above + */ + static Map> buildSupPropertiesMap(final List properties) { + // Empty or null? + if (properties == null || properties.isEmpty()) { + return new HashMap<>(); + } + + Map> subPropertiesMap = new LinkedHashMap<>(); + properties + .stream() + .filter(Utils::isNotBlank) + .forEachOrdered(property -> { + // Split the property into fragments: + // propA => ["propA"] + // propA.subProp => ["propA", "subProp"] + String[] propertyFragmentArray = property.toLowerCase().split("\\.", 2); + String mainProperty = propertyFragmentArray[0]; + String subProperty = propertyFragmentArray.length == 2 ? propertyFragmentArray[1] : null; + + // Add this entry to the map + subPropertiesMap.compute( + mainProperty, + (key, subPropertiesSet) -> { + if (subPropertiesSet == null) { + subPropertiesSet = new HashSet<>(); + } + if (subProperty != null) { + subPropertiesSet.add(subProperty); + } + return subPropertiesSet; + } + ); + }); + + return subPropertiesMap; + } + + /** + * Build a strict WQL query from the "dirty" one we have + * By strict we mean a syntax that can be executed by the WMI provider.
        + * By dirty we mean the extra sugar-coated syntax we're allowing in Metricshub products, + * like subproperties, and SELECT prop FROM ASSOCIATORS OF... + * Examples: + *
          + *
        • SELECT PropA.Name FROM Win32_Class
          + * => SELECT PropA FROM Win32_Class + *
        • SELECT Temperature FROM ASSOCIATORS OF { Win32_Class.Id=1 }
          + * => ASSOCIATORS OF { Win32_Class.Id=1 } + *
        + * + * @param associatorsFragment The extracted ASSOCIATORS OF... fragment + * @param subPropertiesMap The map built with {@link WqlQuery#buildSupPropertiesMap(String[])} + * @param classFragment The extracted class fragment + * @param restFragment The rest (WHERE...) + * @return a clean and strict WQL statement + */ + static String buildCleanWql( + String associatorsFragment, + Map> subPropertiesMap, + String classFragment, + String restFragment + ) { + String cleanWql; + + if (associatorsFragment == null) { + if (subPropertiesMap.keySet().isEmpty()) { + cleanWql = "SELECT * FROM " + classFragment; + } else { + cleanWql = String.format( + "SELECT %s FROM %s", + subPropertiesMap.keySet().stream().collect(Collectors.joining(",")), + classFragment + ); + } + } else { + cleanWql = associatorsFragment; + } + if (restFragment != null) { + cleanWql = cleanWql + restFragment; + } + return cleanWql; + } + + public List getSelectedProperties() { + return selectedProperties; + } + + public Map> getSubPropertiesMap() { + return subPropertiesMap; + } + + public String getCleanWql() { + return cleanWql; + } + + @Override + public String toString() { + return wql; + } +} diff --git a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java index 9464f57..a7f8585 100644 --- a/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java +++ b/src/main/java/org/metricshub/winrm/command/WinRMCommandExecutor.java @@ -1,156 +1,156 @@ -package org.metricshub.winrm.command; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.util.List; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import org.metricshub.winrm.TimeoutHelper; -import org.metricshub.winrm.Utils; -import org.metricshub.winrm.WinRMHttpProtocolEnum; -import org.metricshub.winrm.WindowsRemoteCommandResult; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.WindowsRemoteProcessUtils; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; -import org.metricshub.winrm.shares.SmbTempShare; - -public class WinRMCommandExecutor { - - private WinRMCommandExecutor() {} - - /** - * Execute a command on a remote Windows system and return an object with - * the output of the command. - * You can specify local files to be copied to the remote system before executing the command. - * If the command contains references to these local files, it will be updated to reference the - * path on the remote system where the files have been copied. - * Example: - * - * WinRemoteCommandExecutor.execute( - * "CSCRIPT c:\\MyScript.vbs", null, "remote-srv", null, null, null, 30000, Arrays.asList("c:\\MyScript.vbs"), false); - * - * This will copy c:\\MyScript.vbs to remote-srv, typically in - * C:\\Windows\\Temp\\SEN_ShareFor_MYHOST and the command that is executed will therefore - * become: - * CSCRIPT "C:\\Windows\\Temp\\SEN_ShareFor_MYHOST\\MyScript.vbs" - * - * @param command The command to execute. (Mandatory) - * @param protocol The HTTP protocol (HTTP by default) - * @param hostname Host to connect to. (Mandatory) - * @param port The port (5985 for HTPP or 5986 for HTTPS by default) - * @param username The username name. (Mandatory) - * @param password The password. - * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null) - * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero) - * @param localFileToCopyList List of local files to copy to the remote before the execution - * @param ticketCache The Ticket Cache path - * @param authentications List of authentications. only NTLM if absent - * @return an instance of WindowsRemoteCommandResult with the result of the command - * @throws IOException If an I/O error occurs. - * @throws TimeoutException To notify userName of timeout - * @throws WindowsRemoteException For any problem encountered on remote - */ - public static WindowsRemoteCommandResult execute( - final String command, - final WinRMHttpProtocolEnum protocol, - final String hostname, - final Integer port, - final String username, - final char[] password, - final String workingDirectory, - final long timeout, - final List localFileToCopyList, - final Path ticketCache, - final List authentications - ) throws IOException, TimeoutException, WindowsRemoteException { - Utils.checkNonNull(command, "command"); - Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); - - final long start = System.currentTimeMillis(); - - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(protocol, hostname, port, username, password, null); - - if (localFileToCopyList == null || localFileToCopyList.isEmpty()) { - try ( - final WindowsRemoteExecutor winRMService = WinRMExecutorFactory.createInstance( - winRMEndpoint, - timeout, - ticketCache, - authentications - )) { - final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset( - winRMService, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set") - ); - - return winRMService.executeCommand(command, workingDirectory, charset, timeout); - } catch (final WqlQuerySyntaxException e) { - throw new IOException(e); - } - } - - try ( - final SmbTempShare smbTempShare = SmbTempShare.createInstance( - winRMEndpoint, - timeout, - ticketCache, - authentications - )) { - smbTempShare.checkConnectedFirst(); - - final List localFiles = localFileToCopyList - .stream() - .filter(Utils::isNotBlank) - .collect(Collectors.toList()); - - // Copy the list specified list of files, and update the command accordingly - final String localFilesUpdatedCommand = WindowsRemoteProcessUtils.copyLocalFilesToShare( - command, - localFiles, - smbTempShare.getUncSharePath(), - smbTempShare.getRemotePath() - ); - - final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset( - smbTempShare.getWindowsRemoteExecutor(), - TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set") - ); - - return smbTempShare - .getWindowsRemoteExecutor() - .executeCommand( - String.format("CMD.EXE /C (%s)", localFilesUpdatedCommand), - null, - charset, - TimeoutHelper.getRemainingTime(timeout, start, "No time left to execute command") - ); - } catch (final WqlQuerySyntaxException e) { - throw new IOException(e); - } - } -} +package org.metricshub.winrm.command; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright (C) 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import org.metricshub.winrm.TimeoutHelper; +import org.metricshub.winrm.Utils; +import org.metricshub.winrm.WinRMHttpProtocolEnum; +import org.metricshub.winrm.WindowsRemoteCommandResult; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.WindowsRemoteProcessUtils; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; +import org.metricshub.winrm.shares.SmbTempShare; + +public class WinRMCommandExecutor { + + private WinRMCommandExecutor() {} + + /** + * Execute a command on a remote Windows system and return an object with + * the output of the command. + * You can specify local files to be copied to the remote system before executing the command. + * If the command contains references to these local files, it will be updated to reference the + * path on the remote system where the files have been copied. + * Example: + * + * WinRemoteCommandExecutor.execute( + * "CSCRIPT c:\\MyScript.vbs", null, "remote-srv", null, null, null, 30000, Arrays.asList("c:\\MyScript.vbs"), false); + * + * This will copy c:\\MyScript.vbs to remote-srv, typically in + * C:\\Windows\\Temp\\SEN_ShareFor_MYHOST and the command that is executed will therefore + * become: + * CSCRIPT "C:\\Windows\\Temp\\SEN_ShareFor_MYHOST\\MyScript.vbs" + * + * @param command The command to execute. (Mandatory) + * @param protocol The HTTP protocol (HTTP by default) + * @param hostname Host to connect to. (Mandatory) + * @param port The port (5985 for HTPP or 5986 for HTTPS by default) + * @param username The username name. (Mandatory) + * @param password The password. + * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null) + * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero) + * @param localFileToCopyList List of local files to copy to the remote before the execution + * @param ticketCache The Ticket Cache path + * @param authentications List of authentications. only NTLM if absent + * @return an instance of WindowsRemoteCommandResult with the result of the command + * @throws IOException If an I/O error occurs. + * @throws TimeoutException To notify userName of timeout + * @throws WindowsRemoteException For any problem encountered on remote + */ + public static WindowsRemoteCommandResult execute( + final String command, + final WinRMHttpProtocolEnum protocol, + final String hostname, + final Integer port, + final String username, + final char[] password, + final String workingDirectory, + final long timeout, + final List localFileToCopyList, + final Path ticketCache, + final List authentications + ) throws IOException, TimeoutException, WindowsRemoteException { + Utils.checkNonNull(command, "command"); + Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); + + final long start = System.currentTimeMillis(); + + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(protocol, hostname, port, username, password, null); + + if (localFileToCopyList == null || localFileToCopyList.isEmpty()) { + try ( + final WindowsRemoteExecutor winRMService = WinRMExecutorFactory.createInstance( + winRMEndpoint, + timeout, + ticketCache, + authentications + )) { + final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset( + winRMService, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set") + ); + + return winRMService.executeCommand(command, workingDirectory, charset, timeout); + } catch (final WqlQuerySyntaxException e) { + throw new IOException(e); + } + } + + try ( + final SmbTempShare smbTempShare = SmbTempShare.createInstance( + winRMEndpoint, + timeout, + ticketCache, + authentications + )) { + smbTempShare.checkConnectedFirst(); + + final List localFiles = localFileToCopyList + .stream() + .filter(Utils::isNotBlank) + .collect(Collectors.toList()); + + // Copy the list specified list of files, and update the command accordingly + final String localFilesUpdatedCommand = WindowsRemoteProcessUtils.copyLocalFilesToShare( + command, + localFiles, + smbTempShare.getUncSharePath(), + smbTempShare.getRemotePath() + ); + + final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset( + smbTempShare.getWindowsRemoteExecutor(), + TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set") + ); + + return smbTempShare + .getWindowsRemoteExecutor() + .executeCommand( + String.format("CMD.EXE /C (%s)", localFilesUpdatedCommand), + null, + charset, + TimeoutHelper.getRemainingTime(timeout, start, "No time left to execute command") + ); + } catch (final WqlQuerySyntaxException e) { + throw new IOException(e); + } + } +} diff --git a/src/main/java/org/metricshub/winrm/exceptions/WinRMException.java b/src/main/java/org/metricshub/winrm/exceptions/WinRMException.java index 72f5985..d3b32a3 100644 --- a/src/main/java/org/metricshub/winrm/exceptions/WinRMException.java +++ b/src/main/java/org/metricshub/winrm/exceptions/WinRMException.java @@ -1,42 +1,42 @@ -package org.metricshub.winrm.exceptions; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -public class WinRMException extends WindowsRemoteException { - - private static final long serialVersionUID = 1L; - - public WinRMException(final String message) { - super(message); - } - - public WinRMException(final String messageFromat, final Object... args) { - super(messageFromat, args); - } - - public WinRMException(final Throwable cause, final String message) { - super(cause, message); - } - - public WinRMException(final Throwable cause) { - super(cause); - } -} +package org.metricshub.winrm.exceptions; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +public class WinRMException extends WindowsRemoteException { + + private static final long serialVersionUID = 1L; + + public WinRMException(final String message) { + super(message); + } + + public WinRMException(final String messageFromat, final Object... args) { + super(messageFromat, args); + } + + public WinRMException(final Throwable cause, final String message) { + super(cause, message); + } + + public WinRMException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/metricshub/winrm/exceptions/WindowsRemoteException.java b/src/main/java/org/metricshub/winrm/exceptions/WindowsRemoteException.java index a734176..36eedd2 100644 --- a/src/main/java/org/metricshub/winrm/exceptions/WindowsRemoteException.java +++ b/src/main/java/org/metricshub/winrm/exceptions/WindowsRemoteException.java @@ -1,46 +1,46 @@ -package org.metricshub.winrm.exceptions; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -public class WindowsRemoteException extends Exception { - - private static final long serialVersionUID = 1L; - - public WindowsRemoteException(final String message) { - super(message); - } - - public WindowsRemoteException(final String messageFromat, final Object... args) { - super(String.format(messageFromat, args)); - } - - public WindowsRemoteException(final Throwable cause, final String message) { - super(message, cause); - } - - public WindowsRemoteException(final Throwable cause, final String messageFromat, final Object... args) { - super(String.format(messageFromat, args), cause); - } - - public WindowsRemoteException(final Throwable cause) { - super(cause); - } -} +package org.metricshub.winrm.exceptions; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +public class WindowsRemoteException extends Exception { + + private static final long serialVersionUID = 1L; + + public WindowsRemoteException(final String message) { + super(message); + } + + public WindowsRemoteException(final String messageFromat, final Object... args) { + super(String.format(messageFromat, args)); + } + + public WindowsRemoteException(final Throwable cause, final String message) { + super(message, cause); + } + + public WindowsRemoteException(final Throwable cause, final String messageFromat, final Object... args) { + super(String.format(messageFromat, args), cause); + } + + public WindowsRemoteException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java b/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java index 51926b6..eeca9d3 100644 --- a/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java +++ b/src/main/java/org/metricshub/winrm/exceptions/WqlQuerySyntaxException.java @@ -1,37 +1,37 @@ -package org.metricshub.winrm.exceptions; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -/** - * Exception for a syntax error in the WQL Query parameter. - */ -public class WqlQuerySyntaxException extends Exception { - - private static final long serialVersionUID = 1L; - - public WqlQuerySyntaxException(final String wqlQuery) { - super("Syntax error in WQL Query: " + wqlQuery); - } - - public WqlQuerySyntaxException(final Throwable cause) { - super(cause); - } -} +package org.metricshub.winrm.exceptions; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +/** + * Exception for a syntax error in the WQL Query parameter. + */ +public class WqlQuerySyntaxException extends Exception { + + private static final long serialVersionUID = 1L; + + public WqlQuerySyntaxException(final String wqlQuery) { + super("Syntax error in WQL Query: " + wqlQuery); + } + + public WqlQuerySyntaxException(final Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/metricshub/winrm/light/AuthScheme.java b/src/main/java/org/metricshub/winrm/light/AuthScheme.java index f170e3f..105fae8 100644 --- a/src/main/java/org/metricshub/winrm/light/AuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/AuthScheme.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java b/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java index 6884632..54345a7 100644 --- a/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java +++ b/src/main/java/org/metricshub/winrm/light/ByteArrayUtils.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/CipherGen.java b/src/main/java/org/metricshub/winrm/light/CipherGen.java index 605282b..2bd0dc0 100644 --- a/src/main/java/org/metricshub/winrm/light/CipherGen.java +++ b/src/main/java/org/metricshub/winrm/light/CipherGen.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/EncryptionUtils.java b/src/main/java/org/metricshub/winrm/light/EncryptionUtils.java index 9108c14..315c070 100644 --- a/src/main/java/org/metricshub/winrm/light/EncryptionUtils.java +++ b/src/main/java/org/metricshub/winrm/light/EncryptionUtils.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/Envelopes.java b/src/main/java/org/metricshub/winrm/light/Envelopes.java index 491bd71..00b3483 100644 --- a/src/main/java/org/metricshub/winrm/light/Envelopes.java +++ b/src/main/java/org/metricshub/winrm/light/Envelopes.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java b/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java index 95b7042..ac3b7e0 100644 --- a/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/FallbackAuthScheme.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/HMACMD5.java b/src/main/java/org/metricshub/winrm/light/HMACMD5.java index f3b200f..1722299 100644 --- a/src/main/java/org/metricshub/winrm/light/HMACMD5.java +++ b/src/main/java/org/metricshub/winrm/light/HMACMD5.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/HttpTransport.java b/src/main/java/org/metricshub/winrm/light/HttpTransport.java index 8cfd8d4..580dc98 100644 --- a/src/main/java/org/metricshub/winrm/light/HttpTransport.java +++ b/src/main/java/org/metricshub/winrm/light/HttpTransport.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java b/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java index a7ee1e9..06de531 100644 --- a/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/KerberosAuthScheme.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/LightTls.java b/src/main/java/org/metricshub/winrm/light/LightTls.java index b4e5147..5315687 100644 --- a/src/main/java/org/metricshub/winrm/light/LightTls.java +++ b/src/main/java/org/metricshub/winrm/light/LightTls.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/LightWinRMService.java b/src/main/java/org/metricshub/winrm/light/LightWinRMService.java index f672919..458f12c 100644 --- a/src/main/java/org/metricshub/winrm/light/LightWinRMService.java +++ b/src/main/java/org/metricshub/winrm/light/LightWinRMService.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/MD4.java b/src/main/java/org/metricshub/winrm/light/MD4.java index ddb7f5e..a1dd746 100644 --- a/src/main/java/org/metricshub/winrm/light/MD4.java +++ b/src/main/java/org/metricshub/winrm/light/MD4.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java b/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java index df77b87..3d8d8da 100644 --- a/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java +++ b/src/main/java/org/metricshub/winrm/light/NTLMEngineUtils.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/NTLMMessage.java b/src/main/java/org/metricshub/winrm/light/NTLMMessage.java index 71814a6..b96f426 100644 --- a/src/main/java/org/metricshub/winrm/light/NTLMMessage.java +++ b/src/main/java/org/metricshub/winrm/light/NTLMMessage.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java b/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java index 3f43a24..1983677 100644 --- a/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java +++ b/src/main/java/org/metricshub/winrm/light/NtlmAuthScheme.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java b/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java index f1a18dc..ba83041 100644 --- a/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java +++ b/src/main/java/org/metricshub/winrm/light/NtlmCrypto.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/NtlmException.java b/src/main/java/org/metricshub/winrm/light/NtlmException.java index 5b15d10..e00cb04 100644 --- a/src/main/java/org/metricshub/winrm/light/NtlmException.java +++ b/src/main/java/org/metricshub/winrm/light/NtlmException.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/Type1Message.java b/src/main/java/org/metricshub/winrm/light/Type1Message.java index 4c110b9..e74f48d 100644 --- a/src/main/java/org/metricshub/winrm/light/Type1Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type1Message.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/Type2Message.java b/src/main/java/org/metricshub/winrm/light/Type2Message.java index 30b655c..5f069c3 100644 --- a/src/main/java/org/metricshub/winrm/light/Type2Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type2Message.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/Type3Message.java b/src/main/java/org/metricshub/winrm/light/Type3Message.java index df76f69..0af4499 100644 --- a/src/main/java/org/metricshub/winrm/light/Type3Message.java +++ b/src/main/java/org/metricshub/winrm/light/Type3Message.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/WinRMSession.java b/src/main/java/org/metricshub/winrm/light/WinRMSession.java index b898255..ab165a7 100644 --- a/src/main/java/org/metricshub/winrm/light/WinRMSession.java +++ b/src/main/java/org/metricshub/winrm/light/WinRMSession.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/light/WsmanClient.java b/src/main/java/org/metricshub/winrm/light/WsmanClient.java index f9ef229..587584a 100644 --- a/src/main/java/org/metricshub/winrm/light/WsmanClient.java +++ b/src/main/java/org/metricshub/winrm/light/WsmanClient.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java b/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java index cc508f1..e4d9b55 100644 --- a/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java +++ b/src/main/java/org/metricshub/winrm/service/WinRMEndpoint.java @@ -1,247 +1,247 @@ -package org.metricshub.winrm.service; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Arrays; -import java.util.Objects; -import org.metricshub.winrm.Utils; -import org.metricshub.winrm.WinRMHttpProtocolEnum; -import org.metricshub.winrm.WmiHelper; - -public class WinRMEndpoint { - - private static final int DEFAULT_WIN_RM_HTTP_PORT = 5985; - private static final int DEFAULT_WIN_RM_HTTPS_PORT = 5986; - - private final String hostname; - private final String endpoint; - private final String domain; - private final String username; - private final char[] password; - private final String namespace; - private final String rawUsername; - private final WinRMHttpProtocolEnum protocol; - private final int port; - - /** - * Constructor of the endpoint and credentials for WinRM. - * - * @param protocol The protocol - * @param hostname The host name (mandatory) - * @param port The port - * @param username The user name (mandatory) - * @param password The password (mandatory) - * @param namespace The namespace default value: {@value WmiHelper#DEFAULT_NAMESPACE} - */ - public WinRMEndpoint( - final WinRMHttpProtocolEnum protocol, - final String hostname, - final Integer port, - final String username, - final char[] password, - final String namespace - ) { - Utils.checkNonNull(hostname, "hostname"); - Utils.checkNonNull(username, "username"); - Utils.checkNonNull(password, "password"); - - this.hostname = hostname.replaceAll("\\s", Utils.EMPTY); - this.password = password; - rawUsername = username; - - this.namespace = buildNamespace(namespace); - - final String user = username.replaceAll("\\s", Utils.EMPTY); - if (user.contains("\\")) { - final String[] array = user.split("\\\\"); - domain = array[0]; - this.username = array[1]; - } else { - domain = null; - this.username = user; - } - - this.protocol = protocol != null ? protocol : WinRMHttpProtocolEnum.HTTP; - this.port = getEndpointPort(this.protocol, port); - final String endpointUrl = buildEndpointUrl(this.protocol, this.hostname, port); - - endpoint = buildWSManEndpoint(endpointUrl); - } - - /** Get the hostname */ - public String getHostname() { - return hostname; - } - - /** Get the endpoint in the form of http(s)://host:port/wsman */ - public String getEndpoint() { - return endpoint; - } - - /** Get the domain from the raw username */ - public String getDomain() { - return domain; - } - - /** get the username as indicated in the constructor (could be in domain\\user form) */ - public String getRawUsername() { - return rawUsername; - } - - /** Get the username part of the raw username */ - public String getUsername() { - return username; - } - - /** Get the password */ - public char[] getPassword() { - return password; - } - - /** Get the namespace */ - public String getNamespace() { - return namespace; - } - - /** Get the protocol */ - public WinRMHttpProtocolEnum getProtocol() { - return protocol; - } - - /** Get the resolved endpoint port (the port provided to the constructor, or the protocol default). */ - public int getPort() { - return port; - } - - /** - * Build the endpoint URL. - * - * @param protocol The HTTP protocol - * @param hostname The host name - * @param port The port - * @return the endpoint URL in form of protocol://hostname:port (with protocol in HTTP or HTTPS). - */ - public static String buildEndpointUrl( - final WinRMHttpProtocolEnum protocol, - final String hostname, - final Integer port - ) { - final int endpointPort = getEndpointPort(protocol, port); - - return String.format("%s://%s:%d", protocol.toString(), hostname, endpointPort); - } - - /** - * Get the endpoint port: - *
          - *
        • The port if provided
        • - *
        • 5986 if the protocol is HTTPS
        • - *
        • 5985 otherwise
        • - *
        - * - * @param protocol The HTTP protocol - * @param port The port - * @return The endpoint port - */ - public static int getEndpointPort(final WinRMHttpProtocolEnum protocol, final Integer port) { - if (port != null) { - return port; - } - return protocol == WinRMHttpProtocolEnum.HTTPS ? DEFAULT_WIN_RM_HTTPS_PORT : DEFAULT_WIN_RM_HTTP_PORT; - } - - /** - * Get the namespace and replace '\' by '/' if necessary - * default: {@value WmiHelper#DEFAULT_NAMESPACE} if null - * - * @param namespace the provided namespace - * @return - */ - public static String buildNamespace(final String namespace) { - final String cleanNamespace = namespace != null ? namespace.replaceAll("\\s", Utils.EMPTY) : Utils.EMPTY; - - final String usedNamespace = Utils.isNotBlank(cleanNamespace) ? cleanNamespace : WmiHelper.DEFAULT_NAMESPACE; - - return usedNamespace.replace('\\', '/'); - } - - /** - * Build the WSMan URL endpoint. - * - * @param endpoint the endpoint in form of protocol://hostname:port (with protocol in HTTP or HTTPS). - * @return The external form of the endpoint URL - */ - private static String buildWSManEndpoint(final String endpoint) { - try { - return new URL(String.format("%s/wsman", endpoint)).toExternalForm(); - } catch (final MalformedURLException e) { - throw new IllegalArgumentException(String.format("endpoint %s is invalid.", endpoint), e); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(password); - result = prime * result + Objects.hash(endpoint, namespace, rawUsername); - return result; - } - - @Override - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof WinRMEndpoint)) { - return false; - } - final WinRMEndpoint other = (WinRMEndpoint) obj; - return (Objects.equals(endpoint, other.endpoint) - && - Objects.equals(namespace, other.namespace) - && - Arrays.equals(password, other.password) - && - Objects.equals(rawUsername, other.rawUsername)); - } - - @Override - public String toString() { - return new StringBuilder() - .append("WinRMEndpoint [") - .append("endpoint=") - .append(endpoint) - .append(", domain=") - .append(domain) - .append(", username=") - .append(username) - .append(", namespace=") - .append(namespace) - .append("]") - .toString(); - } -} +package org.metricshub.winrm.service; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Objects; +import org.metricshub.winrm.Utils; +import org.metricshub.winrm.WinRMHttpProtocolEnum; +import org.metricshub.winrm.WmiHelper; + +public class WinRMEndpoint { + + private static final int DEFAULT_WIN_RM_HTTP_PORT = 5985; + private static final int DEFAULT_WIN_RM_HTTPS_PORT = 5986; + + private final String hostname; + private final String endpoint; + private final String domain; + private final String username; + private final char[] password; + private final String namespace; + private final String rawUsername; + private final WinRMHttpProtocolEnum protocol; + private final int port; + + /** + * Constructor of the endpoint and credentials for WinRM. + * + * @param protocol The protocol + * @param hostname The host name (mandatory) + * @param port The port + * @param username The user name (mandatory) + * @param password The password (mandatory) + * @param namespace The namespace default value: {@value WmiHelper#DEFAULT_NAMESPACE} + */ + public WinRMEndpoint( + final WinRMHttpProtocolEnum protocol, + final String hostname, + final Integer port, + final String username, + final char[] password, + final String namespace + ) { + Utils.checkNonNull(hostname, "hostname"); + Utils.checkNonNull(username, "username"); + Utils.checkNonNull(password, "password"); + + this.hostname = hostname.replaceAll("\\s", Utils.EMPTY); + this.password = password; + rawUsername = username; + + this.namespace = buildNamespace(namespace); + + final String user = username.replaceAll("\\s", Utils.EMPTY); + if (user.contains("\\")) { + final String[] array = user.split("\\\\"); + domain = array[0]; + this.username = array[1]; + } else { + domain = null; + this.username = user; + } + + this.protocol = protocol != null ? protocol : WinRMHttpProtocolEnum.HTTP; + this.port = getEndpointPort(this.protocol, port); + final String endpointUrl = buildEndpointUrl(this.protocol, this.hostname, port); + + endpoint = buildWSManEndpoint(endpointUrl); + } + + /** Get the hostname */ + public String getHostname() { + return hostname; + } + + /** Get the endpoint in the form of http(s)://host:port/wsman */ + public String getEndpoint() { + return endpoint; + } + + /** Get the domain from the raw username */ + public String getDomain() { + return domain; + } + + /** get the username as indicated in the constructor (could be in domain\\user form) */ + public String getRawUsername() { + return rawUsername; + } + + /** Get the username part of the raw username */ + public String getUsername() { + return username; + } + + /** Get the password */ + public char[] getPassword() { + return password; + } + + /** Get the namespace */ + public String getNamespace() { + return namespace; + } + + /** Get the protocol */ + public WinRMHttpProtocolEnum getProtocol() { + return protocol; + } + + /** Get the resolved endpoint port (the port provided to the constructor, or the protocol default). */ + public int getPort() { + return port; + } + + /** + * Build the endpoint URL. + * + * @param protocol The HTTP protocol + * @param hostname The host name + * @param port The port + * @return the endpoint URL in form of protocol://hostname:port (with protocol in HTTP or HTTPS). + */ + public static String buildEndpointUrl( + final WinRMHttpProtocolEnum protocol, + final String hostname, + final Integer port + ) { + final int endpointPort = getEndpointPort(protocol, port); + + return String.format("%s://%s:%d", protocol.toString(), hostname, endpointPort); + } + + /** + * Get the endpoint port: + *
          + *
        • The port if provided
        • + *
        • 5986 if the protocol is HTTPS
        • + *
        • 5985 otherwise
        • + *
        + * + * @param protocol The HTTP protocol + * @param port The port + * @return The endpoint port + */ + public static int getEndpointPort(final WinRMHttpProtocolEnum protocol, final Integer port) { + if (port != null) { + return port; + } + return protocol == WinRMHttpProtocolEnum.HTTPS ? DEFAULT_WIN_RM_HTTPS_PORT : DEFAULT_WIN_RM_HTTP_PORT; + } + + /** + * Get the namespace and replace '\' by '/' if necessary + * default: {@value WmiHelper#DEFAULT_NAMESPACE} if null + * + * @param namespace the provided namespace + * @return + */ + public static String buildNamespace(final String namespace) { + final String cleanNamespace = namespace != null ? namespace.replaceAll("\\s", Utils.EMPTY) : Utils.EMPTY; + + final String usedNamespace = Utils.isNotBlank(cleanNamespace) ? cleanNamespace : WmiHelper.DEFAULT_NAMESPACE; + + return usedNamespace.replace('\\', '/'); + } + + /** + * Build the WSMan URL endpoint. + * + * @param endpoint the endpoint in form of protocol://hostname:port (with protocol in HTTP or HTTPS). + * @return The external form of the endpoint URL + */ + private static String buildWSManEndpoint(final String endpoint) { + try { + return new URL(String.format("%s/wsman", endpoint)).toExternalForm(); + } catch (final MalformedURLException e) { + throw new IllegalArgumentException(String.format("endpoint %s is invalid.", endpoint), e); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(password); + result = prime * result + Objects.hash(endpoint, namespace, rawUsername); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof WinRMEndpoint)) { + return false; + } + final WinRMEndpoint other = (WinRMEndpoint) obj; + return (Objects.equals(endpoint, other.endpoint) + && + Objects.equals(namespace, other.namespace) + && + Arrays.equals(password, other.password) + && + Objects.equals(rawUsername, other.rawUsername)); + } + + @Override + public String toString() { + return new StringBuilder() + .append("WinRMEndpoint [") + .append("endpoint=") + .append(endpoint) + .append(", domain=") + .append(domain) + .append(", username=") + .append(username) + .append(", namespace=") + .append(namespace) + .append("]") + .toString(); + } +} diff --git a/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java b/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java index 4690dd8..3639be6 100644 --- a/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java +++ b/src/main/java/org/metricshub/winrm/service/WinRMExecutorFactory.java @@ -4,7 +4,7 @@ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ * WinRM Java Client * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub + * Copyright (C) 2023 - 2026 MetricsHub * ჻჻჻჻჻჻ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java b/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java index 744616a..51b63e3 100644 --- a/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java +++ b/src/main/java/org/metricshub/winrm/service/client/auth/AuthenticationEnum.java @@ -1,46 +1,46 @@ -package org.metricshub.winrm.service.client.auth; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public enum AuthenticationEnum { - NTLM, - KERBEROS; - - private static final Map VALUES_OF = Stream - .of(values()) - .collect(Collectors.toMap(AuthenticationEnum::name, Function.identity())); - - /** - * Get the value of the enum Name regardless the cardinality. - * - * @param name The enum value name - * @return An optional with the enum value if found empty otherwise - */ - public static Optional getValueOf(final String name) { - return name != null ? Optional.ofNullable(VALUES_OF.get(name.trim().toUpperCase())) : Optional.empty(); - } -} +package org.metricshub.winrm.service.client.auth; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum AuthenticationEnum { + NTLM, + KERBEROS; + + private static final Map VALUES_OF = Stream + .of(values()) + .collect(Collectors.toMap(AuthenticationEnum::name, Function.identity())); + + /** + * Get the value of the enum Name regardless the cardinality. + * + * @param name The enum value name + * @return An optional with the enum value if found empty otherwise + */ + public static Optional getValueOf(final String name) { + return name != null ? Optional.ofNullable(VALUES_OF.get(name.trim().toUpperCase())) : Optional.empty(); + } +} diff --git a/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java b/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java index 21dd1f1..4327db5 100644 --- a/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java +++ b/src/main/java/org/metricshub/winrm/shares/SmbTempShare.java @@ -1,314 +1,314 @@ -package org.metricshub.winrm.shares; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2024 Metricshub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import com.hierynomus.security.bc.BCSecurityProvider; -import com.hierynomus.smbj.SMBClient; -import com.hierynomus.smbj.SmbConfig; -import com.hierynomus.smbj.auth.AuthenticationContext; -import com.hierynomus.smbj.connection.Connection; -import com.hierynomus.smbj.session.Session; -import com.hierynomus.smbj.share.DiskShare; -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -import org.metricshub.winrm.Utils; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.WindowsTempShare; -import org.metricshub.winrm.exceptions.WinRMException; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; - -public class SmbTempShare extends WindowsTempShare implements AutoCloseable { - - private final WinRMEndpoint winRMEndpoint; - private final SMBClient smbClient; - private final Connection connection; - private final Session session; - private final DiskShare diskShare; - - /** - * The SmbTempShare constructor. - * - * @param windowsRemoteExecutor WinRM executor (CXF or light backend) - * @param winRMEndpoint Endpoint with credentials - * @param smbClient The SMB client - * @param connection The SMB connection - * @param session The SMB session - * @param diskShare The SMB disk share - * @param shareNameOrUnc The name of the share, or its full UNC path - * @param remotePath The path on the remote system of the directory being shared - */ - private SmbTempShare( - final WindowsRemoteExecutor windowsRemoteExecutor, - final WinRMEndpoint winRMEndpoint, - final SMBClient smbClient, - final Connection connection, - final Session session, - final DiskShare diskShare, - final String shareNameOrUnc, - final String remotePath - ) { - super(windowsRemoteExecutor, shareNameOrUnc, remotePath); - this.winRMEndpoint = winRMEndpoint; - this.smbClient = smbClient; - this.connection = connection; - this.session = session; - this.diskShare = diskShare; - } - - private static final ConcurrentHashMap CONNECTIONS_CACHE = new ConcurrentHashMap<>(); - - private final AtomicInteger useCount = new AtomicInteger(1); - - /** - * Create a SmbTempShare instance. - * Get or create a temp share and connect to it with SMB. - * - * @param winRMEndpoint Endpoint with credentials (mandatory) - * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) - * @param ticketCache The Ticket Cache path - * @param authentications List of authentications. only NTLM if absent - * @return SmbTempShare instance - * @throws IOException If an I/O error occurred - * @throws WinRMException For any problem encountered - * @throws TimeoutException To notify userName of timeout. - */ - public static SmbTempShare createInstance( - final WinRMEndpoint winRMEndpoint, - final long timeout, - final Path ticketCache, - final List authentications - ) throws IOException, WinRMException, TimeoutException { - Utils.checkNonNull(winRMEndpoint, "winRMEndpoint"); - Utils.checkNonNull(winRMEndpoint.getPassword(), "password"); - Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); - - try { - return CONNECTIONS_CACHE.compute( - winRMEndpoint, - (key, smb) -> { - if (smb == null) { - WindowsRemoteExecutor windowsRemoteExecutor = null; - SMBClient smbClient = null; - Connection connection = null; - Session session = null; - DiskShare diskShare = null; - - try { - // Honour the backend toggle: SMB file transfer is always smbj, but the WinRM command - // orchestration follows the selected backend (so "light" does not fall back to CXF). - windowsRemoteExecutor = WinRMExecutorFactory - .createInstance(winRMEndpoint, timeout, ticketCache, authentications); - - final WindowsTempShare windowsTempShare = getOrCreateShare( - windowsRemoteExecutor, - timeout, - (w, r, s, t) -> { - try { - shareRemoteDirectory(w, r, s, t); - } catch (final TimeoutException | WindowsRemoteException e) { - throw new RuntimeException(e); - } - } - ); - - final SmbConfig smbConfig = SmbConfig - .builder() - .withSecurityProvider(new BCSecurityProvider()) - .withTimeout(timeout, TimeUnit.SECONDS) - .build(); - - final AuthenticationContext authenticationContext = new AuthenticationContext( - winRMEndpoint.getUsername(), - winRMEndpoint.getPassword(), - winRMEndpoint.getDomain() - ); - - smbClient = createSmbClient(smbConfig); - connection = smbClient.connect(winRMEndpoint.getHostname()); - session = connection.authenticate(authenticationContext); - diskShare = (DiskShare) session.connectShare(windowsTempShare.getShareName()); - - return new SmbTempShare( - windowsRemoteExecutor, - winRMEndpoint, - smbClient, - connection, - session, - diskShare, - windowsTempShare.getUncSharePath(), - windowsTempShare.getRemotePath() - ); - } catch (final RuntimeException e) { - closeResources(windowsRemoteExecutor, smbClient, connection, session, diskShare); - - throw e; - } catch (final Exception e) { - closeResources(windowsRemoteExecutor, smbClient, connection, session, diskShare); - - throw new RuntimeException(e); - } - } else { - synchronized (smb) { - smb.incrementUseCount(); - - return smb; - } - } - } - ); - } catch (final RuntimeException e) { - final Throwable cause = e.getCause(); - - if (cause instanceof IOException) { - throw (IOException) cause; - } - - if (cause instanceof TimeoutException) { - throw (TimeoutException) cause; - } - - if (cause instanceof WindowsRemoteException) { - throw (WinRMException) cause; - } - - throw e; - } - } - - private static void closeResources( - final WindowsRemoteExecutor windowsRemoteExecutor, - final SMBClient smbClient, - final Connection connection, - final Session session, - final DiskShare diskShare - ) { - try { - if (diskShare != null) { - diskShare.close(); - } - - if (session != null) { - session.close(); - } - - if (connection != null) { - connection.close(); - } - } catch (final IOException ioe) { - throw new RuntimeException(ioe); - } - - if (smbClient != null) { - smbClient.close(); - } - - if (windowsRemoteExecutor != null) { - windowsRemoteExecutor.close(); - } - } - - int getUseCount() { - return useCount.get(); - } - - void incrementUseCount() { - useCount.incrementAndGet(); - } - - /** - * @return whether this WbemServices instance is connected and usable - */ - boolean isConnected() { - return getUseCount() > 0; - } - - /** - * Check if it's connected. If not, throw an IllegalStateException. - */ - public void checkConnectedFirst() { - if (!isConnected()) { - throw new IllegalStateException("This instance has been closed and a new one must be created."); - } - } - - @Override - public synchronized void close() throws IOException { - if (useCount.decrementAndGet() == 0) { - CONNECTIONS_CACHE.remove(winRMEndpoint); - - if (diskShare != null) { - diskShare.close(); - } - - if (session != null) { - session.close(); - } - - if (connection != null) { - connection.close(); - } - - if (smbClient != null) { - smbClient.close(); - } - - getWindowsRemoteExecutor().close(); - } - } - - /** - * Share the remote directory on the host. - * - * @param windowsRemoteExecutor WinRM executor (CXF or light backend). - * @param remotePath The remote path. - * @param shareName The Share Name. - * @param timeout Timeout in milliseconds. - * @throws TimeoutException To notify userName of timeout. - * @throws WindowsRemoteException For any problem encountered - */ - private static void shareRemoteDirectory( - final WindowsRemoteExecutor windowsRemoteExecutor, - final String remotePath, - final String shareName, - final long timeout - ) throws TimeoutException, WindowsRemoteException { - final String command = String.format( - "net share %s=%s /grant:%s,Full", - shareName, - remotePath, - windowsRemoteExecutor.getUsername() - ); - - windowsRemoteExecutor.executeCommand(command, null, null, timeout); - } - - static SMBClient createSmbClient(final SmbConfig smbConfig) { - return new SMBClient(smbConfig); - } -} +package org.metricshub.winrm.shares; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import com.hierynomus.security.bc.BCSecurityProvider; +import com.hierynomus.smbj.SMBClient; +import com.hierynomus.smbj.SmbConfig; +import com.hierynomus.smbj.auth.AuthenticationContext; +import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.session.Session; +import com.hierynomus.smbj.share.DiskShare; +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.metricshub.winrm.Utils; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.WindowsTempShare; +import org.metricshub.winrm.exceptions.WinRMException; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; + +public class SmbTempShare extends WindowsTempShare implements AutoCloseable { + + private final WinRMEndpoint winRMEndpoint; + private final SMBClient smbClient; + private final Connection connection; + private final Session session; + private final DiskShare diskShare; + + /** + * The SmbTempShare constructor. + * + * @param windowsRemoteExecutor WinRM executor (CXF or light backend) + * @param winRMEndpoint Endpoint with credentials + * @param smbClient The SMB client + * @param connection The SMB connection + * @param session The SMB session + * @param diskShare The SMB disk share + * @param shareNameOrUnc The name of the share, or its full UNC path + * @param remotePath The path on the remote system of the directory being shared + */ + private SmbTempShare( + final WindowsRemoteExecutor windowsRemoteExecutor, + final WinRMEndpoint winRMEndpoint, + final SMBClient smbClient, + final Connection connection, + final Session session, + final DiskShare diskShare, + final String shareNameOrUnc, + final String remotePath + ) { + super(windowsRemoteExecutor, shareNameOrUnc, remotePath); + this.winRMEndpoint = winRMEndpoint; + this.smbClient = smbClient; + this.connection = connection; + this.session = session; + this.diskShare = diskShare; + } + + private static final ConcurrentHashMap CONNECTIONS_CACHE = new ConcurrentHashMap<>(); + + private final AtomicInteger useCount = new AtomicInteger(1); + + /** + * Create a SmbTempShare instance. + * Get or create a temp share and connect to it with SMB. + * + * @param winRMEndpoint Endpoint with credentials (mandatory) + * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero) + * @param ticketCache The Ticket Cache path + * @param authentications List of authentications. only NTLM if absent + * @return SmbTempShare instance + * @throws IOException If an I/O error occurred + * @throws WinRMException For any problem encountered + * @throws TimeoutException To notify userName of timeout. + */ + public static SmbTempShare createInstance( + final WinRMEndpoint winRMEndpoint, + final long timeout, + final Path ticketCache, + final List authentications + ) throws IOException, WinRMException, TimeoutException { + Utils.checkNonNull(winRMEndpoint, "winRMEndpoint"); + Utils.checkNonNull(winRMEndpoint.getPassword(), "password"); + Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); + + try { + return CONNECTIONS_CACHE.compute( + winRMEndpoint, + (key, smb) -> { + if (smb == null) { + WindowsRemoteExecutor windowsRemoteExecutor = null; + SMBClient smbClient = null; + Connection connection = null; + Session session = null; + DiskShare diskShare = null; + + try { + // Honour the backend toggle: SMB file transfer is always smbj, but the WinRM command + // orchestration follows the selected backend (so "light" does not fall back to CXF). + windowsRemoteExecutor = WinRMExecutorFactory + .createInstance(winRMEndpoint, timeout, ticketCache, authentications); + + final WindowsTempShare windowsTempShare = getOrCreateShare( + windowsRemoteExecutor, + timeout, + (w, r, s, t) -> { + try { + shareRemoteDirectory(w, r, s, t); + } catch (final TimeoutException | WindowsRemoteException e) { + throw new RuntimeException(e); + } + } + ); + + final SmbConfig smbConfig = SmbConfig + .builder() + .withSecurityProvider(new BCSecurityProvider()) + .withTimeout(timeout, TimeUnit.SECONDS) + .build(); + + final AuthenticationContext authenticationContext = new AuthenticationContext( + winRMEndpoint.getUsername(), + winRMEndpoint.getPassword(), + winRMEndpoint.getDomain() + ); + + smbClient = createSmbClient(smbConfig); + connection = smbClient.connect(winRMEndpoint.getHostname()); + session = connection.authenticate(authenticationContext); + diskShare = (DiskShare) session.connectShare(windowsTempShare.getShareName()); + + return new SmbTempShare( + windowsRemoteExecutor, + winRMEndpoint, + smbClient, + connection, + session, + diskShare, + windowsTempShare.getUncSharePath(), + windowsTempShare.getRemotePath() + ); + } catch (final RuntimeException e) { + closeResources(windowsRemoteExecutor, smbClient, connection, session, diskShare); + + throw e; + } catch (final Exception e) { + closeResources(windowsRemoteExecutor, smbClient, connection, session, diskShare); + + throw new RuntimeException(e); + } + } else { + synchronized (smb) { + smb.incrementUseCount(); + + return smb; + } + } + } + ); + } catch (final RuntimeException e) { + final Throwable cause = e.getCause(); + + if (cause instanceof IOException) { + throw (IOException) cause; + } + + if (cause instanceof TimeoutException) { + throw (TimeoutException) cause; + } + + if (cause instanceof WindowsRemoteException) { + throw (WinRMException) cause; + } + + throw e; + } + } + + private static void closeResources( + final WindowsRemoteExecutor windowsRemoteExecutor, + final SMBClient smbClient, + final Connection connection, + final Session session, + final DiskShare diskShare + ) { + try { + if (diskShare != null) { + diskShare.close(); + } + + if (session != null) { + session.close(); + } + + if (connection != null) { + connection.close(); + } + } catch (final IOException ioe) { + throw new RuntimeException(ioe); + } + + if (smbClient != null) { + smbClient.close(); + } + + if (windowsRemoteExecutor != null) { + windowsRemoteExecutor.close(); + } + } + + int getUseCount() { + return useCount.get(); + } + + void incrementUseCount() { + useCount.incrementAndGet(); + } + + /** + * @return whether this WbemServices instance is connected and usable + */ + boolean isConnected() { + return getUseCount() > 0; + } + + /** + * Check if it's connected. If not, throw an IllegalStateException. + */ + public void checkConnectedFirst() { + if (!isConnected()) { + throw new IllegalStateException("This instance has been closed and a new one must be created."); + } + } + + @Override + public synchronized void close() throws IOException { + if (useCount.decrementAndGet() == 0) { + CONNECTIONS_CACHE.remove(winRMEndpoint); + + if (diskShare != null) { + diskShare.close(); + } + + if (session != null) { + session.close(); + } + + if (connection != null) { + connection.close(); + } + + if (smbClient != null) { + smbClient.close(); + } + + getWindowsRemoteExecutor().close(); + } + } + + /** + * Share the remote directory on the host. + * + * @param windowsRemoteExecutor WinRM executor (CXF or light backend). + * @param remotePath The remote path. + * @param shareName The Share Name. + * @param timeout Timeout in milliseconds. + * @throws TimeoutException To notify userName of timeout. + * @throws WindowsRemoteException For any problem encountered + */ + private static void shareRemoteDirectory( + final WindowsRemoteExecutor windowsRemoteExecutor, + final String remotePath, + final String shareName, + final long timeout + ) throws TimeoutException, WindowsRemoteException { + final String command = String.format( + "net share %s=%s /grant:%s,Full", + shareName, + remotePath, + windowsRemoteExecutor.getUsername() + ); + + windowsRemoteExecutor.executeCommand(command, null, null, timeout); + } + + static SMBClient createSmbClient(final SmbConfig smbConfig) { + return new SMBClient(smbConfig); + } +} diff --git a/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java b/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java index 38211a7..f2f9896 100644 --- a/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java +++ b/src/main/java/org/metricshub/winrm/wql/WinRMWqlExecutor.java @@ -1,148 +1,148 @@ -package org.metricshub.winrm.wql; - -/*- - * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ - * WinRM Java Client - * ჻჻჻჻჻჻ - * Copyright 2023 - 2026 MetricsHub - * ჻჻჻჻჻჻ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ - */ - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; -import org.metricshub.winrm.Utils; -import org.metricshub.winrm.WinRMHttpProtocolEnum; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.WmiHelper; -import org.metricshub.winrm.exceptions.WinRMException; -import org.metricshub.winrm.exceptions.WindowsRemoteException; -import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; - -public class WinRMWqlExecutor { - - private final long executionTime; - private final List headers; - private final List> rows; - - /** - * The WinRMWqlExecutor constructor - * - * @param executionTime The execution time in milliseconds - * @param headers The headers list - * @param rows The value rows list - */ - public WinRMWqlExecutor(final long executionTime, final List headers, final List> rows) { - this.executionTime = executionTime; - this.headers = headers; - this.rows = rows; - } - - /** - * Get the execution time of the query. - * - * @return - */ - public long getExecutionTime() { - return executionTime; - } - - /** - * Get the headers of the query. - * - * @return - */ - public List getHeaders() { - return headers; - } - - /** - * Get the result rows of the query. - * - * @return - */ - public List> getRows() { - return rows; - } - - /** - * Execute a WQL query and process its result. - * - * @param protocol The HTTP protocol (HTTP by default) - * @param hostname Host to connect to. (Mandatory) - * @param port The port (5985 for HTPP or 5986 for HTTPS by default) - * @param username The username name. (Mandatory) - * @param password The password - * @param namespace The namespace default value: {@value WmiHelper#DEFAULT_NAMESPACE} - * @param wqlQuery The WQL query (Mandatory) - * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero) - * @param ticketCache The Ticket Cache path - * @param authentications List of authentications. only NTLM if absent - * @return WinRMWqlExecutor result instance with header, rows and execution time. - * @throws WinRMException For any problem encountered on remote - * @throws WqlQuerySyntaxException On WQL syntax errors - * @throws TimeoutException To notify userName of timeout - */ - public static WinRMWqlExecutor executeWql( - final WinRMHttpProtocolEnum protocol, - final String hostname, - final Integer port, - final String username, - final char[] password, - final String namespace, - final String wqlQuery, - final long timeout, - final Path ticketCache, - final List authentications - ) throws WinRMException, WqlQuerySyntaxException, TimeoutException { - Utils.checkNonNull(wqlQuery, "wqlQuery"); - Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); - - final long start = Utils.getCurrentTimeMillis(); - - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(protocol, hostname, port, username, password, namespace); - - try ( - final WindowsRemoteExecutor winRMService = WinRMExecutorFactory.createInstance( - winRMEndpoint, - timeout, - ticketCache, - authentications - )) { - final List> result = winRMService.executeWql(wqlQuery, timeout); - - // Extract the list of properties from the result, with same order as in the WQL query - final List headers = WmiHelper.extractPropertiesFromResult(result, wqlQuery); - - final List> rows = result - .stream() - .map(row -> headers.stream().map(header -> (String) row.get(header)).collect(Collectors.toList())) - .collect(Collectors.toList()); - - return new WinRMWqlExecutor(Utils.getCurrentTimeMillis() - start, headers, rows); - } catch (final WinRMException | WqlQuerySyntaxException | TimeoutException e) { - throw e; - } catch (final WindowsRemoteException e) { - // The WindowsRemoteExecutor interface declares the broader WindowsRemoteException; both - // backends actually throw WinRMException. Preserve the historical checked-exception surface. - throw new WinRMException(e, e.getMessage()); - } - } -} +package org.metricshub.winrm.wql; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * WinRM Java Client + * ჻჻჻჻჻჻ + * Copyright (C) 2023 - 2026 MetricsHub + * ჻჻჻჻჻჻ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import org.metricshub.winrm.Utils; +import org.metricshub.winrm.WinRMHttpProtocolEnum; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.WmiHelper; +import org.metricshub.winrm.exceptions.WinRMException; +import org.metricshub.winrm.exceptions.WindowsRemoteException; +import org.metricshub.winrm.exceptions.WqlQuerySyntaxException; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; + +public class WinRMWqlExecutor { + + private final long executionTime; + private final List headers; + private final List> rows; + + /** + * The WinRMWqlExecutor constructor + * + * @param executionTime The execution time in milliseconds + * @param headers The headers list + * @param rows The value rows list + */ + public WinRMWqlExecutor(final long executionTime, final List headers, final List> rows) { + this.executionTime = executionTime; + this.headers = headers; + this.rows = rows; + } + + /** + * Get the execution time of the query. + * + * @return + */ + public long getExecutionTime() { + return executionTime; + } + + /** + * Get the headers of the query. + * + * @return + */ + public List getHeaders() { + return headers; + } + + /** + * Get the result rows of the query. + * + * @return + */ + public List> getRows() { + return rows; + } + + /** + * Execute a WQL query and process its result. + * + * @param protocol The HTTP protocol (HTTP by default) + * @param hostname Host to connect to. (Mandatory) + * @param port The port (5985 for HTPP or 5986 for HTTPS by default) + * @param username The username name. (Mandatory) + * @param password The password + * @param namespace The namespace default value: {@value WmiHelper#DEFAULT_NAMESPACE} + * @param wqlQuery The WQL query (Mandatory) + * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero) + * @param ticketCache The Ticket Cache path + * @param authentications List of authentications. only NTLM if absent + * @return WinRMWqlExecutor result instance with header, rows and execution time. + * @throws WinRMException For any problem encountered on remote + * @throws WqlQuerySyntaxException On WQL syntax errors + * @throws TimeoutException To notify userName of timeout + */ + public static WinRMWqlExecutor executeWql( + final WinRMHttpProtocolEnum protocol, + final String hostname, + final Integer port, + final String username, + final char[] password, + final String namespace, + final String wqlQuery, + final long timeout, + final Path ticketCache, + final List authentications + ) throws WinRMException, WqlQuerySyntaxException, TimeoutException { + Utils.checkNonNull(wqlQuery, "wqlQuery"); + Utils.checkArgumentNotZeroOrNegative(timeout, "timeout"); + + final long start = Utils.getCurrentTimeMillis(); + + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(protocol, hostname, port, username, password, namespace); + + try ( + final WindowsRemoteExecutor winRMService = WinRMExecutorFactory.createInstance( + winRMEndpoint, + timeout, + ticketCache, + authentications + )) { + final List> result = winRMService.executeWql(wqlQuery, timeout); + + // Extract the list of properties from the result, with same order as in the WQL query + final List headers = WmiHelper.extractPropertiesFromResult(result, wqlQuery); + + final List> rows = result + .stream() + .map(row -> headers.stream().map(header -> (String) row.get(header)).collect(Collectors.toList())) + .collect(Collectors.toList()); + + return new WinRMWqlExecutor(Utils.getCurrentTimeMillis() - start, headers, rows); + } catch (final WinRMException | WqlQuerySyntaxException | TimeoutException e) { + throw e; + } catch (final WindowsRemoteException e) { + // The WindowsRemoteExecutor interface declares the broader WindowsRemoteException; both + // backends actually throw WinRMException. Preserve the historical checked-exception surface. + throw new WinRMException(e, e.getMessage()); + } + } +} diff --git a/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java b/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java index 5d657eb..be69dda 100644 --- a/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java +++ b/src/test/java/org/metricshub/winrm/command/WinRMCommandExecutorTest.java @@ -1,216 +1,216 @@ -package org.metricshub.winrm.command; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; -import static org.metricshub.winrm.WindowsRemoteProcessUtils.copyLocalFilesToShare; -import static org.metricshub.winrm.WindowsRemoteProcessUtils.getWindowsEncodingCharset; -import static org.metricshub.winrm.command.WinRMCommandExecutor.execute; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.metricshub.winrm.WindowsRemoteCommandResult; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.WindowsRemoteProcessUtils; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; -import org.metricshub.winrm.shares.SmbTempShare; -import org.mockito.MockedStatic; - -class WinRMCommandExecutorTest { - - @Test - void testExecute() throws Exception { - final String command = "launch"; - final String hostname = "host"; - final String username = "domain\\user"; - final char[] password = "pass".toCharArray(); - final String workingDirectory = " \t\r\n dir \t\r\n "; - final long timeout = 30 * 1000L; - final List localFileToCopyList = singletonList(" \r\t\n localFile \t\r\n "); - final WindowsRemoteCommandResult expected = new WindowsRemoteCommandResult("stdout", "stderr", 1.0f, 0); - final Path ticketCache = Paths.get("path"); - final List authentications = singletonList(NTLM); - - // check arguments - assertThrows( - IllegalArgumentException.class, - () -> execute( - null, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - assertThrows( - IllegalArgumentException.class, - () -> execute( - command, - HTTPS, - null, - 5986, - username, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - assertThrows( - IllegalArgumentException.class, - () -> execute( - command, - HTTPS, - hostname, - 5986, - null, - password, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - assertThrows( - IllegalArgumentException.class, - () -> execute( - command, - HTTPS, - hostname, - 5986, - username, - null, - workingDirectory, - timeout, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - assertThrows( - IllegalArgumentException.class, - () -> execute( - command, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - -1L, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - assertThrows( - IllegalArgumentException.class, - () -> execute( - command, - HTTPS, - hostname, - 5986, - username, - password, - workingDirectory, - 0L, - localFileToCopyList, - ticketCache, - authentications - ) - ); - - // case localFileToCopyList null or empty - try ( - final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( - WindowsRemoteProcessUtils.class - ); - final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class)) { - mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); - - final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); - - mockedFactory - .when(() -> WinRMExecutorFactory.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) - .thenReturn(executor); - - doReturn(expected).when(executor).executeCommand(eq(command), isNull(), eq(UTF_8), anyLong()); - - assertEquals( - expected, - execute(command, null, hostname, null, username, password, null, timeout, null, null, null) - ); - - assertEquals( - expected, - execute(command, null, hostname, null, username, password, null, timeout, emptyList(), null, null) - ); - } - - // Case with localFileToCopyList - try ( - final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( - WindowsRemoteProcessUtils.class - ); - final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class)) { - mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); - - mockedWindowsRemoteProcessUtils - .when(() -> copyLocalFilesToShare(anyString(), anyList(), anyString(), anyString())) - .thenReturn("launch remote/localFile"); - - final SmbTempShare smbTempShare = mock(SmbTempShare.class); - final WindowsRemoteExecutor winRMService = mock(WindowsRemoteExecutor.class); - - mockedSmbTempShare - .when(() -> SmbTempShare.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) - .thenReturn(smbTempShare); - - doNothing().when(smbTempShare).checkConnectedFirst(); - doReturn(winRMService).when(smbTempShare).getWindowsRemoteExecutor(); - doReturn("\\\\2001-db8--85b-3c51-f5ff-ffdb.ipv6-literal.net\\SEN_ShareFor_PC-TEST$") - .when(smbTempShare) - .getUncSharePath(); - doReturn("Windows\\Temp\\SEN_ShareFor_TEST$").when(smbTempShare).getRemotePath(); - - doReturn(expected).when(winRMService).executeCommand(anyString(), isNull(), eq(UTF_8), anyLong()); - - assertEquals( - expected, - execute(command, null, hostname, null, username, password, null, timeout, localFileToCopyList, null, null) - ); - } - } -} +package org.metricshub.winrm.command; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; +import static org.metricshub.winrm.WindowsRemoteProcessUtils.copyLocalFilesToShare; +import static org.metricshub.winrm.WindowsRemoteProcessUtils.getWindowsEncodingCharset; +import static org.metricshub.winrm.command.WinRMCommandExecutor.execute; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.metricshub.winrm.WindowsRemoteCommandResult; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.WindowsRemoteProcessUtils; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; +import org.metricshub.winrm.shares.SmbTempShare; +import org.mockito.MockedStatic; + +class WinRMCommandExecutorTest { + + @Test + void testExecute() throws Exception { + final String command = "launch"; + final String hostname = "host"; + final String username = "domain\\user"; + final char[] password = "pass".toCharArray(); + final String workingDirectory = " \t\r\n dir \t\r\n "; + final long timeout = 30 * 1000L; + final List localFileToCopyList = singletonList(" \r\t\n localFile \t\r\n "); + final WindowsRemoteCommandResult expected = new WindowsRemoteCommandResult("stdout", "stderr", 1.0f, 0); + final Path ticketCache = Paths.get("path"); + final List authentications = singletonList(NTLM); + + // check arguments + assertThrows( + IllegalArgumentException.class, + () -> execute( + null, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + assertThrows( + IllegalArgumentException.class, + () -> execute( + command, + HTTPS, + null, + 5986, + username, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + assertThrows( + IllegalArgumentException.class, + () -> execute( + command, + HTTPS, + hostname, + 5986, + null, + password, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + assertThrows( + IllegalArgumentException.class, + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + null, + workingDirectory, + timeout, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + assertThrows( + IllegalArgumentException.class, + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + -1L, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + assertThrows( + IllegalArgumentException.class, + () -> execute( + command, + HTTPS, + hostname, + 5986, + username, + password, + workingDirectory, + 0L, + localFileToCopyList, + ticketCache, + authentications + ) + ); + + // case localFileToCopyList null or empty + try ( + final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( + WindowsRemoteProcessUtils.class + ); + final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class)) { + mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); + + final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); + + mockedFactory + .when(() -> WinRMExecutorFactory.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) + .thenReturn(executor); + + doReturn(expected).when(executor).executeCommand(eq(command), isNull(), eq(UTF_8), anyLong()); + + assertEquals( + expected, + execute(command, null, hostname, null, username, password, null, timeout, null, null, null) + ); + + assertEquals( + expected, + execute(command, null, hostname, null, username, password, null, timeout, emptyList(), null, null) + ); + } + + // Case with localFileToCopyList + try ( + final MockedStatic mockedWindowsRemoteProcessUtils = mockStatic( + WindowsRemoteProcessUtils.class + ); + final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class)) { + mockedWindowsRemoteProcessUtils.when(() -> getWindowsEncodingCharset(any(), anyLong())).thenReturn(UTF_8); + + mockedWindowsRemoteProcessUtils + .when(() -> copyLocalFilesToShare(anyString(), anyList(), anyString(), anyString())) + .thenReturn("launch remote/localFile"); + + final SmbTempShare smbTempShare = mock(SmbTempShare.class); + final WindowsRemoteExecutor winRMService = mock(WindowsRemoteExecutor.class); + + mockedSmbTempShare + .when(() -> SmbTempShare.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) + .thenReturn(smbTempShare); + + doNothing().when(smbTempShare).checkConnectedFirst(); + doReturn(winRMService).when(smbTempShare).getWindowsRemoteExecutor(); + doReturn("\\\\2001-db8--85b-3c51-f5ff-ffdb.ipv6-literal.net\\SEN_ShareFor_PC-TEST$") + .when(smbTempShare) + .getUncSharePath(); + doReturn("Windows\\Temp\\SEN_ShareFor_TEST$").when(smbTempShare).getRemotePath(); + + doReturn(expected).when(winRMService).executeCommand(anyString(), isNull(), eq(UTF_8), anyLong()); + + assertEquals( + expected, + execute(command, null, hostname, null, username, password, null, timeout, localFileToCopyList, null, null) + ); + } + } +} diff --git a/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java b/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java index 5db2a3f..80d913e 100644 --- a/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java +++ b/src/test/java/org/metricshub/winrm/service/WinRMEndpointTest.java @@ -1,138 +1,138 @@ -package org.metricshub.winrm.service; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.metricshub.winrm.Utils.EMPTY; -import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTP; -import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; -import static org.metricshub.winrm.service.WinRMEndpoint.buildEndpointUrl; -import static org.metricshub.winrm.service.WinRMEndpoint.buildNamespace; -import static org.metricshub.winrm.service.WinRMEndpoint.getEndpointPort; - -import org.junit.jupiter.api.Test; - -class WinRMEndpointTest { - - private static final String HOSTNAME = "host"; - private static final int PORT = 443; - private static final String USER = "user"; - private static final char[] PASSWORD = { 'p', 'a', 's', 's' }; - - @Test - void testWinRMEndpoint() { - // check arguments - assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, null, 5985, USER, PASSWORD, null)); - - assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, HOSTNAME, 5985, null, PASSWORD, null)); - - assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, HOSTNAME, 5985, USER, null, null)); - - // Case user name in form of domain\\user - { - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint( - null, - " \t\r\n host \t\r\n ", - null, - " \t\r\n domain \t\r\n \\ \t\r\n user \t\r\n ", - PASSWORD, - null - ); - - assertEquals(HOSTNAME, winRMEndpoint.getHostname()); - assertEquals("http://host:5985/wsman", winRMEndpoint.getEndpoint()); - assertEquals("domain", winRMEndpoint.getDomain()); - assertEquals(USER, winRMEndpoint.getUsername()); - assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); - assertEquals("ROOT/CIMV2", winRMEndpoint.getNamespace()); - assertEquals(HTTP, winRMEndpoint.getProtocol()); - } - { - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTPS, HOSTNAME, PORT, "domain\\user", PASSWORD, "root"); - - assertEquals(HOSTNAME, winRMEndpoint.getHostname()); - assertEquals("https://host:443/wsman", winRMEndpoint.getEndpoint()); - assertEquals("domain", winRMEndpoint.getDomain()); - assertEquals(USER, winRMEndpoint.getUsername()); - assertEquals("root", winRMEndpoint.getNamespace()); - assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); - assertEquals(HTTPS, winRMEndpoint.getProtocol()); - } - - // Case user name without domain - { - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(null, HOSTNAME, null, USER, PASSWORD, "root/cimv2"); - - assertEquals(HOSTNAME, winRMEndpoint.getHostname()); - assertEquals("http://host:5985/wsman", winRMEndpoint.getEndpoint()); - assertNull(winRMEndpoint.getDomain()); - assertEquals(USER, winRMEndpoint.getUsername()); - assertEquals("root/cimv2", winRMEndpoint.getNamespace()); - assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); - assertEquals(HTTP, winRMEndpoint.getProtocol()); - } - { - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTP, HOSTNAME, PORT, USER, PASSWORD, "SRV\\Microsoft"); - - assertEquals(HOSTNAME, winRMEndpoint.getHostname()); - assertEquals("http://host:443/wsman", winRMEndpoint.getEndpoint()); - assertNull(winRMEndpoint.getDomain()); - assertEquals(USER, winRMEndpoint.getUsername()); - assertEquals("SRV/Microsoft", winRMEndpoint.getNamespace()); - assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); - assertEquals(HTTP, winRMEndpoint.getProtocol()); - } - } - - @Test - void testBuildEndpointUrl() { - assertEquals("HTTP://host:5985", buildEndpointUrl(HTTP, HOSTNAME, null)); - assertEquals("HTTPS://host:5986", buildEndpointUrl(HTTPS, HOSTNAME, null)); - assertEquals("HTTP://host:443", buildEndpointUrl(HTTP, HOSTNAME, PORT)); - assertEquals("HTTPS://host:443", buildEndpointUrl(HTTPS, HOSTNAME, PORT)); - } - - @Test - void testGetEndPointPort() { - // case port provided - assertEquals(PORT, getEndpointPort(null, PORT)); - assertEquals(PORT, getEndpointPort(HTTPS, PORT)); - assertEquals(PORT, getEndpointPort(HTTP, PORT)); - - assertEquals(5985, getEndpointPort(null, null)); - assertEquals(5985, getEndpointPort(HTTP, null)); - assertEquals(5986, getEndpointPort(HTTPS, null)); - } - - @Test - void testGetPort() { - assertEquals(5985, new WinRMEndpoint(HTTP, HOSTNAME, null, USER, PASSWORD, null).getPort()); - assertEquals(5986, new WinRMEndpoint(HTTPS, HOSTNAME, null, USER, PASSWORD, null).getPort()); - assertEquals(PORT, new WinRMEndpoint(HTTP, HOSTNAME, PORT, USER, PASSWORD, null).getPort()); - assertEquals(PORT, new WinRMEndpoint(HTTPS, HOSTNAME, PORT, USER, PASSWORD, null).getPort()); - } - - @Test - void testUnderscoreHostnameEndpoint() { - // Regression guard for the light backend default: WinRMEndpoint accepts NetBIOS-style names with - // underscores and exposes a usable hostname/port, whereas java.net.URI cannot classify such a host - // (URI.create(endpoint).getHost() is null). The light backend must rely on these accessors. - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTP, "server_name", 5999, USER, PASSWORD, null); - assertEquals("server_name", winRMEndpoint.getHostname()); - assertEquals(5999, winRMEndpoint.getPort()); - assertNull(java.net.URI.create(winRMEndpoint.getEndpoint()).getHost()); - } - - @Test - void testBuildNamespace() { - assertEquals("ROOT/CIMV2", buildNamespace(null)); - assertEquals("ROOT/CIMV2", buildNamespace(EMPTY)); - assertEquals("ROOT/CIMV2", buildNamespace(" \t\r\n ")); - - // case namespace provided - assertEquals("root", buildNamespace("root")); - assertEquals("root/cimv2", buildNamespace(" \t\r\n root \t\r\n / \t\r\n cimv2 \t\r\n ")); - assertEquals("Srv/StandardCimv2", buildNamespace("Srv\\StandardCimv2")); - } -} +package org.metricshub.winrm.service; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.metricshub.winrm.Utils.EMPTY; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTP; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; +import static org.metricshub.winrm.service.WinRMEndpoint.buildEndpointUrl; +import static org.metricshub.winrm.service.WinRMEndpoint.buildNamespace; +import static org.metricshub.winrm.service.WinRMEndpoint.getEndpointPort; + +import org.junit.jupiter.api.Test; + +class WinRMEndpointTest { + + private static final String HOSTNAME = "host"; + private static final int PORT = 443; + private static final String USER = "user"; + private static final char[] PASSWORD = { 'p', 'a', 's', 's' }; + + @Test + void testWinRMEndpoint() { + // check arguments + assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, null, 5985, USER, PASSWORD, null)); + + assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, HOSTNAME, 5985, null, PASSWORD, null)); + + assertThrows(IllegalArgumentException.class, () -> new WinRMEndpoint(HTTP, HOSTNAME, 5985, USER, null, null)); + + // Case user name in form of domain\\user + { + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint( + null, + " \t\r\n host \t\r\n ", + null, + " \t\r\n domain \t\r\n \\ \t\r\n user \t\r\n ", + PASSWORD, + null + ); + + assertEquals(HOSTNAME, winRMEndpoint.getHostname()); + assertEquals("http://host:5985/wsman", winRMEndpoint.getEndpoint()); + assertEquals("domain", winRMEndpoint.getDomain()); + assertEquals(USER, winRMEndpoint.getUsername()); + assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); + assertEquals("ROOT/CIMV2", winRMEndpoint.getNamespace()); + assertEquals(HTTP, winRMEndpoint.getProtocol()); + } + { + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTPS, HOSTNAME, PORT, "domain\\user", PASSWORD, "root"); + + assertEquals(HOSTNAME, winRMEndpoint.getHostname()); + assertEquals("https://host:443/wsman", winRMEndpoint.getEndpoint()); + assertEquals("domain", winRMEndpoint.getDomain()); + assertEquals(USER, winRMEndpoint.getUsername()); + assertEquals("root", winRMEndpoint.getNamespace()); + assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); + assertEquals(HTTPS, winRMEndpoint.getProtocol()); + } + + // Case user name without domain + { + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(null, HOSTNAME, null, USER, PASSWORD, "root/cimv2"); + + assertEquals(HOSTNAME, winRMEndpoint.getHostname()); + assertEquals("http://host:5985/wsman", winRMEndpoint.getEndpoint()); + assertNull(winRMEndpoint.getDomain()); + assertEquals(USER, winRMEndpoint.getUsername()); + assertEquals("root/cimv2", winRMEndpoint.getNamespace()); + assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); + assertEquals(HTTP, winRMEndpoint.getProtocol()); + } + { + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTP, HOSTNAME, PORT, USER, PASSWORD, "SRV\\Microsoft"); + + assertEquals(HOSTNAME, winRMEndpoint.getHostname()); + assertEquals("http://host:443/wsman", winRMEndpoint.getEndpoint()); + assertNull(winRMEndpoint.getDomain()); + assertEquals(USER, winRMEndpoint.getUsername()); + assertEquals("SRV/Microsoft", winRMEndpoint.getNamespace()); + assertArrayEquals(PASSWORD, winRMEndpoint.getPassword()); + assertEquals(HTTP, winRMEndpoint.getProtocol()); + } + } + + @Test + void testBuildEndpointUrl() { + assertEquals("HTTP://host:5985", buildEndpointUrl(HTTP, HOSTNAME, null)); + assertEquals("HTTPS://host:5986", buildEndpointUrl(HTTPS, HOSTNAME, null)); + assertEquals("HTTP://host:443", buildEndpointUrl(HTTP, HOSTNAME, PORT)); + assertEquals("HTTPS://host:443", buildEndpointUrl(HTTPS, HOSTNAME, PORT)); + } + + @Test + void testGetEndPointPort() { + // case port provided + assertEquals(PORT, getEndpointPort(null, PORT)); + assertEquals(PORT, getEndpointPort(HTTPS, PORT)); + assertEquals(PORT, getEndpointPort(HTTP, PORT)); + + assertEquals(5985, getEndpointPort(null, null)); + assertEquals(5985, getEndpointPort(HTTP, null)); + assertEquals(5986, getEndpointPort(HTTPS, null)); + } + + @Test + void testGetPort() { + assertEquals(5985, new WinRMEndpoint(HTTP, HOSTNAME, null, USER, PASSWORD, null).getPort()); + assertEquals(5986, new WinRMEndpoint(HTTPS, HOSTNAME, null, USER, PASSWORD, null).getPort()); + assertEquals(PORT, new WinRMEndpoint(HTTP, HOSTNAME, PORT, USER, PASSWORD, null).getPort()); + assertEquals(PORT, new WinRMEndpoint(HTTPS, HOSTNAME, PORT, USER, PASSWORD, null).getPort()); + } + + @Test + void testUnderscoreHostnameEndpoint() { + // Regression guard for the light backend default: WinRMEndpoint accepts NetBIOS-style names with + // underscores and exposes a usable hostname/port, whereas java.net.URI cannot classify such a host + // (URI.create(endpoint).getHost() is null). The light backend must rely on these accessors. + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(HTTP, "server_name", 5999, USER, PASSWORD, null); + assertEquals("server_name", winRMEndpoint.getHostname()); + assertEquals(5999, winRMEndpoint.getPort()); + assertNull(java.net.URI.create(winRMEndpoint.getEndpoint()).getHost()); + } + + @Test + void testBuildNamespace() { + assertEquals("ROOT/CIMV2", buildNamespace(null)); + assertEquals("ROOT/CIMV2", buildNamespace(EMPTY)); + assertEquals("ROOT/CIMV2", buildNamespace(" \t\r\n ")); + + // case namespace provided + assertEquals("root", buildNamespace("root")); + assertEquals("root/cimv2", buildNamespace(" \t\r\n root \t\r\n / \t\r\n cimv2 \t\r\n ")); + assertEquals("Srv/StandardCimv2", buildNamespace("Srv\\StandardCimv2")); + } +} diff --git a/src/test/java/org/metricshub/winrm/service/client/auth/AuthenticationEnumTest.java b/src/test/java/org/metricshub/winrm/service/client/auth/AuthenticationEnumTest.java index 127dae2..8dddcaa 100644 --- a/src/test/java/org/metricshub/winrm/service/client/auth/AuthenticationEnumTest.java +++ b/src/test/java/org/metricshub/winrm/service/client/auth/AuthenticationEnumTest.java @@ -1,27 +1,27 @@ -package org.metricshub.winrm.service.client.auth; - -import static java.util.Optional.empty; -import static java.util.Optional.of; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.metricshub.winrm.Utils.EMPTY; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.KERBEROS; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.getValueOf; - -import org.junit.jupiter.api.Test; - -class AuthenticationEnumTest { - - @Test - void testGetValueOf() { - assertEquals(empty(), getValueOf(null)); - assertEquals(empty(), getValueOf(EMPTY)); - assertEquals(empty(), getValueOf("unknown")); - assertEquals(of(NTLM), getValueOf(" ntlm ")); - assertEquals(of(NTLM), getValueOf(" Ntlm ")); - assertEquals(of(NTLM), getValueOf(" NTLM ")); - assertEquals(of(KERBEROS), getValueOf(" kerberos ")); - assertEquals(of(KERBEROS), getValueOf(" Kerberos ")); - assertEquals(of(KERBEROS), getValueOf(" KERBEROS ")); - } -} +package org.metricshub.winrm.service.client.auth; + +import static java.util.Optional.empty; +import static java.util.Optional.of; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.metricshub.winrm.Utils.EMPTY; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.KERBEROS; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.getValueOf; + +import org.junit.jupiter.api.Test; + +class AuthenticationEnumTest { + + @Test + void testGetValueOf() { + assertEquals(empty(), getValueOf(null)); + assertEquals(empty(), getValueOf(EMPTY)); + assertEquals(empty(), getValueOf("unknown")); + assertEquals(of(NTLM), getValueOf(" ntlm ")); + assertEquals(of(NTLM), getValueOf(" Ntlm ")); + assertEquals(of(NTLM), getValueOf(" NTLM ")); + assertEquals(of(KERBEROS), getValueOf(" kerberos ")); + assertEquals(of(KERBEROS), getValueOf(" Kerberos ")); + assertEquals(of(KERBEROS), getValueOf(" KERBEROS ")); + } +} diff --git a/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java b/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java index f807b5f..f27b481 100644 --- a/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java +++ b/src/test/java/org/metricshub/winrm/shares/SmbTempShareTest.java @@ -1,139 +1,139 @@ -package org.metricshub.winrm.shares; - -import static java.nio.file.Paths.get; -import static java.util.Collections.singletonList; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.metricshub.winrm.WindowsTempShare.getOrCreateShare; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; -import static org.metricshub.winrm.shares.SmbTempShare.createInstance; -import static org.metricshub.winrm.shares.SmbTempShare.createSmbClient; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; - -import com.hierynomus.security.bc.BCSecurityProvider; -import com.hierynomus.smbj.SMBClient; -import com.hierynomus.smbj.SmbConfig; -import com.hierynomus.smbj.SmbConfig.Builder; -import com.hierynomus.smbj.auth.AuthenticationContext; -import com.hierynomus.smbj.connection.Connection; -import com.hierynomus.smbj.session.Session; -import com.hierynomus.smbj.share.DiskShare; -import java.nio.file.Path; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.metricshub.winrm.ShareRemoteDirectoryConsumer; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.WindowsTempShare; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; -import org.mockito.MockedStatic; - -class SmbTempShareTest { - - @SuppressWarnings("unchecked") - @Test - void testCreateInstance() throws Exception { - final String hostname = "host"; - final String username = "user"; - final char[] password = "pwd".toCharArray(); - final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(null, hostname, null, "domain\\" + username, password, null); - final long timeout = 30 * 1000L; - final Path ticketCache = get("path"); - final List authentications = singletonList(NTLM); - - // check arguments - assertThrows(IllegalArgumentException.class, () -> createInstance(null, timeout, ticketCache, authentications)); - - assertThrows( - IllegalArgumentException.class, - () -> createInstance(winRMEndpoint, -1L, ticketCache, authentications) - ); - - assertThrows(IllegalArgumentException.class, () -> createInstance(winRMEndpoint, 0L, ticketCache, authentications)); - - try ( - final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class); - final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class); - final MockedStatic mockedWindowsTempShare = mockStatic(WindowsTempShare.class); - final MockedStatic mockedSmbConfig = mockStatic(SmbConfig.class)) { - final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); - mockedFactory - .when(() -> WinRMExecutorFactory.createInstance(winRMEndpoint, timeout, null, null)) - .thenReturn(executor); - - final WindowsTempShare windowsTempShare = mock(WindowsTempShare.class); - mockedWindowsTempShare - .when(() -> getOrCreateShare(eq(executor), anyLong(), any(ShareRemoteDirectoryConsumer.class))) - .thenReturn(windowsTempShare); - doReturn("\\\\2001-db8--85b-3c51-f5ff-ffdb.ipv6-literal.net\\SEN_ShareFor_PC-TEST$") - .when(windowsTempShare) - .getUncSharePath(); - doReturn("Windows\\Temp\\SEN_ShareFor_TEST$").when(windowsTempShare).getRemotePath(); - doReturn("SEN_ShareFor_PC-TEST$").when(windowsTempShare).getShareName(); - - final Builder smbConfigBuilder = mock(Builder.class); - mockedSmbConfig.when(SmbConfig::builder).thenReturn(smbConfigBuilder); - - doReturn(smbConfigBuilder).when(smbConfigBuilder).withSecurityProvider(any(BCSecurityProvider.class)); - - doReturn(smbConfigBuilder).when(smbConfigBuilder).withTimeout(anyLong(), eq(SECONDS)); - - final SmbConfig smbConfig = mock(SmbConfig.class); - doReturn(smbConfig).when(smbConfigBuilder).build(); - - final SMBClient smbClient = mock(SMBClient.class); - mockedSmbTempShare.when(() -> createSmbClient(smbConfig)).thenReturn(smbClient); - - final Connection connection = mock(Connection.class); - doReturn(connection).when(smbClient).connect(anyString()); - - final Session session = mock(Session.class); - doReturn(session).when(connection).authenticate(any(AuthenticationContext.class)); - - final DiskShare diskShare = mock(DiskShare.class); - doReturn(diskShare).when(session).connectShare(anyString()); - - mockedSmbTempShare - .when(() -> createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) - .thenCallRealMethod(); - - final SmbTempShare smbTempShare1 = createInstance(winRMEndpoint, timeout, null, null); - assertNotNull(smbTempShare1); - assertEquals(1, smbTempShare1.getUseCount()); - assertEquals(executor, smbTempShare1.getWindowsRemoteExecutor()); - assertTrue(smbTempShare1.isConnected()); - - final SmbTempShare smbTempShare2 = createInstance(winRMEndpoint, timeout, null, null); - assertNotNull(smbTempShare2); - assertEquals(2, smbTempShare1.getUseCount()); - assertEquals(2, smbTempShare2.getUseCount()); - assertEquals(executor, smbTempShare2.getWindowsRemoteExecutor()); - assertTrue(smbTempShare1.isConnected()); - assertTrue(smbTempShare2.isConnected()); - - smbTempShare1.close(); - assertTrue(smbTempShare1.isConnected()); - assertTrue(smbTempShare2.isConnected()); - assertEquals(1, smbTempShare1.getUseCount()); - assertEquals(1, smbTempShare2.getUseCount()); - - smbTempShare2.close(); - assertFalse(smbTempShare1.isConnected()); - assertFalse(smbTempShare2.isConnected()); - assertEquals(0, smbTempShare1.getUseCount()); - assertEquals(0, smbTempShare2.getUseCount()); - } - } -} +package org.metricshub.winrm.shares; + +import static java.nio.file.Paths.get; +import static java.util.Collections.singletonList; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.metricshub.winrm.WindowsTempShare.getOrCreateShare; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; +import static org.metricshub.winrm.shares.SmbTempShare.createInstance; +import static org.metricshub.winrm.shares.SmbTempShare.createSmbClient; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import com.hierynomus.security.bc.BCSecurityProvider; +import com.hierynomus.smbj.SMBClient; +import com.hierynomus.smbj.SmbConfig; +import com.hierynomus.smbj.SmbConfig.Builder; +import com.hierynomus.smbj.auth.AuthenticationContext; +import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.session.Session; +import com.hierynomus.smbj.share.DiskShare; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.metricshub.winrm.ShareRemoteDirectoryConsumer; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.WindowsTempShare; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; +import org.mockito.MockedStatic; + +class SmbTempShareTest { + + @SuppressWarnings("unchecked") + @Test + void testCreateInstance() throws Exception { + final String hostname = "host"; + final String username = "user"; + final char[] password = "pwd".toCharArray(); + final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(null, hostname, null, "domain\\" + username, password, null); + final long timeout = 30 * 1000L; + final Path ticketCache = get("path"); + final List authentications = singletonList(NTLM); + + // check arguments + assertThrows(IllegalArgumentException.class, () -> createInstance(null, timeout, ticketCache, authentications)); + + assertThrows( + IllegalArgumentException.class, + () -> createInstance(winRMEndpoint, -1L, ticketCache, authentications) + ); + + assertThrows(IllegalArgumentException.class, () -> createInstance(winRMEndpoint, 0L, ticketCache, authentications)); + + try ( + final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class); + final MockedStatic mockedSmbTempShare = mockStatic(SmbTempShare.class); + final MockedStatic mockedWindowsTempShare = mockStatic(WindowsTempShare.class); + final MockedStatic mockedSmbConfig = mockStatic(SmbConfig.class)) { + final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); + mockedFactory + .when(() -> WinRMExecutorFactory.createInstance(winRMEndpoint, timeout, null, null)) + .thenReturn(executor); + + final WindowsTempShare windowsTempShare = mock(WindowsTempShare.class); + mockedWindowsTempShare + .when(() -> getOrCreateShare(eq(executor), anyLong(), any(ShareRemoteDirectoryConsumer.class))) + .thenReturn(windowsTempShare); + doReturn("\\\\2001-db8--85b-3c51-f5ff-ffdb.ipv6-literal.net\\SEN_ShareFor_PC-TEST$") + .when(windowsTempShare) + .getUncSharePath(); + doReturn("Windows\\Temp\\SEN_ShareFor_TEST$").when(windowsTempShare).getRemotePath(); + doReturn("SEN_ShareFor_PC-TEST$").when(windowsTempShare).getShareName(); + + final Builder smbConfigBuilder = mock(Builder.class); + mockedSmbConfig.when(SmbConfig::builder).thenReturn(smbConfigBuilder); + + doReturn(smbConfigBuilder).when(smbConfigBuilder).withSecurityProvider(any(BCSecurityProvider.class)); + + doReturn(smbConfigBuilder).when(smbConfigBuilder).withTimeout(anyLong(), eq(SECONDS)); + + final SmbConfig smbConfig = mock(SmbConfig.class); + doReturn(smbConfig).when(smbConfigBuilder).build(); + + final SMBClient smbClient = mock(SMBClient.class); + mockedSmbTempShare.when(() -> createSmbClient(smbConfig)).thenReturn(smbClient); + + final Connection connection = mock(Connection.class); + doReturn(connection).when(smbClient).connect(anyString()); + + final Session session = mock(Session.class); + doReturn(session).when(connection).authenticate(any(AuthenticationContext.class)); + + final DiskShare diskShare = mock(DiskShare.class); + doReturn(diskShare).when(session).connectShare(anyString()); + + mockedSmbTempShare + .when(() -> createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) + .thenCallRealMethod(); + + final SmbTempShare smbTempShare1 = createInstance(winRMEndpoint, timeout, null, null); + assertNotNull(smbTempShare1); + assertEquals(1, smbTempShare1.getUseCount()); + assertEquals(executor, smbTempShare1.getWindowsRemoteExecutor()); + assertTrue(smbTempShare1.isConnected()); + + final SmbTempShare smbTempShare2 = createInstance(winRMEndpoint, timeout, null, null); + assertNotNull(smbTempShare2); + assertEquals(2, smbTempShare1.getUseCount()); + assertEquals(2, smbTempShare2.getUseCount()); + assertEquals(executor, smbTempShare2.getWindowsRemoteExecutor()); + assertTrue(smbTempShare1.isConnected()); + assertTrue(smbTempShare2.isConnected()); + + smbTempShare1.close(); + assertTrue(smbTempShare1.isConnected()); + assertTrue(smbTempShare2.isConnected()); + assertEquals(1, smbTempShare1.getUseCount()); + assertEquals(1, smbTempShare2.getUseCount()); + + smbTempShare2.close(); + assertFalse(smbTempShare1.isConnected()); + assertFalse(smbTempShare2.isConnected()); + assertEquals(0, smbTempShare1.getUseCount()); + assertEquals(0, smbTempShare2.getUseCount()); + } + } +} diff --git a/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java b/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java index 5090ddd..7aa6afd 100644 --- a/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java +++ b/src/test/java/org/metricshub/winrm/wql/WinRMWqlExecutorTest.java @@ -1,120 +1,120 @@ -package org.metricshub.winrm.wql; - -import static java.nio.file.Paths.get; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; -import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; -import static org.metricshub.winrm.wql.WinRMWqlExecutor.executeWql; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; -import org.metricshub.winrm.WindowsRemoteExecutor; -import org.metricshub.winrm.service.WinRMEndpoint; -import org.metricshub.winrm.service.WinRMExecutorFactory; -import org.metricshub.winrm.service.client.auth.AuthenticationEnum; -import org.mockito.MockedStatic; - -class WinRMWqlExecutorTest { - - @Test - void testExecute() throws Exception { - final String wqlQuery = "Select Name,Path from Win32_Share"; - final String hostname = "host"; - final String username = "user"; - final char[] password = "pass".toCharArray(); - final long timeout = 30 * 1000L; - final Path ticketCache = get("path"); - final List authentications = singletonList(NTLM); - - // check arguments - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, null, 5986, username, password, null, wqlQuery, timeout, ticketCache, authentications) - ); - - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, hostname, 5986, null, password, null, wqlQuery, timeout, ticketCache, authentications) - ); - - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, hostname, 5986, username, null, null, wqlQuery, timeout, ticketCache, authentications) - ); - - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, hostname, 5986, username, password, null, null, timeout, ticketCache, authentications) - ); - - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, hostname, 5986, username, password, null, wqlQuery, -1L, ticketCache, authentications) - ); - - assertThrows( - IllegalArgumentException.class, - () -> executeWql(HTTPS, hostname, 5986, username, password, null, wqlQuery, 0L, ticketCache, authentications) - ); - - try (final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class)) { - final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); - - final List> result = new ArrayList<>(); - { - final Map row = new HashMap<>(); - row.put("Name", "C$"); - row.put("Path", "C:\\"); - result.add(row); - } - { - final Map row = new HashMap<>(); - row.put("Name", "SEN_ShareFor_PC$"); - row.put("Path", "C:\\Windows\\Temp\\SEN_ShareFor_PC$"); - result.add(row); - } - - mockedFactory - .when(() -> WinRMExecutorFactory.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) - .thenReturn(executor); - - doReturn(result).when(executor).executeWql(eq(wqlQuery), anyLong()); - - final WinRMWqlExecutor actual = executeWql( - null, - hostname, - null, - username, - password, - null, - wqlQuery, - timeout, - null, - null - ); - - assertEquals(asList("Name", "Path"), actual.getHeaders()); - - final List> rows = asList( - asList("C$", "C:\\"), - asList("SEN_ShareFor_PC$", "C:\\Windows\\Temp\\SEN_ShareFor_PC$") - ); - - assertEquals(rows, actual.getRows()); - } - } -} +package org.metricshub.winrm.wql; + +import static java.nio.file.Paths.get; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.metricshub.winrm.WinRMHttpProtocolEnum.HTTPS; +import static org.metricshub.winrm.service.client.auth.AuthenticationEnum.NTLM; +import static org.metricshub.winrm.wql.WinRMWqlExecutor.executeWql; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.metricshub.winrm.WindowsRemoteExecutor; +import org.metricshub.winrm.service.WinRMEndpoint; +import org.metricshub.winrm.service.WinRMExecutorFactory; +import org.metricshub.winrm.service.client.auth.AuthenticationEnum; +import org.mockito.MockedStatic; + +class WinRMWqlExecutorTest { + + @Test + void testExecute() throws Exception { + final String wqlQuery = "Select Name,Path from Win32_Share"; + final String hostname = "host"; + final String username = "user"; + final char[] password = "pass".toCharArray(); + final long timeout = 30 * 1000L; + final Path ticketCache = get("path"); + final List authentications = singletonList(NTLM); + + // check arguments + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, null, 5986, username, password, null, wqlQuery, timeout, ticketCache, authentications) + ); + + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, hostname, 5986, null, password, null, wqlQuery, timeout, ticketCache, authentications) + ); + + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, hostname, 5986, username, null, null, wqlQuery, timeout, ticketCache, authentications) + ); + + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, hostname, 5986, username, password, null, null, timeout, ticketCache, authentications) + ); + + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, hostname, 5986, username, password, null, wqlQuery, -1L, ticketCache, authentications) + ); + + assertThrows( + IllegalArgumentException.class, + () -> executeWql(HTTPS, hostname, 5986, username, password, null, wqlQuery, 0L, ticketCache, authentications) + ); + + try (final MockedStatic mockedFactory = mockStatic(WinRMExecutorFactory.class)) { + final WindowsRemoteExecutor executor = mock(WindowsRemoteExecutor.class); + + final List> result = new ArrayList<>(); + { + final Map row = new HashMap<>(); + row.put("Name", "C$"); + row.put("Path", "C:\\"); + result.add(row); + } + { + final Map row = new HashMap<>(); + row.put("Name", "SEN_ShareFor_PC$"); + row.put("Path", "C:\\Windows\\Temp\\SEN_ShareFor_PC$"); + result.add(row); + } + + mockedFactory + .when(() -> WinRMExecutorFactory.createInstance(any(WinRMEndpoint.class), anyLong(), isNull(), isNull())) + .thenReturn(executor); + + doReturn(result).when(executor).executeWql(eq(wqlQuery), anyLong()); + + final WinRMWqlExecutor actual = executeWql( + null, + hostname, + null, + username, + password, + null, + wqlQuery, + timeout, + null, + null + ); + + assertEquals(asList("Name", "Path"), actual.getHeaders()); + + final List> rows = asList( + asList("C$", "C:\\"), + asList("SEN_ShareFor_PC$", "C:\\Windows\\Temp\\SEN_ShareFor_PC$") + ); + + assertEquals(rows, actual.getRows()); + } + } +}