View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.windows;
6   
7   import static oshi.software.os.OSService.State.OTHER;
8   import static oshi.software.os.OSService.State.RUNNING;
9   import static oshi.software.os.OSService.State.STOPPED;
10  import static oshi.software.os.OperatingSystem.ProcessFiltering.VALID_PROCESS;
11  import static oshi.util.Memoizer.defaultExpiration;
12  import static oshi.util.Memoizer.memoize;
13  
14  import java.util.ArrayList;
15  import java.util.Arrays;
16  import java.util.Collection;
17  import java.util.Collections;
18  import java.util.HashMap;
19  import java.util.HashSet;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.concurrent.TimeUnit;
24  import java.util.function.Supplier;
25  import java.util.stream.Collectors;
26  
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import com.sun.jna.Native;
31  import com.sun.jna.platform.win32.Advapi32;
32  import com.sun.jna.platform.win32.Advapi32Util;
33  import com.sun.jna.platform.win32.Advapi32Util.EventLogIterator;
34  import com.sun.jna.platform.win32.Advapi32Util.EventLogRecord;
35  import com.sun.jna.platform.win32.Kernel32;
36  import com.sun.jna.platform.win32.Psapi;
37  import com.sun.jna.platform.win32.Tlhelp32;
38  import com.sun.jna.platform.win32.VersionHelpers;
39  import com.sun.jna.platform.win32.W32ServiceManager;
40  import com.sun.jna.platform.win32.Win32Exception;
41  import com.sun.jna.platform.win32.WinDef.DWORD;
42  import com.sun.jna.platform.win32.WinError;
43  import com.sun.jna.platform.win32.WinNT;
44  import com.sun.jna.platform.win32.WinNT.HANDLE;
45  import com.sun.jna.platform.win32.WinNT.LUID;
46  import com.sun.jna.platform.win32.Winsvc;
47  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
48  
49  import oshi.annotation.concurrent.ThreadSafe;
50  import oshi.driver.windows.EnumWindows;
51  import oshi.driver.windows.registry.HkeyUserData;
52  import oshi.driver.windows.registry.NetSessionData;
53  import oshi.driver.windows.registry.ProcessPerformanceData;
54  import oshi.driver.windows.registry.ProcessWtsData;
55  import oshi.driver.windows.registry.ProcessWtsData.WtsInfo;
56  import oshi.driver.windows.registry.SessionWtsData;
57  import oshi.driver.windows.registry.ThreadPerformanceData;
58  import oshi.driver.windows.wmi.Win32OperatingSystem;
59  import oshi.driver.windows.wmi.Win32OperatingSystem.OSVersionProperty;
60  import oshi.driver.windows.wmi.Win32Processor;
61  import oshi.driver.windows.wmi.Win32Processor.BitnessProperty;
62  import oshi.jna.ByRef.CloseableHANDLEByReference;
63  import oshi.jna.ByRef.CloseableIntByReference;
64  import oshi.jna.ByRef.CloseablePROCESSENTRY32ByReference;
65  import oshi.jna.Struct.CloseablePerformanceInformation;
66  import oshi.jna.Struct.CloseableSystemInfo;
67  import oshi.software.common.AbstractOperatingSystem;
68  import oshi.software.os.FileSystem;
69  import oshi.software.os.InternetProtocolStats;
70  import oshi.software.os.NetworkParams;
71  import oshi.software.os.OSDesktopWindow;
72  import oshi.software.os.OSProcess;
73  import oshi.software.os.OSService;
74  import oshi.software.os.OSService.State;
75  import oshi.software.os.OSSession;
76  import oshi.software.os.OSThread;
77  import oshi.util.Constants;
78  import oshi.util.GlobalConfig;
79  import oshi.util.platform.windows.WmiUtil;
80  import oshi.util.tuples.Pair;
81  
82  /**
83   * Microsoft Windows, commonly referred to as Windows, is a group of several proprietary graphical operating system
84   * families, all of which are developed and marketed by Microsoft.
85   */
86  @ThreadSafe
87  public class WindowsOperatingSystem extends AbstractOperatingSystem {
88  
89      private static final Logger LOG = LoggerFactory.getLogger(WindowsOperatingSystem.class);
90  
91      private static final boolean USE_PROCSTATE_SUSPENDED = GlobalConfig
92              .get(GlobalConfig.OSHI_OS_WINDOWS_PROCSTATE_SUSPENDED, false);
93  
94      private static final boolean IS_VISTA_OR_GREATER = VersionHelpers.IsWindowsVistaOrGreater();
95  
96      /*
97       * Windows event log name
98       */
99      private static Supplier<String> systemLog = memoize(WindowsOperatingSystem::querySystemLog,
100             TimeUnit.HOURS.toNanos(1));
101 
102     private static final long BOOTTIME = querySystemBootTime();
103 
104     static {
105         enableDebugPrivilege();
106     }
107 
108     /*
109      * OSProcess code will need to know bitness of current process
110      */
111     private static final boolean X86 = isCurrentX86();
112     private static final boolean WOW = isCurrentWow();
113 
114     /*
115      * Cache full process stats queries. Second query will only populate if first one returns null.
116      */
117     private Supplier<Map<Integer, ProcessPerformanceData.PerfCounterBlock>> processMapFromRegistry = memoize(
118             WindowsOperatingSystem::queryProcessMapFromRegistry, defaultExpiration());
119     private Supplier<Map<Integer, ProcessPerformanceData.PerfCounterBlock>> processMapFromPerfCounters = memoize(
120             WindowsOperatingSystem::queryProcessMapFromPerfCounters, defaultExpiration());
121     /*
122      * Cache full thread stats queries. Second query will only populate if first one returns null. Only used if
123      * USE_PROCSTATE_SUSPENDED is set true.
124      */
125     private Supplier<Map<Integer, ThreadPerformanceData.PerfCounterBlock>> threadMapFromRegistry = memoize(
126             WindowsOperatingSystem::queryThreadMapFromRegistry, defaultExpiration());
127     private Supplier<Map<Integer, ThreadPerformanceData.PerfCounterBlock>> threadMapFromPerfCounters = memoize(
128             WindowsOperatingSystem::queryThreadMapFromPerfCounters, defaultExpiration());
129 
130     @Override
131     public String queryManufacturer() {
132         return "Microsoft";
133     }
134 
135     @Override
136     public Pair<String, OSVersionInfo> queryFamilyVersionInfo() {
137         String version = System.getProperty("os.name");
138         if (version.startsWith("Windows ")) {
139             version = version.substring(8);
140         }
141 
142         String sp = null;
143         int suiteMask = 0;
144         String buildNumber = "";
145         WmiResult<OSVersionProperty> versionInfo = Win32OperatingSystem.queryOsVersion();
146         if (versionInfo.getResultCount() > 0) {
147             sp = WmiUtil.getString(versionInfo, OSVersionProperty.CSDVERSION, 0);
148             if (!sp.isEmpty() && !Constants.UNKNOWN.equals(sp)) {
149                 version = version + " " + sp.replace("Service Pack ", "SP");
150             }
151             suiteMask = WmiUtil.getUint32(versionInfo, OSVersionProperty.SUITEMASK, 0);
152             buildNumber = WmiUtil.getString(versionInfo, OSVersionProperty.BUILDNUMBER, 0);
153         }
154         String codeName = parseCodeName(suiteMask);
155         // Older JDKs don't recognize Win11 and Server2022
156         if ("10".equals(version) && buildNumber.compareTo("22000") >= 0) {
157             version = "11";
158         } else if ("Server 2019".equals(version) && buildNumber.compareTo("20347") > 0) {
159             version = "Server 2022";
160         }
161         return new Pair<>("Windows", new OSVersionInfo(version, codeName, buildNumber));
162     }
163 
164     /**
165      * Gets suites available on the system and return as a codename
166      *
167      * @param suiteMask The suite mask bitmask
168      *
169      * @return Suites
170      */
171     private static String parseCodeName(int suiteMask) {
172         List<String> suites = new ArrayList<>();
173         if ((suiteMask & 0x00000002) != 0) {
174             suites.add("Enterprise");
175         }
176         if ((suiteMask & 0x00000004) != 0) {
177             suites.add("BackOffice");
178         }
179         if ((suiteMask & 0x00000008) != 0) {
180             suites.add("Communications Server");
181         }
182         if ((suiteMask & 0x00000080) != 0) {
183             suites.add("Datacenter");
184         }
185         if ((suiteMask & 0x00000200) != 0) {
186             suites.add("Home");
187         }
188         if ((suiteMask & 0x00000400) != 0) {
189             suites.add("Web Server");
190         }
191         if ((suiteMask & 0x00002000) != 0) {
192             suites.add("Storage Server");
193         }
194         if ((suiteMask & 0x00004000) != 0) {
195             suites.add("Compute Cluster");
196         }
197         if ((suiteMask & 0x00008000) != 0) {
198             suites.add("Home Server");
199         }
200         return String.join(",", suites);
201     }
202 
203     @Override
204     protected int queryBitness(int jvmBitness) {
205         if (jvmBitness < 64 && System.getenv("ProgramFiles(x86)") != null && IS_VISTA_OR_GREATER) {
206             WmiResult<BitnessProperty> bitnessMap = Win32Processor.queryBitness();
207             if (bitnessMap.getResultCount() > 0) {
208                 return WmiUtil.getUint16(bitnessMap, BitnessProperty.ADDRESSWIDTH, 0);
209             }
210         }
211         return jvmBitness;
212     }
213 
214     @Override
215     public boolean isElevated() {
216         return Advapi32Util.isCurrentProcessElevated();
217     }
218 
219     @Override
220     public FileSystem getFileSystem() {
221         return new WindowsFileSystem();
222     }
223 
224     @Override
225     public InternetProtocolStats getInternetProtocolStats() {
226         return new WindowsInternetProtocolStats();
227     }
228 
229     @Override
230     public List<OSSession> getSessions() {
231         List<OSSession> whoList = HkeyUserData.queryUserSessions();
232         whoList.addAll(SessionWtsData.queryUserSessions());
233         whoList.addAll(NetSessionData.queryUserSessions());
234         return whoList;
235     }
236 
237     @Override
238     public List<OSProcess> getProcesses(Collection<Integer> pids) {
239         return processMapToList(pids);
240     }
241 
242     @Override
243     public List<OSProcess> queryAllProcesses() {
244         return processMapToList(null);
245     }
246 
247     @Override
248     public List<OSProcess> queryChildProcesses(int parentPid) {
249         Set<Integer> descendantPids = getChildrenOrDescendants(getParentPidsFromSnapshot(), parentPid, false);
250         return processMapToList(descendantPids);
251     }
252 
253     @Override
254     public List<OSProcess> queryDescendantProcesses(int parentPid) {
255         Set<Integer> descendantPids = getChildrenOrDescendants(getParentPidsFromSnapshot(), parentPid, true);
256         return processMapToList(descendantPids);
257     }
258 
259     private static Map<Integer, Integer> getParentPidsFromSnapshot() {
260         Map<Integer, Integer> parentPidMap = new HashMap<>();
261         // Get processes from ToolHelp API for parent PID
262         try (CloseablePROCESSENTRY32ByReference processEntry = new CloseablePROCESSENTRY32ByReference()) {
263             WinNT.HANDLE snapshot = Kernel32.INSTANCE.CreateToolhelp32Snapshot(Tlhelp32.TH32CS_SNAPPROCESS,
264                     new DWORD(0));
265             try {
266                 while (Kernel32.INSTANCE.Process32Next(snapshot, processEntry)) {
267                     parentPidMap.put(processEntry.th32ProcessID.intValue(),
268                             processEntry.th32ParentProcessID.intValue());
269                 }
270             } finally {
271                 Kernel32.INSTANCE.CloseHandle(snapshot);
272             }
273         }
274         return parentPidMap;
275     }
276 
277     @Override
278     public OSProcess getProcess(int pid) {
279         List<OSProcess> procList = processMapToList(Arrays.asList(pid));
280         return procList.isEmpty() ? null : procList.get(0);
281     }
282 
283     private List<OSProcess> processMapToList(Collection<Integer> pids) {
284         // Get data from the registry if possible
285         Map<Integer, ProcessPerformanceData.PerfCounterBlock> processMap = processMapFromRegistry.get();
286         // otherwise performance counters with WMI backup
287         if (processMap == null || processMap.isEmpty()) {
288             processMap = (pids == null) ? processMapFromPerfCounters.get()
289                     : ProcessPerformanceData.buildProcessMapFromPerfCounters(pids);
290         }
291         Map<Integer, ThreadPerformanceData.PerfCounterBlock> threadMap = null;
292         if (USE_PROCSTATE_SUSPENDED) {
293             // Get data from the registry if possible
294             threadMap = threadMapFromRegistry.get();
295             // otherwise performance counters with WMI backup
296             if (threadMap == null || threadMap.isEmpty()) {
297                 threadMap = (pids == null) ? threadMapFromPerfCounters.get()
298                         : ThreadPerformanceData.buildThreadMapFromPerfCounters(pids);
299             }
300         }
301 
302         Map<Integer, WtsInfo> processWtsMap = ProcessWtsData.queryProcessWtsMap(pids);
303 
304         Set<Integer> mapKeys = new HashSet<>(processWtsMap.keySet());
305         mapKeys.retainAll(processMap.keySet());
306 
307         final Map<Integer, ProcessPerformanceData.PerfCounterBlock> finalProcessMap = processMap;
308         final Map<Integer, ThreadPerformanceData.PerfCounterBlock> finalThreadMap = threadMap;
309         return mapKeys.stream().parallel()
310                 .map(pid -> new WindowsOSProcess(pid, this, finalProcessMap, processWtsMap, finalThreadMap))
311                 .filter(VALID_PROCESS).collect(Collectors.toList());
312     }
313 
314     private static Map<Integer, ProcessPerformanceData.PerfCounterBlock> queryProcessMapFromRegistry() {
315         return ProcessPerformanceData.buildProcessMapFromRegistry(null);
316     }
317 
318     private static Map<Integer, ProcessPerformanceData.PerfCounterBlock> queryProcessMapFromPerfCounters() {
319         return ProcessPerformanceData.buildProcessMapFromPerfCounters(null);
320     }
321 
322     private static Map<Integer, ThreadPerformanceData.PerfCounterBlock> queryThreadMapFromRegistry() {
323         return ThreadPerformanceData.buildThreadMapFromRegistry(null);
324     }
325 
326     private static Map<Integer, ThreadPerformanceData.PerfCounterBlock> queryThreadMapFromPerfCounters() {
327         return ThreadPerformanceData.buildThreadMapFromPerfCounters(null);
328     }
329 
330     @Override
331     public int getProcessId() {
332         return Kernel32.INSTANCE.GetCurrentProcessId();
333     }
334 
335     @Override
336     public int getProcessCount() {
337         try (CloseablePerformanceInformation perfInfo = new CloseablePerformanceInformation()) {
338             if (!Psapi.INSTANCE.GetPerformanceInfo(perfInfo, perfInfo.size())) {
339                 LOG.error("Failed to get Performance Info. Error code: {}", Kernel32.INSTANCE.GetLastError());
340                 return 0;
341             }
342             return perfInfo.ProcessCount.intValue();
343         }
344     }
345 
346     @Override
347     public int getThreadId() {
348         return Kernel32.INSTANCE.GetCurrentThreadId();
349     }
350 
351     @Override
352     public OSThread getCurrentThread() {
353         OSProcess proc = getCurrentProcess();
354         final int tid = getThreadId();
355         return proc.getThreadDetails().stream().filter(t -> t.getThreadId() == tid).findFirst()
356                 .orElse(new WindowsOSThread(proc.getProcessID(), tid, null, null));
357     }
358 
359     @Override
360     public int getThreadCount() {
361         try (CloseablePerformanceInformation perfInfo = new CloseablePerformanceInformation()) {
362             if (!Psapi.INSTANCE.GetPerformanceInfo(perfInfo, perfInfo.size())) {
363                 LOG.error("Failed to get Performance Info. Error code: {}", Kernel32.INSTANCE.GetLastError());
364                 return 0;
365             }
366             return perfInfo.ThreadCount.intValue();
367         }
368     }
369 
370     @Override
371     public long getSystemUptime() {
372         return querySystemUptime();
373     }
374 
375     private static long querySystemUptime() {
376         // Uptime is in seconds so divide milliseconds
377         // GetTickCount64 requires Vista (6.0) or later
378         if (IS_VISTA_OR_GREATER) {
379             return Kernel32.INSTANCE.GetTickCount64() / 1000L;
380         } else {
381             // 32 bit rolls over at ~ 49 days
382             return Kernel32.INSTANCE.GetTickCount() / 1000L;
383         }
384     }
385 
386     @Override
387     public long getSystemBootTime() {
388         return BOOTTIME;
389     }
390 
391     private static long querySystemBootTime() {
392         String eventLog = systemLog.get();
393         if (eventLog != null) {
394             try {
395                 EventLogIterator iter = new EventLogIterator(null, eventLog, WinNT.EVENTLOG_BACKWARDS_READ);
396                 // Get the most recent boot event (ID 12) from the Event log. If Windows "Fast
397                 // Startup" is enabled we may not see event 12, so also check for most recent ID
398                 // 6005 (Event log startup) as a reasonably close backup.
399                 long event6005Time = 0L;
400                 while (iter.hasNext()) {
401                     EventLogRecord logRecord = iter.next();
402                     if (logRecord.getStatusCode() == 12) {
403                         // Event 12 is system boot. We want this value unless we find two 6005 events
404                         // first (may occur with Fast Boot)
405                         return logRecord.getRecord().TimeGenerated.longValue();
406                     } else if (logRecord.getStatusCode() == 6005) {
407                         // If we already found one, this means we've found a second one without finding
408                         // an event 12. Return the latest one.
409                         if (event6005Time > 0) {
410                             return event6005Time;
411                         }
412                         // First 6005; tentatively assign
413                         event6005Time = logRecord.getRecord().TimeGenerated.longValue();
414                     }
415                 }
416                 // Only one 6005 found, return
417                 if (event6005Time > 0) {
418                     return event6005Time;
419                 }
420             } catch (Win32Exception e) {
421                 LOG.warn("Can't open event log \"{}\".", eventLog);
422             }
423         }
424         // If we get this far, event log reading has failed, either from no log or no
425         // startup times. Subtract up time from current time as a reasonable proxy.
426         return System.currentTimeMillis() / 1000L - querySystemUptime();
427     }
428 
429     @Override
430     public NetworkParams getNetworkParams() {
431         return new WindowsNetworkParams();
432     }
433 
434     /**
435      * Attempts to enable debug privileges for this process, required for OpenProcess() to get processes other than the
436      * current user. Requires elevated permissions.
437      *
438      * @return {@code true} if debug privileges were successfully enabled.
439      */
440     private static boolean enableDebugPrivilege() {
441         try (CloseableHANDLEByReference hToken = new CloseableHANDLEByReference()) {
442             boolean success = Advapi32.INSTANCE.OpenProcessToken(Kernel32.INSTANCE.GetCurrentProcess(),
443                     WinNT.TOKEN_QUERY | WinNT.TOKEN_ADJUST_PRIVILEGES, hToken);
444             if (!success) {
445                 LOG.error("OpenProcessToken failed. Error: {}", Native.getLastError());
446                 return false;
447             }
448             try {
449                 LUID luid = new LUID();
450                 success = Advapi32.INSTANCE.LookupPrivilegeValue(null, WinNT.SE_DEBUG_NAME, luid);
451                 if (!success) {
452                     LOG.error("LookupPrivilegeValue failed. Error: {}", Native.getLastError());
453                     return false;
454                 }
455                 WinNT.TOKEN_PRIVILEGES tkp = new WinNT.TOKEN_PRIVILEGES(1);
456                 tkp.Privileges[0] = new WinNT.LUID_AND_ATTRIBUTES(luid, new DWORD(WinNT.SE_PRIVILEGE_ENABLED));
457                 success = Advapi32.INSTANCE.AdjustTokenPrivileges(hToken.getValue(), false, tkp, 0, null, null);
458                 int err = Native.getLastError();
459                 if (!success) {
460                     LOG.error("AdjustTokenPrivileges failed. Error: {}", err);
461                     return false;
462                 } else if (err == WinError.ERROR_NOT_ALL_ASSIGNED) {
463                     LOG.debug("Debug privileges not enabled.");
464                     return false;
465                 }
466             } finally {
467                 Kernel32.INSTANCE.CloseHandle(hToken.getValue());
468             }
469         }
470         return true;
471     }
472 
473     @Override
474     public List<OSService> getServices() {
475         try (W32ServiceManager sm = new W32ServiceManager()) {
476             sm.open(Winsvc.SC_MANAGER_ENUMERATE_SERVICE);
477             Winsvc.ENUM_SERVICE_STATUS_PROCESS[] services = sm.enumServicesStatusExProcess(WinNT.SERVICE_WIN32,
478                     Winsvc.SERVICE_STATE_ALL, null);
479             List<OSService> svcArray = new ArrayList<>();
480             for (Winsvc.ENUM_SERVICE_STATUS_PROCESS service : services) {
481                 State state;
482                 switch (service.ServiceStatusProcess.dwCurrentState) {
483                 case 1:
484                     state = STOPPED;
485                     break;
486                 case 4:
487                     state = RUNNING;
488                     break;
489                 default:
490                     state = OTHER;
491                     break;
492                 }
493                 svcArray.add(new OSService(service.lpDisplayName, service.ServiceStatusProcess.dwProcessId, state));
494             }
495             return svcArray;
496         } catch (com.sun.jna.platform.win32.Win32Exception ex) {
497             LOG.error("Win32Exception: {}", ex.getMessage());
498             return Collections.emptyList();
499         }
500     }
501 
502     private static String querySystemLog() {
503         String systemLog = GlobalConfig.get(GlobalConfig.OSHI_OS_WINDOWS_EVENTLOG, "System");
504         if (systemLog.isEmpty()) {
505             // Use faster boot time approximation
506             return null;
507         }
508         // Check whether it works
509         HANDLE h = Advapi32.INSTANCE.OpenEventLog(null, systemLog);
510         if (h == null) {
511             LOG.warn("Unable to open configured system Event log \"{}\". Calculating boot time from uptime.",
512                     systemLog);
513             return null;
514         }
515         return systemLog;
516     }
517 
518     @Override
519     public List<OSDesktopWindow> getDesktopWindows(boolean visibleOnly) {
520         return EnumWindows.queryDesktopWindows(visibleOnly);
521     }
522 
523     /*
524      * Package-private methods for use by WindowsOSProcess to limit process memory queries to processes with same
525      * bitness as the current one
526      */
527     /**
528      * Is the processor architecture x86?
529      *
530      * @return true if the processor architecture is Intel x86
531      */
532     static boolean isX86() {
533         return X86;
534     }
535 
536     private static boolean isCurrentX86() {
537         try (CloseableSystemInfo sysinfo = new CloseableSystemInfo()) {
538             Kernel32.INSTANCE.GetNativeSystemInfo(sysinfo);
539             return (0 == sysinfo.processorArchitecture.pi.wProcessorArchitecture.intValue());
540         }
541     }
542 
543     /**
544      * Is the current operating process x86 or x86-compatibility mode?
545      *
546      * @return true if the current process is 32-bit
547      */
548     static boolean isWow() {
549         return WOW;
550     }
551 
552     /**
553      * Is the specified process x86 or x86-compatibility mode?
554      *
555      * @param h The handle to the processs to check
556      * @return true if the process is 32-bit
557      */
558     static boolean isWow(HANDLE h) {
559         if (X86) {
560             return true;
561         }
562         try (CloseableIntByReference isWow = new CloseableIntByReference()) {
563             Kernel32.INSTANCE.IsWow64Process(h, isWow);
564             return isWow.getValue() != 0;
565         }
566     }
567 
568     private static boolean isCurrentWow() {
569         if (X86) {
570             return true;
571         }
572         HANDLE h = Kernel32.INSTANCE.GetCurrentProcess();
573         return (h == null) ? false : isWow(h);
574     }
575 }