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