View Javadoc
1   package org.metricshub.winrm.command;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Metricshub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import java.io.IOException;
24  import java.nio.charset.Charset;
25  import java.nio.file.Path;
26  import java.util.List;
27  import java.util.concurrent.TimeoutException;
28  import java.util.stream.Collectors;
29  import org.metricshub.winrm.TimeoutHelper;
30  import org.metricshub.winrm.Utils;
31  import org.metricshub.winrm.WinRMHttpProtocolEnum;
32  import org.metricshub.winrm.WindowsRemoteCommandResult;
33  import org.metricshub.winrm.WindowsRemoteProcessUtils;
34  import org.metricshub.winrm.exceptions.WindowsRemoteException;
35  import org.metricshub.winrm.exceptions.WqlQuerySyntaxException;
36  import org.metricshub.winrm.service.WinRMEndpoint;
37  import org.metricshub.winrm.service.WinRMService;
38  import org.metricshub.winrm.service.client.auth.AuthenticationEnum;
39  import org.metricshub.winrm.shares.SmbTempShare;
40  
41  public class WinRMCommandExecutor {
42  
43  	private WinRMCommandExecutor() {}
44  
45  	/**
46  	 * Execute a command on a remote Windows system and return an object with
47  	 * the output of the command.
48  	 *
49  	 * You can specify local files to be copied to the remote system before executing the command.
50  	 * If the command contains references to these local files, it will be updated to reference the
51  	 * path on the remote system where the files have been copied.
52  	 *
53  	 * Example:
54  	 *
55  	 * <code>
56  	 * 		WinRemoteCommandExecutor.execute(
57  	 * 		"CSCRIPT c:\\MyScript.vbs", null, "remote-srv", null, null, null, 30000, Arrays.asList("c:\\MyScript.vbs"), false);
58  	 * </code>
59  	 *
60  	 * This will copy <b>c:\\MyScript.vbs</b> to <b>remote-srv</b>, typically in
61  	 * <b>C:\\Windows\\Temp\\SEN_ShareFor_MYHOST</b> and the command that is executed will therefore
62  	 * become:
63  	 *
64  	 * <code>CSCRIPT "C:\\Windows\\Temp\\SEN_ShareFor_MYHOST\\MyScript.vbs"</code>
65  	 *
66  	 * @param command The command to execute. (Mandatory)
67  	 * @param protocol The HTTP protocol (HTTP by default)
68  	 * @param hostname Host to connect to. (Mandatory)
69  	 * @param port The port (5985 for HTPP or 5986 for HTTPS by default)
70  	 * @param username The username name. (Mandatory)
71  	 * @param password The password.
72  	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
73  	 * @param timeout The timeout in milliseconds (throws an IllegalArgumentException if negative or zero)
74  	 * @param localFileToCopyList List of local files to copy to the remote before the execution
75  	 * @param ticketCache The Ticket Cache path
76  	 * @param authentications List of authentications. only NTLM if absent
77  	 *
78  	 * @return an instance of WindowsRemoteCommandResult with the result of the command
79  	 *
80  	 * @throws IOException If an I/O error occurs.
81  	 * @throws TimeoutException To notify userName of timeout
82  	 * @throws WindowsRemoteException For any problem encountered on remote
83  	 */
84  	public static WindowsRemoteCommandResult execute(
85  		final String command,
86  		final WinRMHttpProtocolEnum protocol,
87  		final String hostname,
88  		final Integer port,
89  		final String username,
90  		final char[] password,
91  		final String workingDirectory,
92  		final long timeout,
93  		final List<String> localFileToCopyList,
94  		final Path ticketCache,
95  		final List<AuthenticationEnum> authentications
96  	) throws IOException, TimeoutException, WindowsRemoteException {
97  		Utils.checkNonNull(command, "command");
98  		Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
99  
100 		final long start = System.currentTimeMillis();
101 
102 		final WinRMEndpoint winRMEndpoint = new WinRMEndpoint(protocol, hostname, port, username, password, null);
103 
104 		if (localFileToCopyList == null || localFileToCopyList.isEmpty()) {
105 			try (
106 				final WinRMService winRMService = WinRMService.createInstance(
107 					winRMEndpoint,
108 					timeout,
109 					ticketCache,
110 					authentications
111 				)
112 			) {
113 				final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset(
114 					winRMService,
115 					TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set")
116 				);
117 
118 				return winRMService.executeCommand(command, workingDirectory, charset, timeout);
119 			} catch (final WqlQuerySyntaxException e) {
120 				throw new IOException(e);
121 			}
122 		}
123 
124 		try (
125 			final SmbTempShare smbTempShare = SmbTempShare.createInstance(
126 				winRMEndpoint,
127 				timeout,
128 				ticketCache,
129 				authentications
130 			)
131 		) {
132 			smbTempShare.checkConnectedFirst();
133 
134 			final List<String> localFiles = localFileToCopyList
135 				.stream()
136 				.filter(Utils::isNotBlank)
137 				.collect(Collectors.toList());
138 
139 			// Copy the list specified list of files, and update the command accordingly
140 			final String localFilesUpdatedCommand = WindowsRemoteProcessUtils.copyLocalFilesToShare(
141 				command,
142 				localFiles,
143 				smbTempShare.getUncSharePath(),
144 				smbTempShare.getRemotePath()
145 			);
146 
147 			final Charset charset = WindowsRemoteProcessUtils.getWindowsEncodingCharset(
148 				smbTempShare.getWindowsRemoteExecutor(),
149 				TimeoutHelper.getRemainingTime(timeout, start, "No time left to retrieve the code set")
150 			);
151 
152 			return smbTempShare
153 				.getWindowsRemoteExecutor()
154 				.executeCommand(
155 					String.format("CMD.EXE /C (%s)", localFilesUpdatedCommand),
156 					null,
157 					charset,
158 					TimeoutHelper.getRemainingTime(timeout, start, "No time left to execute command")
159 				);
160 		} catch (final WqlQuerySyntaxException e) {
161 			throw new IOException(e);
162 		}
163 	}
164 }