-
Notifications
You must be signed in to change notification settings - Fork 0
/
ProcessFinder (ProcessEnum).cs
7816 lines (6589 loc) · 306 KB
/
ProcessFinder (ProcessEnum).cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#region Classes & Methods within:
// --------------------
// Classes contained:
// --------------------
//
// ProcessTerminateGraceful. [ WM_QUIT & WM_CLOSE to all Windows of all threads of all processes ]
// ProcessTerminateForceful. [ TerminateProcess ]
// FindWindowExtended.
// ProcessEnum.
// ProcessInformationReading.
// ProcessModify. [Critical unsetting]
//
//
// --------------------
// Notable methods
// --------------------
//
// ProcessTerminateGraceful.
// .CloseAllWindows()
// .CloseAllExplorerFileWindows()
// .TerminateProcessGraceful
// .TerminateProcessGracefulByWindowName()
// ProcessTerminateForceful.
// .TerminateProcess_API
// .TerminateProcessForcefulByWindowName()
// FindWindowExtended.
// .FindWindowLike()
// ProcessEnum.
// .EnumProcesses (.GetAllProcesses as its Alias)
// .GetAllProcessIDs()
// .IsProcessRunning()
// .FindProcessByName() (.GetProcessByName as its Alias)
// .GetProcessInfoByPID()
// .EnumModulesForProcess
// .GetProcessInfoByFullFileNameWin32
// .GetFullFileNameOfProcess_ViaModuleLookup
// .EnumThreadsForProcess_Toolhelp
//
// ProcessInformationReading.
// .GetWindowThreadProcessId (API)
// ProcessModify.
// .SetCriticalProcess
#endregion
// "Ctrl M, O is your Friend"
//
// Author: https://github.com/AltF5
// Created: November 2020
// History: November 5th 2021 - Improved RestartExplorerGracefully()
// June 28th 2022 - Added SetProcessCritical & listed out notable methods (above)
// Feb 12th 2024 - Bugfix: ProcessEnum.EnumInfoRequest.Basic wasn't including the process name, unless EnumInfoRequest.Path was supplied
// - Added: IsProcessRunning_ByPath
//
//
//
//
// What:
// What is this ProcessFinder.cs file?
// This is a fast, light-weight, self-contained, grab-n-go, 0-dependency Utility class housing 3x static classes & static methods.
// I try to opt for minimalism and a "try your best" approach to all implemented methods, avoiding blowups (albeit perhaps somewhere, in some cases, could swallow possible Exceptions, that I'm sure could be improved. Please by all means.)
// I typically opt for Win32 (Windows API) or ntdll (Native API) [If I don't pull out my hair over PInvoking it] over .NET implementations, unless the .NET src reveals otherwise. In this case the .NET process enum appears flawed from my tests (perhaps due to calling from a lower integrity process (Medium IL) where not all processes are returned)
//
// Overall, this is a culmination of needs, research, and Native API tests.
// Starting point references were best-attempted to be placed where suited, such as which methods are based on what StackOverflow answers, github declarations, and msdn forums.
// It was also helpful to utilize the ["released"] Windows XP SP1 src as reference to some questions on how things are implemented within (such as toolhelp.c)
// which reveals how Process and Thread enum via toolhelp utilize the same NTQSI call, but Module enum is a different Rtl Debugger call
//
// It was created Nov 2020, but some methods I snagged from other code projects I did (hence there many be some dates laying around like "Created 2017" etc
//
// 3x of the classes this file contains:
//
// 1 - A Process Terminator
// > PID - Posts WM_QUIT and WM_CLOSE for Grace | OpenProcess(PROCESS_TERMINATE) + TerminateProcess call for Force
// > Name - Enumerates all processes, and terminates all matching (Gracefully or Forcefully)
// > Window Title - GetWindowThreadProcessId for hWindow --> PID
//
// 2 - A robust process enumerator via Toolhelp or Native (NtQuerySystemInformation)
// > Obtains desried information, including a full module list, Integrity, Privileges, etx
//
// 3 - A process information query class to read info such as the Process Command Line, or Parent PID from the PEB
//
// 4 - ******* TO DO ******** - File unlocker via 'Rm' (Resource Manager) APIs
//
// Why:
// Why the need for the ProcessEnum class?
// - I've noticed the .NET GetAllProcesses doesn't always (almost always!) load all processes, and GetProcessesByName does not always find the process requested.
// I haven't investigated too deep into the "why" since I already knew the "how" for implementing a true Process Enumeration via Windows API
// From initial tests it has to do if the caller (this code) of System.Diagnostics.Process.GetProcesses() or .GetProcessesByName("notepad.exe") isn't running elevated (this is of Medium IL)
//
// - I have noticed very slow performance with the .NET Process class, and thus want to avoid using it
// It was especially apparent when utilizing Process.GetProcessById for every process on the system, which was/is VERY slow -- https://github.com/dotnet/runtime/issues/20725
// This process class is significantly more efficient
//
// - It was also good to experiment how processes respond to all of their threads being posted the WM_QUIT and WM_CLOSE window messages for graceful & proper
// process termination, rather than just blindly calling TerminateProcess without giving it a chance to perform its cleanup needed.
//
//
//
//
// TO DO:
// - 11-11-2020 - To integrate: 4th features: ProcessFileUtil for Rm file unlocking
//
//
//
// Notable methods:
// - ProcessTerminateGraceful.TerminateProcessGraceful("notepad.exe");
// - ProcessTerminateGraceful.TerminateProcessForcefulByWindowName("%notepad++%");
// - ProcessTerminateGraceful.GetProcessByFullFileNameWin32(@"c:\windows\explorer.exe");
//
// - ProcessTerminateGraceful.CloseAllExplorerWindows();
// - ProcessTerminateClean.RestartExplorerGracefully();
// - ProcessTerminateClean.TerminateExplorerGracefully();
//
// - var test = ProcessTerminateGraceful.CloseAllWindows(true, true)
//
// - var ps = ProcessEnum.EnumProcesses().OrderByDescending(x => x.StartOrderFromEnum).ToList();
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
#region 1 - Process Termination via Gracefully & Forcefully classes
// Notes - This is how taskmgr.exe operates
// https://stackoverflow.com/a/10765274
//
// [Applications] tab displaying Windows:
// Attempt 1:
// When you click the "X" button in the title bar of an application's window, that sends the window a 'WM_CLOSE' message. This is a "graceful" shutdown—the application processes the message, handles any necessary cleanup tasks, and can even refuse to shut down if it so desires (by returning zero in response to the message). WM_CLOSE is simply a request that the window or application terminate; the window is not destroyed until the application itself calls the DestroyWindow function.
// Pressing "End Task" button in Task Manager, [it] will first try to send the application (if it is a GUI application) a 'WM_CLOSE' message. In other words, it first asks nicely and gives the app a chance to terminate itself cleanly.*
//
// Attempt 2: TerminateProcess()
// [If the process fails to] close in response to that initial WM_CLOSE message, the Task Manager will follow up by calling the TerminateProcess() function. This function is a little bit different because it forcefully terminates the application's process and all of its threads without asking for permission from the app. This is a very harsh method of closing something, and should be used as a last resort—such as when an application is hung and is no longer responding to messages.
// TerminateProcess() is a very low-level function that essentially rips the user-mode part of a process from memory, forcing it to terminate unconditionally. Calling TerminateProcess() bypasses such niceties as close notifications and DLL_PROCESS_DETACH.
// Your application does not have the ability to refuse to close, and there is no way to catch/trap/hook calls to TerminateProcess(). All user-mode code in the process simply stops running for good. This is a very unclean shut down procedure, somewhat akin to jerking the computer's power plug out of the wall.
//
//
// [Processes] tab:
// [The first] step is skipped and the TerminateProcess() function is called immediately. This distinction is reflected in the caption on the respective buttons. For the "Applications" tab, the button is lableled "End Task"; for the "Processes" tab, the button is labeled "End Process".
public static class ProcessTerminateGraceful
{
#region -- APIs - Windows, Threads --
#region Thread Windows
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
#endregion
#region Windows Enum (via GetWindow) & Message sending
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
const int WM_CLOSE = 0x0010;
const uint WM_QUIT = 0x0012;
[DllImport("user32")]
static extern IntPtr GetWindow(IntPtr hwnd, int wCmd);
[DllImport("user32.dll")]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
static extern IntPtr GetShellWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
#endregion
#endregion
#region Last Err
static int _lastAPIError = 0;
public static int LastAPIErrorCode
{
get
{
return _lastAPIError;
}
}
#endregion
#region [Public] Close All Windows (Send WM_QUIT + WM_Close to all Thread windows)
/// <summary>
/// NOTICE: Best to run Elevated (High IL), otherwise PostMessage will fail to elevated apps (if this application is not running elevated itself)
/// "Requests" all Process' Thread's Windows close in the entire OS by sending WM_QUIT & WM_CLOSE
///
/// Note: For PostMessage to succeed, then current application must be running at or Equal to the same IL (Integrity Level) as what is being sent
/// Otherwise Access Denied will occur when sending that window message to a higher IL process. UIPI is what blocks it (sidenote: which could be disabled via .dll injection)
/// </summary>
public static void CloseAllWindows(bool skipBrowsers = false, bool skipVisualStudio = false, List<string> processNamesToExclude = null)
{
// Example call:
//
// List<string> exclude = new List<string>();
// exclude.Add("aida64.exe");
// exclude.Add("devenv.exe");
// exclude.Add("DisplayFusion.exe");
// exclude.Add("googledrivesync.exe");
// exclude.Add("icue.exe");
// exclude.Add("notepad++.exe");
// exclude.Add("NZXT CAM.exe");
// exclude.Add("Plex Media Server.exe");
// exclude.Add("ProcessHacker.exe");
// exclude.Add("setpoint.exe");
// exclude.Add("snagit32.exe");
// exclude.Add("treesize.exe");
// exclude.Add("teamviewer.exe");
// exclude.Add("XYplorer.exe");
// ProcessTerminateGraceful.CloseAllWindows(true, true, exclude);
// Go ahead and handle explorer 1st (could do it last too, which is fine)
CloseAllExplorerFileWindows();
List<ProcessEnum.ProcessInfo> all = ProcessEnum.EnumProcessesAndThreads_Native_MoreInfoAndFast();
// This is still causing the taskbar and windows explorer to terminiate surprisingly
// Must ALSO NOT send this to PID 0 (Idle process) which will send the message to Windows Explorer to quit as well (interesting)
List<ProcessEnum.ProcessInfo> processesMinusExplorer = all.Where(x => x.Name.ToLower() != "explorer.exe" && x.PID > 0).ToList();
if (skipBrowsers)
{
processesMinusExplorer = processesMinusExplorer.Where(x =>
x.Name.ToLower() != "chrome.exe" &&
x.Name.ToLower() != "firefox.exe" &&
x.Name.ToLower() != "msedge.exe").ToList();
}
if (skipVisualStudio)
{
processesMinusExplorer = processesMinusExplorer.Where(x =>
x.Name.ToLower() != "devenv.exe").ToList();
}
if (processNamesToExclude != null)
{
// From: stackoverflow.com/a/22160480/5555423 - Exclude items from a list
processesMinusExplorer = processesMinusExplorer
.Except(processNamesToExclude, mainList => mainList.Name.ToLower(), exlList => exlList.ToLower()).ToList();
}
foreach (ProcessEnum.ProcessInfo p in processesMinusExplorer)
{
TerminateProcessGraceful(p);
}
}
public static void CloseAllExplorerFileWindows()
{
// Explorer somewhat after this, when opening new windows
IntPtr hWindow_TopDesktopWindow = GetDesktopWindow();
IntPtr hWindow_TopShellWindow = GetShellWindow(); // Progman : Program Manager
IntPtr hWindow_Taskbar = FindWindow("Shell_TrayWnd", "");
IntPtr hWindow_Taskbar2 = FindWindow("Shell_SecondaryTrayWnd", ""); // Creates 1 per monitor
uint threadIDOfTaskbarToSkip = GetWindowThreadProcessId(hWindow_Taskbar, out uint ignore);
uint threadIDOfTaskbarToSkip2 = GetWindowThreadProcessId(hWindow_Taskbar2, out uint ignore2);
foreach (ProcessEnum.ProcessInfo p in ProcessEnum.FindProcessByName_NativeAPI("explorer.exe"))
{
foreach (int TID in p.ThreadIDs)
{
if (TID != threadIDOfTaskbarToSkip && TID != threadIDOfTaskbarToSkip2)
{
bool doesThreadHaveAnyWindows = EnumThreadWindows((uint)TID, delegate (IntPtr hWnd, IntPtr lParam)
{
if (hWnd != hWindow_TopDesktopWindow && hWnd != hWindow_TopShellWindow)
{
// Instead, better to whitelist, and ONLY close CabinetWClass windows which are explorer windows
// This way we are not closing window that cause issues it appears (despite whitelisting the Taskbar threads, Desktop and Shell window, etc)
StringBuilder sbClassText = new StringBuilder(256);
int ret = GetClassName(hWnd, sbClassText, sbClassText.Capacity);
string className = sbClassText.ToString();
if (className.ToLower() == "CabinetWClass".ToLower())
{
// Will also send WM_QUIT first, because why not -- https://stackoverflow.com/a/3155879
// WM_CLOSE vs WM_QUIT vs WM_DESTROY
// WM_CLOSE = [X] button
// WM_QUIT = WM_QUIT message is not related to any window (the hwnd got from GetMessage is NULL and no window procedure is called). This message indicates that the message loop should be stopped and application should be closed. When GetMessage reads WM_QUIT it returns 0 to indicate that.
PostMessage(hWnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero);
// Request that all windows for this thread be closed
PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
//PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
return true;
}, IntPtr.Zero);
}
}
}
System.Threading.Thread.Sleep(50);
FindWindowExtended.Window[] windowsFound = FindWindowExtended.FindWindowLike("Shut Down Windows");
foreach (var w in windowsFound)
{
if (w.Handle != IntPtr.Zero)
{
// Request that the window be closed
bool did = PostMessage(w.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
}
}
#endregion
#region [Public] Terminate Process gracefully (window closing via Msg send)
/// <summary>
/// Send WM_QUIT + WM_Close to all windows for all thread within a process
/// IMPORTANT: This will ONLY work if the current process is running at the same IL (Integrity Level) or higher. Otherwise PostMessage will be deflected with error 5 (Access denied)
/// </summary>
public static void TerminateProcessGraceful(int PID)
{
TerminateProcessGraceful(ProcessEnum.LoadProcessInfoByPID(PID, ProcessEnum.EnumInfoRequest.Basic)); // Just need PIDs and TIDs
}
/// <summary>
/// Send WM_QUIT + WM_Close to all windows for all thread within a process
/// IMPORTANT: This will ONLY work if the current process is running at the same IL (Integrity Level) or higher. Otherwise PostMessage will be deflected with error 5 (Access denied)
/// </summary>
public static void TerminateProcessGraceful(ProcessEnum.ProcessInfo process)
{
// Based on:
// social.msdn.microsoft.com/Forums/vstudio/en-US/82992842-80eb-43c8-a9e6-0a6a1d19b00f/terminating-a-process-in-a-friendly-way?forum=csharpgeneral
if (process.WasProcessRunning)
{
// Try closing application by sending WM_CLOSE to all child windows in all threads.
foreach (int TID in process.ThreadIDs)
{
bool doesThreadHaveAnyWindows = EnumThreadWindows((uint)TID, delegate (IntPtr hWnd, IntPtr lParam)
{
// Will also send WM_QUIT first, because why not -- https://stackoverflow.com/a/3155879
// WM_CLOSE vs WM_QUIT vs WM_DESTROY
// WM_CLOSE = [X] button
// WM_QUIT = WM_QUIT message is not related to any window (the hwnd got from GetMessage is NULL and no window procedure is called). This message indicates that the message loop should be stopped and application should be closed. When GetMessage reads WM_QUIT it returns 0 to indicate that.
bool did = PostMessage(hWnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero);
// Request that all windows for this thread be closed
did = PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
if (!did)
{
_lastAPIError = Marshal.GetLastWin32Error();
}
return true;
}, IntPtr.Zero);
}
}
}
/// <summary>
/// Send WM_QUIT + WM_Close to all windows for all thread within all matching processes located by name (ProcessEnum).
/// IMPORTANT: This will ONLY work if the current process is running at the same IL (Integrity Level) or higher. Otherwise PostMessage will be deflected with error 5 (Access denied)
/// </summary>
public static void TerminateProcessGraceful(string allProccessMatchedWith1Name)
{
TerminateProcessGraceful(new string[] { allProccessMatchedWith1Name });
}
/// <summary>
/// Send WM_QUIT + WM_Close to all windows for all thread within all matching processes located by name (ProcessEnum).
/// IMPORTANT: This will ONLY work if the current process is running at the same IL (Integrity Level) or higher. Otherwise PostMessage will be deflected with error 5 (Access denied)
/// </summary>
public static void TerminateProcessGraceful(string[] multipleProcessNames)
{
var foundProcesses = new List<ProcessEnum.ProcessInfo[]>();
foreach (string processName in multipleProcessNames)
{
ProcessEnum.ProcessInfo[] p = ProcessEnum.FindProcessByName(processName).ToArray();
foundProcesses.Add(p);
}
foreach (ProcessEnum.ProcessInfo[] matchingProcsByName in foundProcesses)
{
foreach (ProcessEnum.ProcessInfo proc in matchingProcsByName)
{
// First try this...
// if (proc.MainWindowHandle != IntPtr.Zero)
// {
// // Try to close main window.
// bool didClose = proc.CloseMainWindow();
// }
// Try closing application by sending WM_CLOSE to all child windows in all threads.
foreach (int TID in proc.ThreadIDs)
{
bool doesThreadHaveAnyWindows = EnumThreadWindows((uint)TID, delegate (IntPtr hWnd, IntPtr lParam)
{
// Will also send WM_QUIT first, because why not -- https://stackoverflow.com/a/3155879
// WM_CLOSE vs WM_QUIT vs WM_DESTROY
// WM_CLOSE = [X] button
// WM_QUIT = WM_QUIT message is not related to any window (the hwnd got from GetMessage is NULL and no window procedure is called). This message indicates that the message loop should be stopped and application should be closed. When GetMessage reads WM_QUIT it returns 0 to indicate that.
bool did = PostMessage(hWnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero);
// Request that all windows for this thread be closed
did = PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
if (!did)
{
_lastAPIError = Marshal.GetLastWin32Error();
}
return did;
}, IntPtr.Zero);
}
}
}
}
/// <summary>
/// Does NOT require a ProcessEnum.
/// </summary>
public static void TerminateProcessGracefulByWindowName(string windowNameLike)
{
// Example call:
// ProcessTerminateGraceful.TerminateProcessGracefulByWindowName("%notepad++%");
FindWindowExtended.Window[] windows = FindWindowExtended.FindWindowLike(windowNameLike, null);
foreach (var window in windows)
{
// Window handle --> Thread ID and Process ID
uint tid_NotUsed = GetWindowThreadProcessId(window.Handle, out uint pid);
TerminateProcessGraceful((int)pid);
}
}
#endregion
#region [Public] Explorer specific window closing & termination
/// <summary>
/// This only terminates the explorer.exe that runs the Desktop (Wallpaper) and Taskbar, not any other explorers, which may need to be done
/// </summary>
public static void TerminateExplorerDesktopGracefully()
{
// From: stackoverflow.com/a/5705965
IntPtr hWndTray = FindWindow("Shell_TrayWnd", "");
bool did = PostMessage(hWndTray, 0x5B4, IntPtr.Zero, IntPtr.Zero); // Special undocumented WM_USER window message
if (!did)
{
_lastAPIError = Marshal.GetLastWin32Error();
}
}
/// <summary>
/// Attempts to restart explorer gracefully by sending WM_QUIT & WM_CLOSE to all explorer windows.
/// If any windows don close after .7 of 1 second (common for altnerative Explorer.exe instances to have non-visible windows not respond to these)
/// Then TerminateProcess (Forcefull termination) is performed
/// Lastly, explorer.exe is restarted
/// </summary>
public static void RestartExplorerGracefully()
{
// Shortcut to not do the waiting below if explorer.exe isn't even running (simply start it)
bool isExplorerRunning = ProcessEnum.IsProcessRunning("explorer.exe");
if (isExplorerRunning)
{
// Go ahead and start with the Main Desktop Explorer.exe to get the ball rolling quickly, since it takes a bit to come down & repaint
TerminateExplorerDesktopGracefully();
// Then target all other explorers gracefully
ProcessTerminateGraceful.TerminateProcessGraceful("explorer.exe");
Thread.Sleep(700); // Let that cook before we go after them with TerminateProcess, especially since sending via PostMessage() rather than SendMessage() to not hang ourselces
// Some explorer.exe processes will have some non-visible windows that appear to get "stuck" and wont respond to WM_QUIT commands
// As such, a final termination sweep needs to be performed with TerminateProcess() call
var ret = ProcessTerminateForceful.TerminateProcessForceful("explorer.exe");
// A lot shorter wait since TerminateProcess causes our thread to "do" the work synchronously terminating each thread of another process
// UPDATE: TerminateProcess is actually async: - https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
// it initiates termination and returns immediately
// If you need to be sure the process has terminated, call the WaitForSingleObject function with a handle to the process.
Thread.Sleep(200);
}
// ONLY after all other instances of explorer that were killed, can it be restarted, otherwise the Main Desktop & Taskbar explorer end up in a loop
// [Re]Start it
// Ideally restart with Medium IL, if the registry modification is in place, but this will suffice for now
StopWow64Redirection(true); // If this app was built as x86 and its running on x64 CPU, then WOW64 (32-bit) explorer.exe would instead be launched from c:\windows\syswow64, and not the actual main Desktop Shell x64 bit one
System.Diagnostics.Process.Start("explorer.exe");
StopWow64Redirection(false);
}
#endregion
#region Wow64 Disable Help
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
static IntPtr wow64TogglePointer = new IntPtr();
/// <summary>
/// Call this method with true this when !AmI64Process
/// </summary>
public static bool StopWow64Redirection(bool stop)
{
if (stop)
{
return Wow64DisableWow64FsRedirection(ref wow64TogglePointer);
}
else
{
return Wow64RevertWow64FsRedirection(wow64TogglePointer);
}
}
#endregion
}
public static class ProcessTerminateForceful
{
//
// Terminate process forcefully(requires being able to open the process with PROCESS_TERMINATE)
//
#region Last Err Field
static int _lastAPIError = 0;
public static int LastAPIErrorCode
{
get
{
return _lastAPIError;
}
}
#endregion
/// <summary>
/// TerminateProcess is used to cause all of the threads in the process to terminate their execution, and causes all of the
/// object handles opened by the process to be closed.The process is
/// not removed from the system until the last handle to the process is closed.
///
/// See process.c in Windows XP Src
/// TerminateProcess directly forwards the call to NtTerminateProcess (psdelete.c)
/// PsGetNextProcessThread --> PspTerminateThreadByPointer
///
/// </summary>
/// <param name="hProcess">The handle must have been created with PROCESS_TERMINATE access.</param>
/// <param name="uExitCode">Supplies the termination status for each thread in the process.</param>
/// <returns>
/// True: Successful terminations by all threads terminating.
/// False (NULL) - The operation failed. Extended error status is available using GetLastError.</returns>
[DllImport("kernel32.dll", EntryPoint = "TerminateProcess", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool TerminateProcess_API(IntPtr hProcess, uint uExitCode);
/// <summary>
/// Terminates 1 process by PID, by Force (TerminateProcess call).
/// Does NOT require a ProcessEnum.
/// </summary>
public static bool TerminateProcessForceful(int PID)
{
IntPtr hProcess = ProcessEnum.OpenProcess(ProcessEnum.ProcessAccessFlags.Terminate, false, PID); // PROCESS_TERMINATE access right needed
if (hProcess == IntPtr.Zero)
{
return false;
}
else
{
bool did = TerminateProcess_API(hProcess, 0);
if (!did)
{
_lastAPIError = Marshal.GetLastWin32Error();
return false;
}
else
{
return true;
}
}
}
public enum NumberProcessesTerminatedStatus
{
All,
Some,
None
}
public class NumberProcessesTerminated
{
public int CountTerminatedSuccessfully = 0;
public int CountFailed = 0;
public int CountTotalFound = 0;
public NumberProcessesTerminatedStatus ResultStatus = NumberProcessesTerminatedStatus.None;
}
public static NumberProcessesTerminated TerminateProcessForceful(string allProccessMatchedWith1Name)
{
// Don't assume this is one process. By name this could be MANY proccesses, like "explorer.exe"
return TerminateProcessForceful(new string[] { allProccessMatchedWith1Name });
}
/// <summary>
/// Terminates all matching process names by Force (TerminateProcess call)
/// </summary>
public static NumberProcessesTerminated TerminateProcessForceful(string[] multipleProcessNames)
{
NumberProcessesTerminated ret = new NumberProcessesTerminated();
foreach (string currentNameToMatchAllProcesses in multipleProcessNames)
{
List<ProcessEnum.ProcessInfo> processesFoundRet = ProcessEnum.GetProcessesByName(currentNameToMatchAllProcesses);
foreach (var p in processesFoundRet)
{
ret.CountTotalFound++;
IntPtr hProcess = ProcessEnum.OpenProcess(ProcessEnum.ProcessAccessFlags.Terminate, false, p.PID); // PROCESS_TERMINATE access right needed
if (hProcess == IntPtr.Zero)
{
ret.CountFailed++; // Failed if couln't open
}
else
{
bool did = TerminateProcess_API(hProcess, 0);
if (!did)
{
_lastAPIError = Marshal.GetLastWin32Error();
ret.CountFailed++;
}
else
{
ret.CountTerminatedSuccessfully++;
}
}
}
}
if (ret.CountTerminatedSuccessfully == ret.CountTotalFound)
{
ret.ResultStatus = NumberProcessesTerminatedStatus.All;
}
else if (ret.CountFailed == ret.CountTotalFound)
{
ret.ResultStatus = NumberProcessesTerminatedStatus.None;
}
else
{
ret.ResultStatus = NumberProcessesTerminatedStatus.Some;
}
return ret;
}
/// <summary>
/// Does NOT require a ProcessEnum.
/// </summary>
public static void TerminateProcessForcefulByWindowName(string windowNameLike)
{
// Example call:
// ProcessTerminateGraceful.TerminateProcessForcefulByWindowName("%notepad++%");
FindWindowExtended.Window[] windows = FindWindowExtended.FindWindowLike(windowNameLike, null);
foreach (var window in windows)
{
// Window handle --> Thread ID and Process ID
uint tid_NotUsed = ProcessInformationReading.GetWindowThreadProcessId(window.Handle, out uint pid);
TerminateProcessForceful((int)pid);
}
}
}
#endregion
#region 2 - FindWindowExtended Class (FindWindowLike)
public static class FindWindowExtended
{
#region -- APIs --
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32")]
static extern IntPtr GetWindow(IntPtr hwnd, int wCmd);
[DllImport("user32.dll")]
static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
static extern IntPtr GetShellWindow();
[DllImport("user32", EntryPoint = "GetWindowLongA")]
static extern int GetWindowLong(IntPtr hwnd, int nIndex);
[DllImport("user32")]
static extern IntPtr GetParent(IntPtr hwnd);
[DllImport("user32", EntryPoint = "GetWindowTextA")]
static extern int GetWindowText(IntPtr hWnd, [Out] StringBuilder lpString, int nMaxCount);
const int GWL_ID = (-12);
const int GW_HWNDNEXT = 2;
const int GW_CHILD = 5;
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
#endregion
#region -- User-defined Data Structures (Window Data Structure) --
public class Window
{
public string Title;
public string Class;
public IntPtr Handle;
}
#endregion
/// <summary>
/// Uses FindWindowRecursive
/// Current Wildcard Support: % at the Start or End
///
/// Search the entire window list (all windows and child windows) starting either from the main Desktop parent window (if hwndStart = IntPtr.Zero)
/// or starting from the parent window supplied. Uses recursing to enumerate the child windows.
/// Supports wild cards at the start and end of findText and findClassName. Not currently in the middle.
/// This includes finding button names where FindWindowEx fails
/// </summary>
/// <param name="hwndStart">Where to start the search in the window tree</param>
/// <param name="findText">The window title / text</param>
/// <param name="findClassName">The window class</param>
/// <returns>List of matching windows</returns>
public static Window[] FindWindowLike(string findText, string findClassName = null, IntPtr hwndStart = default(IntPtr))
{
// From - experts-exchange.com/questions/21611201/I-need-FindWindowLike-for-C.html
if (hwndStart == IntPtr.Zero)
{
hwndStart = GetDesktopWindow(); // The topmost window to start the search
}
List<Window> windows = FindWindowRecursive(hwndStart, findText, findClassName, true);
return windows.ToArray();
}
private static List<Window> FindWindowRecursive(IntPtr hwndStart, string findText, string findClassName,
bool firstCall = true,
bool wasWildCardSupplied_Previous = false,
bool useStartsWithForText_Previous = false,
bool useStartsWithForClass_Previous = false)
{
//
// Modified from - experts-exchange.com/questions/21611201/I-need-FindWindowLike-for-C.html
//
// Right now only supports % at the start and end of the string, not in the middle
var list = new List<Window>();
if (hwndStart == IntPtr.Zero)
{
hwndStart = GetDesktopWindow();
}
IntPtr hWindowChild = GetWindow(hwndStart, GW_CHILD);
bool textSpecified = !string.IsNullOrWhiteSpace(findText);
bool classSpecified = !string.IsNullOrWhiteSpace(findClassName);
const string WILDCARD = "%";
bool wildcardSuppliedNowOrBefore = false;
// Only analyzed when wildcardSuppliedNowOrBefore is true
bool useStartsWithForTextNowOrBefore = true;
bool useStartsWithForClassNowOrBefore = true;
if (firstCall)
{
wildcardSuppliedNowOrBefore = findText.Contains(WILDCARD);
if (wildcardSuppliedNowOrBefore)
{
if (textSpecified && findText.Contains(WILDCARD))
{
if (findText.StartsWith(WILDCARD))
{
useStartsWithForTextNowOrBefore = false; // use EndsWith instead
}
// Remove wildcard
findText = findText.Replace(WILDCARD, "");
}
if (classSpecified && findClassName.Contains(WILDCARD))
{
if (findClassName.StartsWith(WILDCARD))
{
useStartsWithForClassNowOrBefore = false; // use EndsWith instead
}
// Remove wildcard
findClassName = findClassName.Replace(WILDCARD, "");
}
}
// Set the values
wasWildCardSupplied_Previous = wildcardSuppliedNowOrBefore;
useStartsWithForText_Previous = useStartsWithForTextNowOrBefore;
useStartsWithForClass_Previous = useStartsWithForClassNowOrBefore;
}
else
{
// Reload the prior call's values
wildcardSuppliedNowOrBefore = wasWildCardSupplied_Previous;
useStartsWithForTextNowOrBefore = useStartsWithForText_Previous;
useStartsWithForClassNowOrBefore = useStartsWithForClass_Previous;
}
while (hWindowChild != IntPtr.Zero)
{
//
// Recursion - If there is a child window search deeper until locating a child of that child that returns IntPtr.Zero for GetWindow(GW_CHILD)
//
// Recursively search for child windows.
list.AddRange(FindWindowRecursive(hWindowChild, findText, findClassName, false, wasWildCardSupplied_Previous, useStartsWithForText_Previous, useStartsWithForClass_Previous));
StringBuilder text = new StringBuilder(255);
int rtn = GetWindowText(hWindowChild, text, 255);
string windowText = text.ToString();
windowText = windowText.Substring(0, rtn);
StringBuilder cls = new StringBuilder(255);
rtn = GetClassName(hWindowChild, cls, 255);
string className = cls.ToString();
className = className.Substring(0, rtn);
// --- DBG ---
//
//if (GetParent(hwnd) != IntPtr.Zero)
//{
// rtn = GetWindowLong(hwnd, GWL_ID);
//}
// --- DBG ---
//
//if(windowText.Contains("Create"))
//{
// int break0 = 7;
//}
if (!textSpecified && !classSpecified)
{
// Case 1 - Neither supplied - Add all windows
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;
currentWindow.Handle = hWindowChild;
list.Add(currentWindow);
}
else if (textSpecified && !classSpecified)
{
// Case 2 - Lookup by Text only
if (windowText.Length > 0)
{
if (wildcardSuppliedNowOrBefore)
{
if (useStartsWithForTextNowOrBefore && windowText.StartsWith(findText, StringComparison.CurrentCultureIgnoreCase)
|| (!useStartsWithForTextNowOrBefore && windowText.EndsWith(findText, StringComparison.CurrentCultureIgnoreCase)))
{
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;
currentWindow.Handle = hWindowChild;
list.Add(currentWindow);
}
}
else
{
if (windowText.ToLower() == findText.ToLower())
{
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;
currentWindow.Handle = hWindowChild;
list.Add(currentWindow);
}
}
}
}
else if (!textSpecified && classSpecified)
{
// Case 3 - Lookup by Class only
if (className.Length > 0)
{
if (wildcardSuppliedNowOrBefore)
{
if (useStartsWithForClassNowOrBefore && className.StartsWith(findClassName, StringComparison.CurrentCultureIgnoreCase)
|| (!useStartsWithForClassNowOrBefore && className.EndsWith(findClassName, StringComparison.CurrentCultureIgnoreCase)))
{
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;
currentWindow.Handle = hWindowChild;
list.Add(currentWindow);
}
}
else
{
if (className.ToLower() == findClassName.ToLower())
{
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;
currentWindow.Handle = hWindowChild;
list.Add(currentWindow);
}
}
}
}
else if (textSpecified && classSpecified)
{
// Case 4 - Both Text and Class supplied
if (wildcardSuppliedNowOrBefore)
{
if (windowText.Length > 0
&&
(useStartsWithForTextNowOrBefore && windowText.StartsWith(findText, StringComparison.CurrentCultureIgnoreCase)
|| (!useStartsWithForTextNowOrBefore && windowText.EndsWith(findText, StringComparison.CurrentCultureIgnoreCase)))
&&
(useStartsWithForClassNowOrBefore && className.StartsWith(findClassName, StringComparison.CurrentCultureIgnoreCase)
|| (!useStartsWithForClassNowOrBefore && className.EndsWith(findClassName, StringComparison.CurrentCultureIgnoreCase))))
{
Window currentWindow = new Window();
currentWindow.Title = windowText;
currentWindow.Class = className;