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 }