View Javadoc
1   package org.sentrysoftware.winrm;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * WinRM Java Client
6    * ჻჻჻჻჻჻
7    * Copyright 2023 - 2024 Sentry Software
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.util.Objects;
24  import java.util.Optional;
25  import java.util.concurrent.TimeoutException;
26  
27  import org.sentrysoftware.winrm.exceptions.WindowsRemoteException;
28  import org.sentrysoftware.winrm.exceptions.WqlQuerySyntaxException;
29  
30  public class WindowsTempShare {
31  
32  	/** The share name */
33  	private final String shareName;
34  
35  	/** The UNC path of the share. */
36  	private final String uncSharePath;
37  
38  	/** The remote path.*/
39  	private final String remotePath;
40  
41  	/** The WindowsRemoteExecutor instance */
42  	private final WindowsRemoteExecutor windowsRemoteExecutor;
43  
44  	/**
45  	 * Constructor of WindowsTempShare
46  	 * 
47  	 * @param windowsRemoteExecutor the WindowsRemoteExecutor instance
48  	 * connected to the remote host (mandatory)
49  	 * @param shareNameOrUnc The name of the share, or its full UNC path (mandatory)
50  	 * @param remotePath The path on the remote system of the directory being shared
51  	 */
52  	public WindowsTempShare(
53  			final WindowsRemoteExecutor windowsRemoteExecutor,
54  			final String shareNameOrUnc,
55  			final String remotePath) {
56  
57  		Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor");
58  		Utils.checkNonNull(shareNameOrUnc, "shareNameOrUnc");
59  
60  		if (shareNameOrUnc.startsWith("\\\\")) {
61  			this.uncSharePath = shareNameOrUnc;
62  			final String[] uncElements = shareNameOrUnc.split("\\\\");
63  			this.shareName = uncElements[3];
64  		} else {
65  			this.uncSharePath = buildUncPath(windowsRemoteExecutor.getHostname(), shareNameOrUnc);
66  			this.shareName = shareNameOrUnc;
67  		}
68  
69  		this.remotePath = remotePath;
70  		this.windowsRemoteExecutor = windowsRemoteExecutor;
71  	}
72  
73  	/**
74  	 * Get the existing share on the host or create it if absent.
75  	 *
76  	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory)
77  	 * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero)
78  	 * @param shareRemoteDirectory ShareRemoteDirectoryConsumer function (mandatory)
79  	 * 
80  	 * @return The remote path.
81  	 * 
82  	 * @throws TimeoutException To notify userName of timeout.
83  	 * @throws WindowsRemoteException For any problem encountered.
84  	 */
85  	public static WindowsTempShare getOrCreateShare(
86  			final WindowsRemoteExecutor windowsRemoteExecutor,
87  			final long timeout,
88  			final ShareRemoteDirectoryConsumer<WindowsRemoteExecutor, String, String, Long> shareRemoteDirectory)
89  					throws TimeoutException, WindowsRemoteException {
90  
91  		Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor");
92  		Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
93  		Utils.checkNonNull(shareRemoteDirectory, "shareRemoteDirectory");
94  
95  		final long start = Utils.getCurrentTimeMillis();
96  
97  		// Are we targeting a Windows cluster?
98  		// (in which case, things are a bit different)
99  
100 		// Try to get a clustered share
101 		final Optional<WindowsTempShare> clusterShare = getClusterShare(
102 				windowsRemoteExecutor,
103 				TimeoutHelper.getRemainingTime(timeout, start, "No time left to check for cluster share"),
104 				start);
105 
106 		if (clusterShare.isPresent()) {
107 			return clusterShare.get();
108 		}
109 
110 		// Normal case (non-cluster)
111 		final String shareName = buildShareName();
112 		final Optional<WindowsTempShare> share = getShare(
113 				windowsRemoteExecutor,
114 				shareName,
115 				TimeoutHelper.getRemainingTime(timeout, start, "No time left to get a normal temporary share"));
116 		if (share.isPresent()) {
117 			return share.get();
118 		}
119 
120 		return createTempShare(
121 				windowsRemoteExecutor,
122 				shareName,
123 				TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary share"),
124 				shareRemoteDirectory);
125 	}
126 
127 	/**
128 	 * Get the Windows directory.
129 	 *
130 	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory)
131 	 * @param timeout Timeout in milliseconds. (throws an IllegalArgumentException if negative or zero)
132 	 * 
133 	 * @return The Windows directory.
134 	 * 
135 	 * @throws WindowsRemoteException For any problem encountered.
136 	 * @throws TimeoutException To notify userName of timeout.
137 	 * 
138 	 * @see <a href="https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem">
139 	 * Win32_OperatingSystem class</a>
140 	 *
141 	 */
142 	public static String getWindowsDirectory(
143 			final WindowsRemoteExecutor windowsRemoteExecutor,
144 			final long timeout) throws WindowsRemoteException, TimeoutException {
145 
146 		Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor");
147 		Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
148 
149 		try {
150 			// Extract the WindowsDirectory property from the first instance and return it (or throw an exception)
151 			return windowsRemoteExecutor.executeWql(
152 					"SELECT WindowsDirectory FROM Win32_OperatingSystem",
153 					timeout
154 					).stream()
155 					.limit(1)
156 					.map(row -> (String) row.get("WindowsDirectory"))
157 					.filter(Objects::nonNull)
158 					.findFirst()
159 					.orElseThrow(
160 							() -> new WindowsRemoteException(
161 									String.format("Couldn't identify the Windows root directory on %s.",
162 											windowsRemoteExecutor.getHostname())));
163 
164 		} catch (final WqlQuerySyntaxException e) {
165 			throw new WindowsRemoteException(e); // Impossible
166 		}
167 	}
168 
169 	/**
170 	 * Create the remote directory.
171 	 *
172 	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance. (mandatory)
173 	 * @param remotePath The remote path to create
174 	 * @param timeout Timeout in milliseconds
175 	 * @param start start time in milliseconds.
176 	 * 
177 	 * @throws WindowsRemoteException For any problem encountered.
178 	 * @throws TimeoutException To notify userName of timeout.
179 	 */
180 	public static void createRemoteDirectory(
181 			final WindowsRemoteExecutor windowsRemoteExecutor,
182 			final String remotePath,
183 			final long timeout,
184 			final long start) throws WindowsRemoteException, TimeoutException {
185 
186 		Utils.checkNonNull(windowsRemoteExecutor, "windowsRemoteExecutor");
187 
188 		windowsRemoteExecutor.executeCommand(
189 				buildCreateRemoteDirectoryCommand(remotePath),
190 				null,
191 				null,
192 				timeout);
193 	}
194 
195 	/**
196 	 * Build a UNC path from hostname and share name.
197 	 * 
198 	 * Note: This method ensures compatibility with IPv6 hosts
199 	 * 
200 	 * @param hostname Host to connect to. (mandatory)
201 	 * @param share The share
202 	 * 
203 	 * @return The UNC path to the share (\\unc(host)\share)
204 	 */
205 	static String buildUncPath(final String hostname, final String share) {
206 		Utils.checkNonNull(hostname, "hostname");
207 
208 		return hostname.contains(":") ?
209 				String.format("\\\\%s.ipv6-literal.net\\%s", hostname.replace(":", "-").replace("%", "s"), share) :
210 					String.format("\\\\%s\\%s", hostname, share);
211 	}
212 
213 	/**
214 	 * Create the Windows command for a directory creation.
215 	 * 
216 	 * @param remotePath The remote path to create. (mandatory)
217 	 * 
218 	 * @return The command to execute.
219 	 */
220 	static String buildCreateRemoteDirectoryCommand(final String remotePath) {
221 		Utils.checkNonBlank(remotePath, "remotePath");
222 
223 		return String.format("CMD.EXE /C IF NOT EXIST \"%s\" MKDIR %s", remotePath, remotePath);
224 	}
225 
226 	/**
227 	 * @param path Root path of the temporary directory that will be used in a cluster. (mandatory)
228 	 * 
229 	 * @return Path to the temporary directory
230 	 */
231 	static String buildPathOnCluster(final String path) {
232 		Utils.checkNonNull(path, "path");
233 
234 		return String.format("%s\\Temp\\SEN_TempFor_%s", path, Utils.getComputerName());
235 	}
236 
237 	/**
238 	 * Build the remote temp path name with the folder name.
239 	 * 
240 	 * @param folder The folder name. (mandatory)
241 	 * @param shareName The Share Name. (mandatory)
242 	 * 
243 	 * @return The remote directory path. (folder\Temp\shareName)
244 	 */
245 	static String buildRemotePath(
246 			final String folder,
247 			final String shareName) {
248 		Utils.checkNonNull(folder, "folder");
249 		Utils.checkNonBlank(shareName, "shareName");
250 
251 		return String.format("%s\\Temp\\%s", folder, shareName);
252 	}
253 
254 	/**
255 	 * Build the Share name with the computer name.
256 	 * 
257 	 * @return The share name.
258 	 */
259 	static String buildShareName() {
260 		return String.format("SEN_ShareFor_%s$", Utils.getComputerName());
261 	}
262 
263 	/**
264 	 * Retrieve an "Admin Share" (like D$, E$, etc.) that is exposed by a cluster.
265 	 * 
266 	 * If the targeted system is not a cluster, returns an empty optional.
267 	 * 
268 	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance.
269 	 * @param timeout Timeout in milliseconds.
270 	 * @param start start time in milliseconds.
271 	 * 
272 	 * @return An optional Map<String, Object> with 2 entries: "Name" and "Path"
273 	 * 
274 	 * @throws TimeoutException To notify userName of timeout.
275 	 * @throws WindowsRemoteException For any problem encountered.
276 	 */
277 	static Optional<WindowsTempShare> getClusterShare(
278 			final WindowsRemoteExecutor windowsRemoteExecutor,
279 			final long timeout,
280 			final long start) throws TimeoutException, WindowsRemoteException {
281 
282 		try {
283 			final Optional<WindowsTempShare> clusterShare = windowsRemoteExecutor.executeWql(
284 					"SELECT Name,Path FROM Win32_ClusterShare WHERE "
285 					+ "ServerName <> '*' AND (Type = 2147483648 OR Type = 3221225472) AND Name LIKE '%\\\\_$'",
286 					timeout
287 					).stream()
288 					.limit(1)
289 					.map(
290 							// We return a TempShare instance pointing to a subdirectory in this share
291 							row -> new WindowsTempShare(
292 									windowsRemoteExecutor,
293 									buildPathOnCluster((String) row.get("Name")),
294 									buildPathOnCluster((String) row.get("Path"))))
295 					.findFirst();
296 
297 			if (clusterShare.isPresent()) {
298 				// We create the subdirectory (if necessary)
299 				createRemoteDirectory(windowsRemoteExecutor, clusterShare.get().getRemotePath(), timeout, start);
300 			}
301 
302 			return clusterShare;
303 
304 		} catch (final WqlQuerySyntaxException e) {
305 			throw new WindowsRemoteException(e); // Impossible
306 		}
307 	}
308 
309 	/**
310 	 * Retrieve the specified share.
311 	 *
312 	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance
313 	 * @param shareName The share name
314 	 * @param timeout Timeout in milliseconds
315 	 * 
316 	 * @return An optional TempShare instance
317 	 * 
318 	 * @throws TimeoutException To notify userName of timeout
319 	 * @throws WindowsRemoteException For any problem encountered
320 	 */
321 	static Optional<WindowsTempShare> getShare(
322 			final WindowsRemoteExecutor windowsRemoteExecutor,
323 			final String shareName,
324 			final long timeout
325 			) throws TimeoutException, WindowsRemoteException {
326 		try {
327 
328 			return windowsRemoteExecutor.executeWql(
329 					String.format("SELECT Name,Path FROM Win32_Share WHERE Name = '%s'", shareName),
330 					timeout
331 					).stream()
332 					.limit(1)
333 					.map(row -> new WindowsTempShare(
334 							windowsRemoteExecutor,
335 							(String) row.get("Name"),
336 							(String) row.get("Path")))
337 					.findFirst();
338 
339 		} catch (final WqlQuerySyntaxException e) {
340 			throw new WindowsRemoteException(e); // Impossible
341 		}
342 	}
343 
344 	/**
345 	 * Create a shared temporary folder on the remote
346 	 *
347 	 * @param windowsRemoteExecutor WindowsRemoteExecutor instance.
348 	 * @param shareName The Share Name.
349 	 * @param timeout Timeout in milliseconds.
350 	 * @param shareRemoteDirectory shareRemoteDirectory function
351 	 * 
352 	 * @return A TempShare instance
353 	 * 
354 	 * @throws WindowsRemoteException For any problem encountered.
355 	 * @throws TimeoutException To notify userName of timeout.
356 	 */
357 	static WindowsTempShare createTempShare(
358 			final WindowsRemoteExecutor windowsRemoteExecutor,
359 			final String shareName,
360 			final long timeout,
361 			final ShareRemoteDirectoryConsumer<WindowsRemoteExecutor, String, String, Long> shareRemoteDirectory
362 			) throws WindowsRemoteException, TimeoutException {
363 
364 		final long start = Utils.getCurrentTimeMillis();
365 
366 		// Find where Windows is installed on the remote system. We will create the share under %WINDIR%\Temp.
367 		final String folder = getWindowsDirectory(windowsRemoteExecutor, timeout);
368 
369 		// Create the folder on the remote system
370 		final String remotePath = buildRemotePath(folder, shareName);
371 		createRemoteDirectory(
372 				windowsRemoteExecutor,
373 				remotePath,
374 				TimeoutHelper.getRemainingTime(timeout, start, "No time left to create the temporary directory"),
375 				start);
376 
377 		// Create the share
378 		shareRemoteDirectory.apply(windowsRemoteExecutor, remotePath, shareName, timeout);
379 
380 		return new WindowsTempShare(windowsRemoteExecutor, shareName, remotePath);
381 	}
382 
383 	/** Get the share name */
384 	public String getShareName() {
385 		return shareName;
386 	}
387 
388 	/** Get the UNC path of the share */
389 	public String getUncSharePath() {
390 		return uncSharePath;
391 	}
392 
393 	/** Get the remote path */
394 	public String getRemotePath() {
395 		return remotePath;
396 	}
397 
398 	/** Get the WindowsRemoteExecutor instance */
399 	public WindowsRemoteExecutor getWindowsRemoteExecutor() {
400 		return windowsRemoteExecutor;
401 	}
402 }