View Javadoc
1   package org.metricshub.winrm;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2023 - 2026 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.nio.charset.Charset;
24  import java.nio.charset.StandardCharsets;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.TimeoutException;
28  import org.metricshub.winrm.exceptions.WindowsRemoteException;
29  import org.metricshub.winrm.exceptions.WqlQuerySyntaxException;
30  
31  public interface WindowsRemoteExecutor extends AutoCloseable {
32  	/**
33  	 * Default WS-Enumeration {@code MaxElements} batch size for WQL queries: how many rows the
34  	 * server may return per Enumerate/Pull response.
35  	 */
36  	int DEFAULT_WQL_MAX_ELEMENTS = 32000;
37  
38  	/**
39  	 * Charset of the output of every command run through this executor: UTF-8, because the remote
40  	 * command shell is created with console code page 65001. It is not the remote machine's ANSI or
41  	 * OEM code page, and it does not depend on the remote locale.
42  	 */
43  	Charset SHELL_OUTPUT_CHARSET = StandardCharsets.UTF_8;
44  
45  	/**
46  	 * <p>
47  	 * Execute a WQL query and process its result.
48  	 * </p>
49  	 *
50  	 * @param wqlQuery the WQL query (required)
51  	 * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero)
52  	 * @return a list of result rows. A result row is a Map(LinkedHashMap to preserve the query order) of
53  	 *         properties/values.
54  	 * @throws TimeoutException to notify userName of timeout.
55  	 * @throws WqlQuerySyntaxException if WQL query syntax is invalid
56  	 * @throws WindowsRemoteException For any problem encountered
57  	 */
58  	List<Map<String, Object>> executeWql(final String wqlQuery, final long timeout)
59  		throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException;
60  
61  	/**
62  	 * <p>
63  	 * Execute a WQL query with explicit enumeration parameters: the WMI namespace, the
64  	 * WS-Enumeration {@code MaxElements} batch size, and the per-Pull {@code MaxTime}.
65  	 * </p>
66  	 * <p>
67  	 * The default implementation throws {@link UnsupportedOperationException}: only executors that
68  	 * can honor the namespace and enumeration parameters (such as the built-in lightweight backend)
69  	 * implement this method, and silently ignoring a namespace would query the wrong resource.
70  	 * </p>
71  	 *
72  	 * @param namespace the WMI namespace to query, e.g. {@code ROOT\CIMV2} (required)
73  	 * @param wqlQuery the WQL query (required)
74  	 * @param timeout Timeout in milliseconds (throws an IllegalArgumentException if negative or zero)
75  	 * @param maxElements maximum number of rows per Enumerate/Pull response (throws an
76  	 *        IllegalArgumentException if negative or zero); see {@link #DEFAULT_WQL_MAX_ELEMENTS}
77  	 * @param pullTimeout maximum time in milliseconds the server may hold a single Pull open before
78  	 *        answering with the rows it has ({@code MaxTime}); 0 leaves it to the server default
79  	 * @return a list of result rows. A result row is a Map(LinkedHashMap to preserve the query order) of
80  	 *         properties/values.
81  	 * @throws TimeoutException to notify userName of timeout.
82  	 * @throws WqlQuerySyntaxException if WQL query syntax is invalid
83  	 * @throws WindowsRemoteException For any problem encountered
84  	 */
85  	default List<Map<String, Object>> executeWql(
86  		final String namespace,
87  		final String wqlQuery,
88  		final long timeout,
89  		final int maxElements,
90  		final long pullTimeout
91  	) throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException {
92  		throw new UnsupportedOperationException(
93  			getClass().getName() + " does not support WQL enumeration parameters."
94  		);
95  	}
96  
97  	/**
98  	 * <p>
99  	 * Start a WQL enumeration and return a lazy {@link WqlCursor} over its rows: rows can be
100 	 * consumed as the WS-Enumeration pages arrive, and memory stays bounded by one page.
101 	 * </p>
102 	 * <p>
103 	 * The default implementation throws {@link UnsupportedOperationException}: only executors that
104 	 * support streaming (such as the built-in lightweight backend) implement this method.
105 	 * </p>
106 	 *
107 	 * @param namespace the WMI namespace to query, e.g. {@code ROOT\CIMV2} (required)
108 	 * @param wqlQuery the WQL query (required)
109 	 * @param timeout timeout in milliseconds of each WSMan round trip — the inactivity timeout of
110 	 *        the stream, not an overall deadline (throws an IllegalArgumentException if negative
111 	 *        or zero)
112 	 * @param maxElements maximum number of rows per Enumerate/Pull response (throws an
113 	 *        IllegalArgumentException if negative or zero); see {@link #DEFAULT_WQL_MAX_ELEMENTS}
114 	 * @param pullTimeout maximum time in milliseconds the server may hold a single Pull open before
115 	 *        answering with the rows it has ({@code MaxTime}); 0 leaves it to the server default
116 	 * @return a cursor over the result rows, owning the executor's connection until exhausted or
117 	 *         closed — always close it (try-with-resources)
118 	 * @throws TimeoutException when the server does not answer the initial Enumerate in time
119 	 * @throws WqlQuerySyntaxException if WQL query syntax is invalid
120 	 * @throws WindowsRemoteException For any problem encountered
121 	 */
122 	default WqlCursor streamWql(
123 		final String namespace,
124 		final String wqlQuery,
125 		final long timeout,
126 		final int maxElements,
127 		final long pullTimeout
128 	) throws TimeoutException, WqlQuerySyntaxException, WindowsRemoteException {
129 		throw new UnsupportedOperationException(getClass().getName() + " does not support streaming WQL enumeration.");
130 	}
131 
132 	/**
133 	 * <p>
134 	 * Start a command on the remote host and return a {@link CommandCursor} over its raw output:
135 	 * chunks can be consumed as the WSMan Receive responses arrive, before the command exits.
136 	 * </p>
137 	 * <p>
138 	 * The default implementation throws {@link UnsupportedOperationException}: only executors that
139 	 * support streaming (such as the built-in lightweight backend) implement this method.
140 	 * </p>
141 	 *
142 	 * @param command The command to execute
143 	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
144 	 * @param timeout timeout in milliseconds of each WSMan round trip — the inactivity timeout of
145 	 *        the stream, not an overall deadline (throws an IllegalArgumentException if negative
146 	 *        or zero)
147 	 * @return a cursor over the command output, owning the executor's connection until the command
148 	 *         completes or the cursor is closed — always close it (try-with-resources)
149 	 * @throws TimeoutException when the server does not answer the command startup in time
150 	 * @throws WindowsRemoteException For any problem encountered
151 	 */
152 	default CommandCursor startCommand(final String command, final String workingDirectory, final long timeout)
153 		throws TimeoutException, WindowsRemoteException {
154 		throw new UnsupportedOperationException(getClass().getName() + " does not support streaming command execution.");
155 	}
156 
157 	/**
158 	 * <p>
159 	 * Variant of {@link #startCommand(String, String, long)} making the {@code WINRS_CONSOLEMODE_STDIN}
160 	 * option explicit. The three-argument variant keeps the historical console semantics
161 	 * ({@code TRUE}); pass {@code false} when the command will be fed standard input through
162 	 * {@link CommandCursor#send(byte[], boolean)} and must see it as an ordinary pipe — a
163 	 * console-mode stdin never reaches EOF for tools like {@code sort} or {@code more}.
164 	 * </p>
165 	 * <p>
166 	 * The default implementation delegates console-mode requests to
167 	 * {@link #startCommand(String, String, long)} — an executor that overrides only the historical
168 	 * three-argument variant keeps working for ordinary commands — and throws
169 	 * {@link UnsupportedOperationException} for pipe mode: only executors that support command
170 	 * input (such as the built-in lightweight backend) implement it.
171 	 * </p>
172 	 *
173 	 * @param command The command to execute
174 	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
175 	 * @param timeout timeout in milliseconds of each WSMan round trip — the inactivity timeout of
176 	 *        the stream, not an overall deadline (throws an IllegalArgumentException if negative
177 	 *        or zero)
178 	 * @param consoleModeStdin the value of the {@code WINRS_CONSOLEMODE_STDIN} option: {@code true}
179 	 *        for console semantics (the historical default), {@code false} for pipe semantics
180 	 * @return a cursor over the command output, owning the executor's connection until the command
181 	 *         completes or the cursor is closed — always close it (try-with-resources)
182 	 * @throws TimeoutException when the server does not answer the command startup in time
183 	 * @throws WindowsRemoteException For any problem encountered
184 	 */
185 	default CommandCursor startCommand(
186 		final String command,
187 		final String workingDirectory,
188 		final long timeout,
189 		final boolean consoleModeStdin
190 	) throws TimeoutException, WindowsRemoteException {
191 		if (consoleModeStdin) {
192 			return startCommand(command, workingDirectory, timeout);
193 		}
194 		throw new UnsupportedOperationException(getClass().getName() + " does not support pipe-mode standard input.");
195 	}
196 
197 	/**
198 	 * <p>
199 	 * Variant of {@link #startCommand(String, String, long, boolean)} that also sets environment
200 	 * variables in the remote shell. Like the working directory, the environment is shell-scoped:
201 	 * it is honored only when the shell is created, i.e. by the first command this executor runs.
202 	 * </p>
203 	 * <p>
204 	 * The default implementation delegates to {@link #startCommand(String, String, long, boolean)}
205 	 * when no variable is requested — an executor unaware of this variant keeps working for
206 	 * ordinary commands — and throws {@link UnsupportedOperationException} otherwise: only
207 	 * executors that can put the variables on the wire (such as the built-in lightweight backend)
208 	 * implement it, and silently dropping them would run the command in the wrong environment.
209 	 * </p>
210 	 *
211 	 * @param command The command to execute
212 	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
213 	 * @param environment Environment variables of the remote shell, in insertion order (can be null
214 	 *        or empty for none)
215 	 * @param timeout timeout in milliseconds of each WSMan round trip — the inactivity timeout of
216 	 *        the stream, not an overall deadline (throws an IllegalArgumentException if negative
217 	 *        or zero)
218 	 * @param consoleModeStdin the value of the {@code WINRS_CONSOLEMODE_STDIN} option: {@code true}
219 	 *        for console semantics (the historical default), {@code false} for pipe semantics
220 	 * @return a cursor over the command output, owning the executor's connection until the command
221 	 *         completes or the cursor is closed — always close it (try-with-resources)
222 	 * @throws TimeoutException when the server does not answer the command startup in time
223 	 * @throws WindowsRemoteException For any problem encountered
224 	 */
225 	default CommandCursor startCommand(
226 		final String command,
227 		final String workingDirectory,
228 		final Map<String, String> environment,
229 		final long timeout,
230 		final boolean consoleModeStdin
231 	) throws TimeoutException, WindowsRemoteException {
232 		if (environment == null || environment.isEmpty()) {
233 			return startCommand(command, workingDirectory, timeout, consoleModeStdin);
234 		}
235 		throw new UnsupportedOperationException(getClass().getName() + " does not support shell environment variables.");
236 	}
237 
238 	/**
239 	 * Execute the command on the remote
240 	 *
241 	 * @param command The command to execute
242 	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
243 	 * @param charset The charset decoding the command output; {@code null} uses
244 	 *        {@link #SHELL_OUTPUT_CHARSET}, which is what the remote shell actually emits
245 	 * @param timeout Timeout in milliseconds
246 	 * @return The command result
247 	 * @throws WindowsRemoteException For any problem encountered
248 	 * @throws TimeoutException To notify userName of timeout.
249 	 */
250 	WindowsRemoteCommandResult executeCommand(
251 		final String command,
252 		final String workingDirectory,
253 		final Charset charset,
254 		final long timeout
255 	) throws WindowsRemoteException, TimeoutException;
256 
257 	/**
258 	 * <p>
259 	 * Variant of {@link #executeCommand(String, String, Charset, long)} that also sets environment
260 	 * variables in the remote shell. Like the working directory, the environment is shell-scoped:
261 	 * it is honored only when the shell is created, i.e. by the first command this executor runs.
262 	 * </p>
263 	 * <p>
264 	 * The default implementation delegates to {@link #executeCommand(String, String, Charset, long)}
265 	 * when no variable is requested — an executor unaware of this variant keeps working for
266 	 * ordinary commands — and throws {@link UnsupportedOperationException} otherwise: only
267 	 * executors that can put the variables on the wire (such as the built-in lightweight backend)
268 	 * implement it, and silently dropping them would run the command in the wrong environment.
269 	 * </p>
270 	 *
271 	 * @param command The command to execute
272 	 * @param workingDirectory Path of the directory for the spawned process on the remote system (can be null)
273 	 * @param environment Environment variables of the remote shell, in insertion order (can be null
274 	 *        or empty for none)
275 	 * @param charset The charset decoding the command output; {@code null} uses
276 	 *        {@link #SHELL_OUTPUT_CHARSET}, which is what the remote shell actually emits
277 	 * @param timeout Timeout in milliseconds
278 	 * @return The command result
279 	 * @throws WindowsRemoteException For any problem encountered
280 	 * @throws TimeoutException To notify userName of timeout.
281 	 */
282 	default WindowsRemoteCommandResult executeCommand(
283 		final String command,
284 		final String workingDirectory,
285 		final Map<String, String> environment,
286 		final Charset charset,
287 		final long timeout
288 	) throws WindowsRemoteException, TimeoutException {
289 		if (environment == null || environment.isEmpty()) {
290 			return executeCommand(command, workingDirectory, charset, timeout);
291 		}
292 		throw new UnsupportedOperationException(getClass().getName() + " does not support shell environment variables.");
293 	}
294 
295 	/**
296 	 * Get the hostname.
297 	 *
298 	 * @return
299 	 */
300 	String getHostname();
301 
302 	/**
303 	 * Get the username.
304 	 *
305 	 * @return
306 	 */
307 	String getUsername();
308 
309 	/**
310 	 * Get the password.
311 	 *
312 	 * @return
313 	 */
314 	char[] getPassword();
315 
316 	/**
317 	 * Close the executor and release its resources. Narrows {@link AutoCloseable#close()} so it does
318 	 * not declare a checked exception, letting callers use try-with-resources without catching
319 	 * {@link Exception}.
320 	 */
321 	@Override
322 	void close();
323 }