View Javadoc
1   package org.metricshub.wmi.wbem;
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 com.sun.jna.Native;
24  import com.sun.jna.Pointer;
25  import com.sun.jna.platform.win32.COM.COMUtils;
26  import com.sun.jna.platform.win32.Ole32;
27  import com.sun.jna.platform.win32.WinError;
28  import com.sun.jna.platform.win32.WinNT.HRESULT;
29  import org.metricshub.wmi.exceptions.WmiComException;
30  
31  /**
32   * Class with various static methods to help with COM interaction.
33   */
34  public class WmiComHelper {
35  
36  	/**
37  	 * Private constructor, as this class cannot be instantiated (it's pure static)
38  	 */
39  	private WmiComHelper() {}
40  
41  	/**
42  	 * Whether COM Library has been initialized for current Thread
43  	 */
44  	private static ThreadLocal<Boolean> comLibraryInitialized = ThreadLocal.withInitial(() -> false);
45  
46  	/**
47  	 * Initializes COM library and sets security to impersonate the local userName.
48  	 * This function needs to be called at least once for each thread, as COM must be
49  	 * initialized for each thread. You can call this function even if COM has already
50  	 * been initialized. It will verify first whether COM initialization has already
51  	 * been done for this thread before proceeding.
52  	 * The threading model is <b>multi-threaded</b>.
53  	 * @throws WmiComException when COM library cannot be instantiated
54  	 * @throws IllegalStateException when COM library has already been initialized with a different
55  	 * threading model
56  	 */
57  	public static void initializeComLibrary() throws WmiComException {
58  		// Do nothing if COM Library already initialized
59  		if (Boolean.TRUE.equals(comLibraryInitialized.get())) {
60  			return;
61  		}
62  
63  		// Initialize COM parameters
64  		// Step 1 from: <a href="https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-a-remote-computer">Example: Getting WMI Data from a Remote Computer</a>
65  		final HRESULT hCoInitResult = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED);
66  		final int coInitResult = hCoInitResult.intValue();
67  		if (coInitResult == WinError.RPC_E_CHANGED_MODE) {
68  			throw new IllegalStateException("CoInitializeEx() has already been called with a different threading model");
69  		} else if (coInitResult != COMUtils.S_OK && coInitResult != COMUtils.S_FALSE) {
70  			throw new WmiComException(
71  				"Failed to initialize the COM Library (HRESULT=0x%s)",
72  				Integer.toHexString(coInitResult)
73  			);
74  		}
75  
76  		// Initialize COM process security
77  		// Step 2 from: <a href="https://docs.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-a-remote-computer">Example: Getting WMI Data from a Remote Computer</a>
78  		// @formatter:off
79  		// CHECKSTYLE:OFF
80  		final HRESULT hResult = Ole32.INSTANCE.CoInitializeSecurity(
81  			null,
82  			-1,
83  			null,
84  			null,
85  			Ole32.RPC_C_AUTHN_LEVEL_DEFAULT,
86  			Ole32.RPC_C_IMP_LEVEL_IMPERSONATE,
87  			null,
88  			Ole32.EOAC_NONE,
89  			null
90  		);
91  		// CHECKSTYLE:ON
92  		// @formatter:on
93  
94  		// If security was already initialized, we'll get RPC_E_TOO_LATE
95  		// which can be safely ignored
96  		if (COMUtils.FAILED(hResult) && hResult.intValue() != WinError.RPC_E_TOO_LATE) {
97  			unInitializeCom();
98  			throw new WmiComException(
99  				"Failed to initialize security (HRESULT=0x%s)",
100 				Integer.toHexString(hResult.intValue())
101 			);
102 		}
103 
104 		// We're good!
105 		comLibraryInitialized.set(true);
106 	}
107 
108 	/**
109 	 * UnInitialize the COM library.
110 	 */
111 	public static void unInitializeCom() {
112 		// Do nothing if COM has not been initialized
113 		if (Boolean.FALSE.equals(comLibraryInitialized.get())) {
114 			return;
115 		}
116 
117 		Ole32.INSTANCE.CoUninitialize();
118 		comLibraryInitialized.set(false);
119 	}
120 
121 	/**
122 	 * @return whether COM has been initialized for the current thread
123 	 */
124 	public static boolean isComInitialized() {
125 		return comLibraryInitialized.get();
126 	}
127 
128 	/**
129 	 * Same function than in com.sun.jna.platform.win32.COM.COMInvoker._invokeNativeInt
130 	 *
131 	 * @param contextPointer
132 	 * @param vtableId
133 	 * @param args
134 	 * @param returnType
135 	 * @return whatever the specified function returns
136 	 */
137 	public static Object comInvokerInvokeNativeObject(
138 		final Pointer contextPointer,
139 		final int vtableId,
140 		final Object[] args,
141 		final Class<?> returnType
142 	) {
143 		final Pointer vptr = contextPointer.getPointer(0);
144 		final com.sun.jna.Function func = com.sun.jna.Function.getFunction(
145 			vptr.getPointer(vtableId * Native.POINTER_SIZE * 1L)
146 		);
147 		return func.invoke(returnType, args);
148 	}
149 }