View Javadoc
1   /*
2    * Copyright 2020-2025 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.windows;
6   
7   import static oshi.software.os.OSProcess.State.INVALID;
8   import static oshi.software.os.OSProcess.State.RUNNING;
9   import static oshi.software.os.OSProcess.State.SUSPENDED;
10  import static oshi.util.Memoizer.memoize;
11  
12  import java.io.File;
13  import java.util.Arrays;
14  import java.util.Collections;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  import java.util.function.Supplier;
19  import java.util.stream.Collectors;
20  
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import com.sun.jna.Memory;
25  import com.sun.jna.Pointer;
26  import com.sun.jna.platform.win32.Advapi32;
27  import com.sun.jna.platform.win32.Advapi32Util;
28  import com.sun.jna.platform.win32.Advapi32Util.Account;
29  import com.sun.jna.platform.win32.Kernel32;
30  import com.sun.jna.platform.win32.Kernel32Util;
31  import com.sun.jna.platform.win32.Shell32Util;
32  import com.sun.jna.platform.win32.VersionHelpers;
33  import com.sun.jna.platform.win32.Win32Exception;
34  import com.sun.jna.platform.win32.WinError;
35  import com.sun.jna.platform.win32.WinNT;
36  import com.sun.jna.platform.win32.WinNT.HANDLE;
37  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
38  
39  import oshi.annotation.concurrent.ThreadSafe;
40  import oshi.driver.windows.registry.ProcessPerformanceData;
41  import oshi.driver.windows.registry.ProcessWtsData;
42  import oshi.driver.windows.registry.ProcessWtsData.WtsInfo;
43  import oshi.driver.windows.registry.ThreadPerformanceData;
44  import oshi.driver.windows.wmi.Win32Process;
45  import oshi.driver.windows.wmi.Win32Process.CommandLineProperty;
46  import oshi.driver.windows.wmi.Win32ProcessCached;
47  import oshi.jna.ByRef.CloseableHANDLEByReference;
48  import oshi.jna.ByRef.CloseableIntByReference;
49  import oshi.jna.ByRef.CloseableULONGptrByReference;
50  import oshi.jna.platform.windows.NtDll;
51  import oshi.jna.platform.windows.NtDll.UNICODE_STRING;
52  import oshi.software.common.AbstractOSProcess;
53  import oshi.software.os.OSThread;
54  import oshi.util.Constants;
55  import oshi.util.GlobalConfig;
56  import oshi.util.ParseUtil;
57  import oshi.util.platform.windows.WmiUtil;
58  import oshi.util.tuples.Pair;
59  import oshi.util.tuples.Triplet;
60  
61  /**
62   * OSProcess implementation
63   */
64  @ThreadSafe
65  public class WindowsOSProcess extends AbstractOSProcess {
66  
67      private static final Logger LOG = LoggerFactory.getLogger(WindowsOSProcess.class);
68  
69      private static final boolean USE_BATCH_COMMANDLINE = GlobalConfig
70              .get(GlobalConfig.OSHI_OS_WINDOWS_COMMANDLINE_BATCH, false);
71  
72      private static final boolean USE_PROCSTATE_SUSPENDED = GlobalConfig
73              .get(GlobalConfig.OSHI_OS_WINDOWS_PROCSTATE_SUSPENDED, false);
74  
75      private static final boolean IS_VISTA_OR_GREATER = VersionHelpers.IsWindowsVistaOrGreater();
76      private static final boolean IS_WINDOWS7_OR_GREATER = VersionHelpers.IsWindows7OrGreater();
77  
78      // track the OperatingSystem object that created this
79      private final WindowsOperatingSystem os;
80  
81      private Supplier<Pair<String, String>> userInfo = memoize(this::queryUserInfo);
82      private Supplier<Pair<String, String>> groupInfo = memoize(this::queryGroupInfo);
83      private Supplier<String> currentWorkingDirectory = memoize(this::queryCwd);
84      private Supplier<String> commandLine = memoize(this::queryCommandLine);
85      private Supplier<List<String>> args = memoize(this::queryArguments);
86      private Supplier<Triplet<String, String, Map<String, String>>> cwdCmdEnv = memoize(
87              this::queryCwdCommandlineEnvironment);
88      private Map<Integer, ThreadPerformanceData.PerfCounterBlock> tcb;
89  
90      private String name;
91      private String path;
92      private State state = INVALID;
93      private int parentProcessID;
94      private int threadCount;
95      private int priority;
96      private long virtualSize;
97      private long residentSetSize;
98      private long kernelTime;
99      private long userTime;
100     private long startTime;
101     private long upTime;
102     private long bytesRead;
103     private long bytesWritten;
104     private long openFiles;
105     private int bitness;
106     private long pageFaults;
107 
108     public WindowsOSProcess(int pid, WindowsOperatingSystem os,
109             Map<Integer, ProcessPerformanceData.PerfCounterBlock> processMap, Map<Integer, WtsInfo> processWtsMap,
110             Map<Integer, ThreadPerformanceData.PerfCounterBlock> threadMap) {
111         super(pid);
112         // Save a copy of OS creating this object for later use
113         this.os = os;
114         // Initially set to match OS bitness. If 64 will check later for 32-bit process
115         this.bitness = os.getBitness();
116         // Initialize thread counters
117         this.tcb = threadMap;
118         updateAttributes(processMap.get(pid), processWtsMap.get(pid));
119     }
120 
121     @Override
122     public String getName() {
123         return this.name;
124     }
125 
126     @Override
127     public String getPath() {
128         return this.path;
129     }
130 
131     @Override
132     public String getCommandLine() {
133         return this.commandLine.get();
134     }
135 
136     @Override
137     public List<String> getArguments() {
138         return args.get();
139     }
140 
141     @Override
142     public Map<String, String> getEnvironmentVariables() {
143         return cwdCmdEnv.get().getC();
144     }
145 
146     @Override
147     public String getCurrentWorkingDirectory() {
148         return currentWorkingDirectory.get();
149     }
150 
151     @Override
152     public String getUser() {
153         return userInfo.get().getA();
154     }
155 
156     @Override
157     public String getUserID() {
158         return userInfo.get().getB();
159     }
160 
161     @Override
162     public String getGroup() {
163         return groupInfo.get().getA();
164     }
165 
166     @Override
167     public String getGroupID() {
168         return groupInfo.get().getB();
169     }
170 
171     @Override
172     public State getState() {
173         return this.state;
174     }
175 
176     @Override
177     public int getParentProcessID() {
178         return this.parentProcessID;
179     }
180 
181     @Override
182     public int getThreadCount() {
183         return this.threadCount;
184     }
185 
186     @Override
187     public int getPriority() {
188         return this.priority;
189     }
190 
191     @Override
192     public long getVirtualSize() {
193         return this.virtualSize;
194     }
195 
196     @Override
197     public long getResidentSetSize() {
198         return this.residentSetSize;
199     }
200 
201     @Override
202     public long getKernelTime() {
203         return this.kernelTime;
204     }
205 
206     @Override
207     public long getUserTime() {
208         return this.userTime;
209     }
210 
211     @Override
212     public long getUpTime() {
213         return this.upTime;
214     }
215 
216     @Override
217     public long getStartTime() {
218         return this.startTime;
219     }
220 
221     @Override
222     public long getBytesRead() {
223         return this.bytesRead;
224     }
225 
226     @Override
227     public long getBytesWritten() {
228         return this.bytesWritten;
229     }
230 
231     @Override
232     public long getOpenFiles() {
233         return this.openFiles;
234     }
235 
236     @Override
237     public long getSoftOpenFileLimit() {
238         return WindowsFileSystem.MAX_WINDOWS_HANDLES;
239     }
240 
241     @Override
242     public long getHardOpenFileLimit() {
243         return WindowsFileSystem.MAX_WINDOWS_HANDLES;
244     }
245 
246     @Override
247     public int getBitness() {
248         return this.bitness;
249     }
250 
251     @Override
252     public long getAffinityMask() {
253         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
254         if (pHandle != null) {
255             try (CloseableULONGptrByReference processAffinity = new CloseableULONGptrByReference();
256                     CloseableULONGptrByReference systemAffinity = new CloseableULONGptrByReference()) {
257                 if (Kernel32.INSTANCE.GetProcessAffinityMask(pHandle, processAffinity, systemAffinity)) {
258                     return Pointer.nativeValue(processAffinity.getValue().toPointer());
259                 }
260             } finally {
261                 Kernel32.INSTANCE.CloseHandle(pHandle);
262             }
263         }
264         return 0L;
265     }
266 
267     @Override
268     public long getMinorFaults() {
269         return this.pageFaults;
270     }
271 
272     @Override
273     public List<OSThread> getThreadDetails() {
274         Map<Integer, ThreadPerformanceData.PerfCounterBlock> threads = tcb == null
275                 ? queryMatchingThreads(Collections.singleton(this.getProcessID()))
276                 : tcb;
277         return threads.entrySet().stream().parallel()
278                 .filter(entry -> entry.getValue().getOwningProcessID() == this.getProcessID())
279                 .map(entry -> new WindowsOSThread(getProcessID(), entry.getKey(), this.name, entry.getValue()))
280                 .collect(Collectors.toList());
281     }
282 
283     @Override
284     public boolean updateAttributes() {
285         Set<Integer> pids = Collections.singleton(this.getProcessID());
286         // Get data from the registry if possible
287         Map<Integer, ProcessPerformanceData.PerfCounterBlock> pcb = ProcessPerformanceData
288                 .buildProcessMapFromRegistry(null);
289         // otherwise performance counters with WMI backup
290         if (pcb == null) {
291             pcb = ProcessPerformanceData.buildProcessMapFromPerfCounters(pids);
292         }
293         if (USE_PROCSTATE_SUSPENDED) {
294             this.tcb = queryMatchingThreads(pids);
295         }
296         Map<Integer, WtsInfo> wts = ProcessWtsData.queryProcessWtsMap(pids);
297         return updateAttributes(pcb.get(this.getProcessID()), wts.get(this.getProcessID()));
298     }
299 
300     private boolean updateAttributes(ProcessPerformanceData.PerfCounterBlock pcb, WtsInfo wts) {
301         this.name = pcb.getName();
302         this.path = wts.getPath(); // Empty string for Win7+
303         this.parentProcessID = pcb.getParentProcessID();
304         this.threadCount = wts.getThreadCount();
305         this.priority = pcb.getPriority();
306         this.virtualSize = wts.getVirtualSize();
307         this.residentSetSize = pcb.getResidentSetSize();
308         this.kernelTime = wts.getKernelTime();
309         this.userTime = wts.getUserTime();
310         this.startTime = pcb.getStartTime();
311         this.upTime = pcb.getUpTime();
312         this.bytesRead = pcb.getBytesRead();
313         this.bytesWritten = pcb.getBytesWritten();
314         this.openFiles = wts.getOpenFiles();
315         this.pageFaults = pcb.getPageFaults();
316 
317         // There are only 3 possible Process states on Windows: RUNNING, SUSPENDED, or
318         // UNKNOWN. Processes are considered running unless all of their threads are
319         // SUSPENDED.
320         this.state = RUNNING;
321         if (this.tcb != null) {
322             // If user hasn't enabled this in properties, we ignore
323             int pid = this.getProcessID();
324             // If any thread is NOT suspended, set running
325             for (ThreadPerformanceData.PerfCounterBlock tpd : this.tcb.values()) {
326                 if (tpd.getOwningProcessID() == pid) {
327                     if (tpd.getThreadWaitReason() == 5) {
328                         this.state = SUSPENDED;
329                     } else {
330                         this.state = RUNNING;
331                         break;
332                     }
333                 }
334             }
335         }
336 
337         // Get a handle to the process for various extended info. Only gets
338         // current user unless running as administrator
339         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
340         if (pHandle != null) {
341             try {
342                 // Test for 32-bit process on 64-bit windows
343                 if (IS_VISTA_OR_GREATER && this.bitness == 64) {
344                     try (CloseableIntByReference wow64 = new CloseableIntByReference()) {
345                         if (Kernel32.INSTANCE.IsWow64Process(pHandle, wow64) && wow64.getValue() > 0) {
346                             this.bitness = 32;
347                         }
348                     }
349                 }
350                 try { // EXECUTABLEPATH
351                     if (IS_WINDOWS7_OR_GREATER) {
352                         this.path = Kernel32Util.QueryFullProcessImageName(pHandle, 0);
353                     }
354                 } catch (Win32Exception e) {
355                     this.state = INVALID;
356                 }
357             } finally {
358                 Kernel32.INSTANCE.CloseHandle(pHandle);
359             }
360         }
361 
362         return !this.state.equals(INVALID);
363     }
364 
365     private Map<Integer, ThreadPerformanceData.PerfCounterBlock> queryMatchingThreads(Set<Integer> pids) {
366         // fetch from registry
367         Map<Integer, ThreadPerformanceData.PerfCounterBlock> threads = ThreadPerformanceData
368                 .buildThreadMapFromRegistry(pids);
369         // otherwise performance counters with WMI backup
370         if (threads == null) {
371             threads = ThreadPerformanceData.buildThreadMapFromPerfCounters(pids, this.getName(), -1);
372         }
373         return threads;
374     }
375 
376     private String queryCommandLine() {
377         // Try to fetch from process memory
378         if (!cwdCmdEnv.get().getB().isEmpty()) {
379             return cwdCmdEnv.get().getB();
380         }
381         // If using batch mode fetch from WMI Cache
382         if (USE_BATCH_COMMANDLINE) {
383             return Win32ProcessCached.getInstance().getCommandLine(getProcessID(), getStartTime());
384         }
385         // If no cache enabled, query line by line
386         WmiResult<CommandLineProperty> commandLineProcs = Win32Process
387                 .queryCommandLines(Collections.singleton(getProcessID()));
388         if (commandLineProcs.getResultCount() > 0) {
389             return WmiUtil.getString(commandLineProcs, CommandLineProperty.COMMANDLINE, 0);
390         }
391         return "";
392     }
393 
394     private List<String> queryArguments() {
395         String cl = getCommandLine();
396         if (!cl.isEmpty()) {
397             return Arrays.asList(Shell32Util.CommandLineToArgv(cl));
398         }
399         return Collections.emptyList();
400     }
401 
402     private String queryCwd() {
403         // Try to fetch from process memory
404         if (!cwdCmdEnv.get().getA().isEmpty()) {
405             return cwdCmdEnv.get().getA();
406         }
407         // For executing process, set CWD
408         if (getProcessID() == this.os.getProcessId()) {
409             String cwd = new File(".").getAbsolutePath();
410             // trim off trailing "."
411             if (!cwd.isEmpty()) {
412                 return cwd.substring(0, cwd.length() - 1);
413             }
414         }
415         return "";
416     }
417 
418     private Pair<String, String> queryUserInfo() {
419         Pair<String, String> pair = null;
420         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
421         if (pHandle != null) {
422             try (CloseableHANDLEByReference phToken = new CloseableHANDLEByReference()) {
423                 try {
424                     if (Advapi32.INSTANCE.OpenProcessToken(pHandle, WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY,
425                             phToken)) {
426                         Account account = Advapi32Util.getTokenAccount(phToken.getValue());
427                         pair = new Pair<>(account.name, account.sidString);
428                     } else {
429                         int error = Kernel32.INSTANCE.GetLastError();
430                         // Access denied errors are common. Fail silently.
431                         if (error != WinError.ERROR_ACCESS_DENIED) {
432                             LOG.error("Failed to get process token for process {}: {}", getProcessID(),
433                                     Kernel32.INSTANCE.GetLastError());
434                         }
435                     }
436                 } catch (Win32Exception e) {
437                     LOG.warn("Failed to query user info for process {} ({}): {}", getProcessID(), getName(),
438                             e.getMessage());
439                 } finally {
440                     final HANDLE token = phToken.getValue();
441                     if (token != null) {
442                         Kernel32.INSTANCE.CloseHandle(token);
443                     }
444                     Kernel32.INSTANCE.CloseHandle(pHandle);
445                 }
446             }
447         }
448         if (pair == null) {
449             return new Pair<>(Constants.UNKNOWN, Constants.UNKNOWN);
450         }
451         return pair;
452     }
453 
454     private Pair<String, String> queryGroupInfo() {
455         Pair<String, String> pair = null;
456         final HANDLE pHandle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, getProcessID());
457         if (pHandle != null) {
458             try (CloseableHANDLEByReference phToken = new CloseableHANDLEByReference()) {
459                 if (Advapi32.INSTANCE.OpenProcessToken(pHandle, WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY, phToken)) {
460                     Account account = Advapi32Util.getTokenPrimaryGroup(phToken.getValue());
461                     pair = new Pair<>(account.name, account.sidString);
462                 } else {
463                     int error = Kernel32.INSTANCE.GetLastError();
464                     // Access denied errors are common. Fail silently.
465                     if (error != WinError.ERROR_ACCESS_DENIED) {
466                         LOG.error("Failed to get process token for process {}: {}", getProcessID(),
467                                 Kernel32.INSTANCE.GetLastError());
468                     }
469                 }
470                 final HANDLE token = phToken.getValue();
471                 if (token != null) {
472                     Kernel32.INSTANCE.CloseHandle(token);
473                 }
474                 Kernel32.INSTANCE.CloseHandle(pHandle);
475             }
476         }
477         if (pair == null) {
478             return new Pair<>(Constants.UNKNOWN, Constants.UNKNOWN);
479         }
480         return pair;
481     }
482 
483     private Triplet<String, String, Map<String, String>> queryCwdCommandlineEnvironment() {
484         // Get the process handle
485         HANDLE h = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION | WinNT.PROCESS_VM_READ, false,
486                 getProcessID());
487         if (h != null) {
488             try {
489                 // Can't check 32-bit procs from a 64-bit one
490                 if (WindowsOperatingSystem.isX86() == WindowsOperatingSystem.isWow(h)) {
491                     try (CloseableIntByReference nRead = new CloseableIntByReference()) {
492                         // Start by getting the address of the PEB
493                         NtDll.PROCESS_BASIC_INFORMATION pbi = new NtDll.PROCESS_BASIC_INFORMATION();
494                         int ret = NtDll.INSTANCE.NtQueryInformationProcess(h, NtDll.PROCESS_BASIC_INFORMATION,
495                                 pbi.getPointer(), pbi.size(), nRead);
496                         if (ret != 0) {
497                             return defaultCwdCommandlineEnvironment();
498                         }
499                         pbi.read();
500 
501                         // Now fetch the PEB
502                         NtDll.PEB peb = new NtDll.PEB();
503                         Kernel32.INSTANCE.ReadProcessMemory(h, pbi.PebBaseAddress, peb.getPointer(), peb.size(), nRead);
504                         if (nRead.getValue() == 0) {
505                             return defaultCwdCommandlineEnvironment();
506                         }
507                         peb.read();
508 
509                         // Now fetch the Process Parameters structure containing our data
510                         NtDll.RTL_USER_PROCESS_PARAMETERS upp = new NtDll.RTL_USER_PROCESS_PARAMETERS();
511                         Kernel32.INSTANCE.ReadProcessMemory(h, peb.ProcessParameters, upp.getPointer(), upp.size(),
512                                 nRead);
513                         if (nRead.getValue() == 0) {
514                             return defaultCwdCommandlineEnvironment();
515                         }
516                         upp.read();
517 
518                         // Get CWD and Command Line strings here
519                         String cwd = readUnicodeString(h, upp.CurrentDirectory.DosPath);
520                         String cl = readUnicodeString(h, upp.CommandLine);
521 
522                         // Fetch the Environment Strings
523                         int envSize = upp.EnvironmentSize.intValue();
524                         if (envSize > 0) {
525                             try (Memory buffer = new Memory(envSize)) {
526                                 Kernel32.INSTANCE.ReadProcessMemory(h, upp.Environment, buffer, envSize, nRead);
527                                 if (nRead.getValue() > 0) {
528                                     char[] env = buffer.getCharArray(0, envSize / 2);
529                                     Map<String, String> envMap = ParseUtil.parseCharArrayToStringMap(env);
530                                     // First entry in Environment is "=::=::\"
531                                     envMap.remove("");
532                                     return new Triplet<>(cwd, cl, Collections.unmodifiableMap(envMap));
533                                 }
534                             }
535                         }
536                         return new Triplet<>(cwd, cl, Collections.emptyMap());
537                     }
538                 }
539             } finally {
540                 Kernel32.INSTANCE.CloseHandle(h);
541             }
542         }
543         return defaultCwdCommandlineEnvironment();
544     }
545 
546     private static Triplet<String, String, Map<String, String>> defaultCwdCommandlineEnvironment() {
547         return new Triplet<>("", "", Collections.emptyMap());
548     }
549 
550     private static String readUnicodeString(HANDLE h, UNICODE_STRING s) {
551         if (s.Length > 0) {
552             // Add space for null terminator
553             try (Memory m = new Memory(s.Length + 2L); CloseableIntByReference nRead = new CloseableIntByReference()) {
554                 m.clear(); // really only need null in last 2 bytes but this is easier
555                 Kernel32.INSTANCE.ReadProcessMemory(h, s.Buffer, m, s.Length, nRead);
556                 if (nRead.getValue() > 0) {
557                     return m.getWideString(0);
558                 }
559             }
560         }
561         return "";
562     }
563 }