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 }