Bearsampp 2026.5.5
Loading...
Searching...
No Matches
class.win32native.php
Go to the documentation of this file.
1<?php
2/*
3 * Copyright (c) 2022-2025 Bearsampp
4 * License: GNU General Public License version 3 or later; see LICENSE.txt
5 * Website: https://bearsampp.com
6 * Github: https://github.com/Bearsampp
7 */
8
17{
18 // ========================================================================
19 // COM Connection Cache
20 // Each COM object is created once per PHP process and reused across calls.
21 // On a COM/WMI failure the catching method calls resetConnections() so the
22 // next call gets a fresh object instead of reusing a broken cached instance.
23 // ========================================================================
24
26 private static ?COM $wmiCimv2 = null;
27
29 private static ?COM $wmiStdRegProv = null;
30
32 private static ?COM $wscriptShell = null;
33
37 private static function getWmiCimv2(): COM
38 {
39 if (self::$wmiCimv2 === null) {
40 self::$wmiCimv2 = new COM("winmgmts://./root/cimv2");
41 }
42 return self::$wmiCimv2;
43 }
44
48 private static function getWmiStdRegProv(): COM
49 {
50 if (self::$wmiStdRegProv === null) {
51 self::$wmiStdRegProv = new COM("winmgmts://./root/default:StdRegProv");
52 }
53 return self::$wmiStdRegProv;
54 }
55
59 private static function getWscriptShell(): COM
60 {
61 if (self::$wscriptShell === null) {
62 self::$wscriptShell = new COM("WScript.Shell");
63 }
64 return self::$wscriptShell;
65 }
66
71 public static function resetConnections(): void
72 {
73 self::$wmiCimv2 = null;
74 self::$wmiStdRegProv = null;
75 self::$wscriptShell = null;
76 }
77
85 public static function getProcessList($properties = [])
86 {
87 Log::debug('getProcessList: Listing processes (COM/WMI)');
88
89 $startTime = microtime(true);
90
91 try {
92 // Create WMI connection
93 $wmi = self::getWmiCimv2();
94
95 // Build WQL query
96 if (empty($properties)) {
97 $properties = ['Name', 'ProcessID', 'ExecutablePath'];
98 }
99
100 $selectClause = implode(', ', $properties);
101 $query = "SELECT {$selectClause} FROM Win32_Process";
102
103 // Execute query
104 $processes = $wmi->ExecQuery($query);
105
106 // Convert to array
107 $result = [];
108 foreach ($processes as $proc) {
109 $process = [];
110 foreach ($properties as $prop) {
111 // Handle property access
112 try {
113 $value = $proc->$prop;
114 $process[$prop] = $value ?? '';
115 } catch (Exception $e) {
116 $process[$prop] = '';
117 }
118 }
119 $result[] = $process;
120 }
121
122 $duration = round((microtime(true) - $startTime) * 1000, 2);
123 Log::debug('getProcessList: Found ' . count($result) . ' processes in ' . $duration . 'ms (COM/WMI)');
124
125 return $result;
126
127 } catch (Exception $e) {
129 Log::error('getProcessList: COM exception: ' . $e->getMessage());
130 return [];
131 }
132 }
133
141 public static function killProcess($pid)
142 {
143 // Validate PID
144 if (!is_numeric($pid) || $pid <= 0) {
145 Log::error('killProcess: Invalid PID: ' . $pid);
146 return false;
147 }
148
149 Log::debug('killProcess: Killing process PID ' . $pid . ' (COM/WMI)');
150
151 $startTime = microtime(true);
152
153 try {
154 // Create WMI connection
155 $wmi = self::getWmiCimv2();
156
157 // Query for specific process
158 $query = "SELECT * FROM Win32_Process WHERE ProcessID = {$pid}";
159 $processes = $wmi->ExecQuery($query);
160
161 // Terminate the process
162 $found = false;
163 $terminateResult = null;
164 foreach ($processes as $proc) {
165 $terminateResult = $proc->Terminate();
166 $found = true;
167 break;
168 }
169
170 $duration = round((microtime(true) - $startTime) * 1000, 2);
171
172 if (!$found) {
173 Log::debug('killProcess: Process ' . $pid . ' not found');
174 return false;
175 }
176
177 // Check if Terminate() returned success (0 = success)
178 if ($terminateResult !== 0) {
179 Log::error('killProcess: Terminate() failed with status code: ' . $terminateResult);
180 return false;
181 }
182
183 // Additional verification: check if process still exists after a short delay
184 usleep(100000); // Wait 100ms for termination to take effect
185 if (self::processExists($pid)) {
186 Log::error('killProcess: Process ' . $pid . ' still exists after termination attempt');
187 return false;
188 }
189
190 Log::debug('killProcess: Successfully killed process ' . $pid . ' in ' . $duration . 'ms (COM/WMI)');
191 return true;
192
193 } catch (Exception $e) {
195 Log::error('killProcess: COM exception: ' . $e->getMessage());
196 return false;
197 }
198 }
199
206 public static function processExists($pid)
207 {
208 if (!is_numeric($pid) || $pid <= 0) {
209 return false;
210 }
211
212 try {
213 $wmi = self::getWmiCimv2();
214 $query = "SELECT ProcessID FROM Win32_Process WHERE ProcessID = {$pid}";
215 $processes = $wmi->ExecQuery($query);
216
217 foreach ($processes as $proc) {
218 return true;
219 }
220
221 return false;
222
223 } catch (Exception $e) {
225 return false;
226 }
227 }
228
236 public static function getProcessInfo($pid, $properties = [])
237 {
238 if (!is_numeric($pid) || $pid <= 0) {
239 return false;
240 }
241
242 if (empty($properties)) {
243 $properties = ['Name', 'ProcessID', 'ExecutablePath', 'CommandLine'];
244 }
245
246 try {
247 $wmi = self::getWmiCimv2();
248 $selectClause = implode(', ', $properties);
249 $query = "SELECT {$selectClause} FROM Win32_Process WHERE ProcessID = {$pid}";
250 $processes = $wmi->ExecQuery($query);
251
252 foreach ($processes as $proc) {
253 $result = [];
254 foreach ($properties as $prop) {
255 try {
256 $value = $proc->$prop;
257 $result[$prop] = $value ?? '';
258 } catch (Exception $e) {
259 $result[$prop] = '';
260 }
261 }
262 return $result;
263 }
264
265 return false;
266
267 } catch (Exception $e) {
269 Log::error('getProcessInfo: COM exception: ' . $e->getMessage());
270 return false;
271 }
272 }
273
281 public static function findProcessesByName($name, $properties = [])
282 {
283 if (empty($name)) {
284 return [];
285 }
286
287 if (empty($properties)) {
288 $properties = ['Name', 'ProcessID', 'ExecutablePath'];
289 }
290
291 try {
292 $wmi = self::getWmiCimv2();
293 $selectClause = implode(', ', $properties);
294
295 // Sanitize the name parameter to prevent WQL injection
296 // Escape single quotes by doubling them (WQL standard)
297 $safeName = str_replace("'", "''", $name);
298
299 $query = "SELECT {$selectClause} FROM Win32_Process WHERE Name = '{$safeName}'";
300 $processes = $wmi->ExecQuery($query);
301
302 $result = [];
303 foreach ($processes as $proc) {
304 $process = [];
305 foreach ($properties as $prop) {
306 try {
307 $value = $proc->$prop;
308 $process[$prop] = $value ?? '';
309 } catch (Exception $e) {
310 $process[$prop] = '';
311 }
312 }
313 $result[] = $process;
314 }
315
316 return $result;
317
318 } catch (Exception $e) {
320 Log::error('findProcessesByName: COM exception: ' . $e->getMessage());
321 return [];
322 }
323 }
324
330 public static function getCurrentPid()
331 {
332 return getmypid();
333 }
334
335 // ========================================================================
336 // Registry Operations (COM/WScript.Shell)
337 // ========================================================================
338
345 private static function mapRegistryHive($hive)
346 {
347 $hiveMap = [
348 'HKLM' => 'HKLM',
349 'HKCU' => 'HKCU',
350 'HKCR' => 'HKCR',
351 'HKU' => 'HKU',
352 'HKEY_LOCAL_MACHINE' => 'HKLM',
353 'HKEY_CURRENT_USER' => 'HKCU',
354 'HKEY_CLASSES_ROOT' => 'HKCR',
355 'HKEY_USERS' => 'HKU',
356 ];
357
358 return isset($hiveMap[$hive]) ? $hiveMap[$hive] : $hive;
359 }
360
373 public static function registryExists($hive, $key, $value = null)
374 {
375 $hive = self::mapRegistryHive($hive);
376 $regPath = $hive . '\\' . $key;
377
378 Log::debug('registryExists: Checking ' . $regPath . ($value !== null ? '\\' . $value : ' (key)') . ' (COM)');
379
380 try {
381 if ($value === null) {
382 // Check if the key itself exists
383 // Use StdRegProv::EnumKey for reliable key existence checking
384 // This avoids false negatives from checking for a default value that may not exist
385 try {
386 $wmi = self::getWmiStdRegProv();
387
388 // Map hive names to WMI constants
389 $hiveConstMap = [
390 'HKCR' => 0x80000000, // HKEY_CLASSES_ROOT
391 'HKCU' => 0x80000001, // HKEY_CURRENT_USER
392 'HKLM' => 0x80000002, // HKEY_LOCAL_MACHINE
393 'HKU' => 0x80000003, // HKEY_USERS
394 ];
395
396 $hConst = $hiveConstMap[$hive] ?? 0x80000002; // Default to HKLM
397
398 // EnumKey checks if parent key's subkeys contain the requested key
399 // For root-level checks, pass empty parent
400 $pathParts = explode('\\', $key);
401
402 if (count($pathParts) === 1) {
403 // Top-level key: parent is root
404 $parentKey = '';
405 $keyName = $pathParts[0];
406 } else {
407 // Nested key: split parent from key name
408 $keyName = array_pop($pathParts);
409 $parentKey = implode('\\', $pathParts);
410 }
411
412 $subKeys = null;
413 $rc = $wmi->EnumKey($hConst, $parentKey, $subKeys);
414
415 if ($rc !== 0 || !is_array($subKeys)) {
416 Log::debug('registryExists: Key not found (EnumKey failed)');
417 return false;
418 }
419
420 // Check if our key name is in the list of subkeys
421 $exists = false;
422 foreach ($subKeys as $subKey) {
423 if (strcasecmp($subKey, $keyName) === 0) {
424 $exists = true;
425 break;
426 }
427 }
428
429 if ($exists) {
430 Log::debug('registryExists: Key found');
431 return true;
432 } else {
433 Log::debug('registryExists: Key not found');
434 return false;
435 }
436
437 } catch (Exception $e) {
438 self::$wmiStdRegProv = null;
439 Log::error('registryExists: StdRegProv exception during key check: ' . $e->getMessage());
440 return false;
441 }
442 } else {
443 // Check if a specific value exists within the key
444 // Use WScript.Shell::RegRead for value existence
445 try {
446 $shell = self::getWscriptShell();
447 $valuePath = $regPath . '\\' . $value;
448 $shell->RegRead($valuePath);
449 Log::debug('registryExists: Value found');
450 return true;
451 } catch (Exception $e) {
452 Log::debug('registryExists: Value not found');
453 return false;
454 }
455 }
456
457 } catch (Exception $e) {
459 Log::error('registryExists: COM exception: ' . $e->getMessage());
460 return false;
461 }
462 }
463
473 public static function registryGetValue($hive, $key, $value = '')
474 {
475 $hive = self::mapRegistryHive($hive);
476 $regPath = $hive . '\\' . $key;
477
478 if ($value !== '') {
479 $regPath .= '\\' . $value;
480 } else {
481 // For default value, append backslash
482 $regPath .= '\\';
483 }
484
485 Log::debug('registryGetValue: Reading ' . $regPath . ' (COM)');
486
487 $startTime = microtime(true);
488
489 try {
490 $shell = self::getWscriptShell();
491 $result = $shell->RegRead($regPath);
492
493 $duration = round((microtime(true) - $startTime) * 1000, 2);
494
495 // Convert result to appropriate PHP type
496 if (is_object($result)) {
497 // COM objects need special handling
498 $result = (string)$result;
499 }
500
501 Log::debug('registryGetValue: Found value in ' . $duration . 'ms (COM)');
502 return $result;
503
504 } catch (Exception $e) {
505 Log::debug('registryGetValue: Value not found');
506 return null;
507 }
508 }
509
521 public static function registrySetValue($hive, $key, $value, $data, $type = 'REG_SZ')
522 {
523 $hive = self::mapRegistryHive($hive);
524 $regPath = $hive . '\\' . $key . '\\' . $value;
525
526 // Validate type
527 $validTypes = ['REG_SZ', 'REG_EXPAND_SZ', 'REG_DWORD', 'REG_BINARY'];
528 if (!in_array($type, $validTypes)) {
529 Log::error('registrySetValue: Invalid type: ' . $type);
530 return false;
531 }
532
533 Log::debug('registrySetValue: Writing ' . $regPath . ' (' . $type . ') (COM)');
534
535 $startTime = microtime(true);
536
537 try {
538 $shell = self::getWscriptShell();
539
540 // Convert data based on type
541 if ($type === 'REG_DWORD') {
542 $data = (int)$data;
543 }
544
545 // Write the value
546 $shell->RegWrite($regPath, $data, $type);
547
548 $duration = round((microtime(true) - $startTime) * 1000, 2);
549 Log::debug('registrySetValue: Successfully wrote value in ' . $duration . 'ms (COM)');
550
551 return true;
552
553 } catch (Exception $e) {
555 Log::error('registrySetValue: COM exception: ' . $e->getMessage());
556 return false;
557 }
558 }
559
569 public static function registryDeleteValue($hive, $key, $value)
570 {
571 $hive = self::mapRegistryHive($hive);
572 $regPath = $hive . '\\' . $key . '\\' . $value;
573
574 Log::debug('registryDeleteValue: Deleting ' . $regPath . ' (COM)');
575
576 $startTime = microtime(true);
577
578 try {
579 $shell = self::getWscriptShell();
580
581 // Delete the value
582 $shell->RegDelete($regPath);
583
584 $duration = round((microtime(true) - $startTime) * 1000, 2);
585 Log::debug('registryDeleteValue: Successfully deleted value in ' . $duration . 'ms (COM)');
586
587 return true;
588
589 } catch (Exception $e) {
590 // If the value doesn't exist, that's OK
591 $errorMsg = $e->getMessage();
592 if (strpos($errorMsg, 'Unable to remove') !== false ||
593 strpos($errorMsg, 'Invalid root') !== false) {
594 Log::debug('registryDeleteValue: Value does not exist (already deleted)');
595 return true;
596 }
597
599 Log::error('registryDeleteValue: COM exception: ' . $e->getMessage());
600 return false;
601 }
602 }
603
612 public static function registryDeleteKey($hive, $key)
613 {
614 $hive = self::mapRegistryHive($hive);
615 $regPath = $hive . '\\' . $key . '\\';
616
617 Log::debug('registryDeleteKey: Deleting ' . $regPath . ' (COM)');
618
619 try {
620 $shell = self::getWscriptShell();
621
622 // Delete the key (note the trailing backslash)
623 $shell->RegDelete($regPath);
624
625 Log::debug('registryDeleteKey: Successfully deleted key (COM)');
626 return true;
627
628 } catch (Exception $e) {
629 // If the key doesn't exist, that's OK
630 $errorMsg = $e->getMessage();
631 if (strpos($errorMsg, 'Unable to remove') !== false ||
632 strpos($errorMsg, 'Invalid root') !== false) {
633 Log::debug('registryDeleteKey: Key does not exist (already deleted)');
634 return true;
635 }
636
638 Log::error('registryDeleteKey: COM exception: ' . $e->getMessage());
639 return false;
640 }
641 }
642
643 // ========================================================================
644 // Shortcuts & Special Paths (COM/WScript.Shell)
645 // ========================================================================
646
654 public static function getSpecialFolderPath($folderName)
655 {
656 Log::debug('getSpecialFolderPath: Getting ' . $folderName . ' path (COM)');
657
658 $startTime = microtime(true);
659
660 try {
661 $shell = self::getWscriptShell();
662
663 // Get the special folder path
664 $path = $shell->SpecialFolders($folderName);
665
666 $duration = round((microtime(true) - $startTime) * 1000, 2);
667
668 if ($path && !empty($path)) {
669 // Convert to Unix-style path
670 $path = str_replace('\\', '/', $path);
671 Log::debug('getSpecialFolderPath: Found ' . $folderName . ' in ' . $duration . 'ms (COM)');
672 return $path;
673 } else {
674 Log::debug('getSpecialFolderPath: ' . $folderName . ' not found');
675 return false;
676 }
677
678 } catch (Exception $e) {
680 Log::error('getSpecialFolderPath: COM exception: ' . $e->getMessage());
681 return false;
682 }
683 }
684
696 public static function createShortcut($shortcutPath, $targetPath, $workingDir = '', $description = '', $iconPath = '')
697 {
698 Log::debug('createShortcut: Creating shortcut at ' . $shortcutPath . ' (COM)');
699
700 $startTime = microtime(true);
701
702 try {
703 $shell = self::getWscriptShell();
704
705 // Create the shortcut object
706 $shortcut = $shell->CreateShortcut($shortcutPath);
707
708 // Set shortcut properties
709 $shortcut->TargetPath = $targetPath;
710
711 if (!empty($workingDir)) {
712 $shortcut->WorkingDirectory = $workingDir;
713 }
714
715 if (!empty($description)) {
716 $shortcut->Description = $description;
717 }
718
719 if (!empty($iconPath)) {
720 $shortcut->IconLocation = $iconPath;
721 }
722
723 // Save the shortcut
724 $shortcut->Save();
725
726 $duration = round((microtime(true) - $startTime) * 1000, 2);
727 Log::debug('createShortcut: Successfully created shortcut in ' . $duration . 'ms (COM)');
728
729 return true;
730
731 } catch (Exception $e) {
733 Log::error('createShortcut: COM exception: ' . $e->getMessage());
734 return false;
735 }
736 }
737
738 // ========================================================================
739 // File Operations (COM/FileSystemObject or Native PHP)
740 // ========================================================================
741
749 public static function countFilesFolders($path)
750 {
751 Log::debug('countFilesFolders: Counting in ' . $path . ' (Native PHP)');
752
753 $startTime = microtime(true);
754
755 try {
756 if (!is_dir($path)) {
757 Log::error('countFilesFolders: Path is not a directory: ' . $path);
758 return false;
759 }
760
761 $count = 0;
762
763 // Use RecursiveDirectoryIterator for efficient recursive counting
764 try {
765 $iterator = new RecursiveIteratorIterator(
766 new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
767 RecursiveIteratorIterator::SELF_FIRST
768 );
769
770 foreach ($iterator as $item) {
771 $count++;
772 }
773 } catch (Exception $e) {
774 // If RecursiveIterator fails, fall back to manual recursion
775 Log::debug('countFilesFolders: RecursiveIterator failed, using manual recursion');
776 $count = self::countFilesFoldersManual($path);
777 }
778
779 $duration = round((microtime(true) - $startTime) * 1000, 2);
780 Log::debug('countFilesFolders: Counted ' . $count . ' items in ' . $duration . 'ms (Native PHP)');
781
782 return $count;
783
784 } catch (Exception $e) {
785 Log::error('countFilesFolders: Exception: ' . $e->getMessage());
786 return false;
787 }
788 }
789
797 private static function countFilesFoldersManual($path)
798 {
799 $count = 0;
800
801 try {
802 $items = @scandir($path);
803
804 if ($items === false) {
805 return 0;
806 }
807
808 foreach ($items as $item) {
809 if ($item === '.' || $item === '..') {
810 continue;
811 }
812
813 $fullPath = $path . DIRECTORY_SEPARATOR . $item;
814 $count++; // Count this item
815
816 if (is_dir($fullPath)) {
817 // Recursively count subdirectory
818 $count += self::countFilesFoldersManual($fullPath);
819 }
820 }
821 } catch (Exception $e) {
822 // Silently handle errors (permission denied, etc.)
823 }
824
825 return $count;
826 }
827
835 public static function countFilesFoldersCOM($path)
836 {
837 Log::debug('countFilesFoldersCOM: Counting in ' . $path . ' (COM/FSO)');
838
839 $startTime = microtime(true);
840
841 try {
842 $fso = new COM("Scripting.FileSystemObject");
843
844 if (!$fso->FolderExists($path)) {
845 Log::error('countFilesFoldersCOM: Path does not exist: ' . $path);
846 return false;
847 }
848
849 $count = self::countFolderItemsCOM($fso, $path);
850
851 $duration = round((microtime(true) - $startTime) * 1000, 2);
852 Log::debug('countFilesFoldersCOM: Counted ' . $count . ' items in ' . $duration . 'ms (COM/FSO)');
853
854 return $count;
855
856 } catch (Exception $e) {
857 Log::error('countFilesFoldersCOM: COM exception: ' . $e->getMessage());
858 return false;
859 }
860 }
861
870 private static function countFolderItemsCOM($fso, $path)
871 {
872 try {
873 $folder = $fso->GetFolder($path);
874
875 // Count files and subfolders in this folder
876 $count = $folder->Files->Count + $folder->SubFolders->Count;
877
878 // Recursively count subfolders
879 foreach ($folder->SubFolders as $subFolder) {
880 $count += self::countFolderItemsCOM($fso, $subFolder->Path);
881 }
882
883 return $count;
884
885 } catch (Exception $e) {
886 // Silently handle errors (permission denied, etc.)
887 return 0;
888 }
889 }
890
891 // ========================================================================
892 // Service Operations (COM/WMI)
893 // ========================================================================
894
903 public static function getServiceInfo($serviceName, $properties = [])
904 {
905 Log::debug('getServiceInfo: Getting info for service ' . $serviceName . ' (COM/WMI)');
906
907 $startTime = microtime(true);
908
909 try {
910 // Create WMI connection
911 $wmi = self::getWmiCimv2();
912
913 // Default properties if none specified
914 if (empty($properties)) {
915 $properties = [
916 'Name',
917 'DisplayName',
918 'State',
919 'Status',
920 'StartMode',
921 'PathName',
922 'ProcessId',
923 'Started',
924 'StartName',
925 'Description'
926 ];
927 }
928
929 // Build WQL query
930 $selectClause = implode(', ', $properties);
931 $safeServiceName = str_replace("'", "''", $serviceName);
932 $query = "SELECT {$selectClause} FROM Win32_Service WHERE Name = '{$safeServiceName}'";
933
934 // Execute query
935 $services = $wmi->ExecQuery($query);
936
937 // Get the first (and should be only) result
938 foreach ($services as $service) {
939 $result = [];
940 foreach ($properties as $prop) {
941 try {
942 $value = $service->$prop;
943 $result[$prop] = $value ?? '';
944 } catch (Exception $e) {
945 $result[$prop] = '';
946 }
947 }
948
949 $duration = round((microtime(true) - $startTime) * 1000, 2);
950 Log::debug('getServiceInfo: Found service in ' . $duration . 'ms (COM/WMI)');
951
952 return $result;
953 }
954
955 // Service not found
956 Log::debug('getServiceInfo: Service not found');
957 return false;
958
959 } catch (Exception $e) {
961 Log::error('getServiceInfo: COM exception: ' . $e->getMessage());
962 return false;
963 }
964 }
965
973 public static function listServices($properties = [])
974 {
975 Log::debug('listServices: Listing all services (COM/WMI)');
976
977 $startTime = microtime(true);
978
979 try {
980 // Create WMI connection
981 $wmi = self::getWmiCimv2();
982
983 // Default properties if none specified
984 if (empty($properties)) {
985 $properties = ['Name', 'DisplayName', 'State', 'StartMode'];
986 }
987
988 // Build WQL query
989 $selectClause = implode(', ', $properties);
990 $query = "SELECT {$selectClause} FROM Win32_Service";
991
992 // Execute query
993 $services = $wmi->ExecQuery($query);
994
995 // Convert to array
996 $result = [];
997 foreach ($services as $service) {
998 $serviceInfo = [];
999 foreach ($properties as $prop) {
1000 try {
1001 $value = $service->$prop;
1002 $serviceInfo[$prop] = $value ?? '';
1003 } catch (Exception $e) {
1004 $serviceInfo[$prop] = '';
1005 }
1006 }
1007 $result[] = $serviceInfo;
1008 }
1009
1010 $duration = round((microtime(true) - $startTime) * 1000, 2);
1011 Log::debug('listServices: Found ' . count($result) . ' services in ' . $duration . 'ms (COM/WMI)');
1012
1013 return $result;
1014
1015 } catch (Exception $e) {
1017 Log::error('listServices: COM exception: ' . $e->getMessage());
1018 return [];
1019 }
1020 }
1021
1029 public static function serviceExists($serviceName)
1030 {
1031 try {
1032 $wmi = self::getWmiCimv2();
1033 // Sanitize the serviceName parameter to prevent WQL injection
1034 // Escape single quotes by doubling them (WQL standard)
1035 $safeServiceName = str_replace("'", "''", $serviceName);
1036 $query = "SELECT Name FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1037
1038 // Execute query
1039 $services = $wmi->ExecQuery($query);
1040
1041 foreach ($services as $service) {
1042 return true;
1043 }
1044
1045 return false;
1046
1047 } catch (Exception $e) {
1049 return false;
1050 }
1051 }
1052
1060 public static function getServiceState($serviceName)
1061 {
1062 try {
1063 $wmi = self::getWmiCimv2();
1064 // Sanitize the serviceName parameter to prevent WQL injection
1065 // Escape single quotes by doubling them (WQL standard)
1066 $safeServiceName = str_replace("'", "''", $serviceName);
1067 $query = "SELECT State FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1068
1069 // Execute query
1070 $services = $wmi->ExecQuery($query);
1071
1072 foreach ($services as $service) {
1073 return $service->State;
1074 }
1075
1076 return false;
1077
1078 } catch (Exception $e) {
1080 return false;
1081 }
1082 }
1083
1084 // ========================================================================
1085 // Browser Detection (COM/Registry)
1086 // ========================================================================
1087
1094 public static function getDefaultBrowser()
1095 {
1096 Log::debug('getDefaultBrowser: Reading default browser (COM)');
1097
1098 $startTime = microtime(true);
1099
1100 // Try to read the default browser from registry
1101 $browserPath = self::registryGetValue('HKLM', 'SOFTWARE\\Classes\\http\\shell\\open\\command', '');
1102
1103 if ($browserPath === null) {
1104 Log::debug('getDefaultBrowser: No default browser found');
1105 return false;
1106 }
1107
1108 $duration = round((microtime(true) - $startTime) * 1000, 2);
1109
1110 // Extract the executable path from the command
1111 // Format is usually: "C:\Program Files\Browser\browser.exe" -- "%1"
1112 if (preg_match('/"([^"]+)"/', $browserPath, $matches)) {
1113 $path = $matches[1];
1114 } else {
1115 // No quotes, take everything before the first space or use as-is
1116 $path = trim(explode(' ', $browserPath)[0]);
1117 $path = str_replace('"', '', $path);
1118 }
1119
1120 Log::debug('getDefaultBrowser: Found browser in ' . $duration . 'ms (COM)');
1121 return $path;
1122 }
1123
1130 public static function getInstalledBrowsers()
1131 {
1132 Log::debug('getInstalledBrowsers: Enumerating installed browsers (Hybrid - No VBS)');
1133
1134 $startTime = microtime(true);
1135 $browsers = [];
1136
1137 // Known browser registry names (covers 99% of browsers)
1138 $knownBrowsers = [
1139 // Major browsers
1140 'Google Chrome',
1141 'Microsoft Edge',
1142 'Opera',
1143 'Brave',
1144 'Vivaldi',
1145 'Chromium',
1146
1147 // Firefox variants
1148 'Firefox',
1149 'Firefox-308046B0AF4A39CB', // Common Firefox GUID
1150 'Firefox Developer Edition',
1151 'Firefox Nightly',
1152
1153 // Other browsers
1154 'Safari',
1155 'Waterfox',
1156 'Pale Moon',
1157 'Yandex',
1158 'IEXPLORE.EXE',
1159 'Total Browser',
1160 'Maxthon',
1161 'Seamonkey',
1162 'K-Meleon',
1163 'Basilisk',
1164 'Tor Browser',
1165 'Slimjet',
1166 'Epic Privacy Browser',
1167 'Comodo Dragon',
1168 'SRWare Iron',
1169 'Cent Browser',
1170 '360 Browser',
1171 'UC Browser',
1172 'Avast Secure Browser',
1173 'AVG Secure Browser',
1174 ];
1175
1176 // Registry paths to check
1177 $registryPaths = [
1178 ['hive' => 'HKLM', 'key' => 'SOFTWARE\\WOW6432Node\\Clients\\StartMenuInternet'],
1179 ['hive' => 'HKLM', 'key' => 'SOFTWARE\\Clients\\StartMenuInternet'],
1180 ['hive' => 'HKCU', 'key' => 'SOFTWARE\\Clients\\StartMenuInternet'],
1181 ];
1182
1183 // Check each known browser in each registry path
1184 foreach ($registryPaths as $regPath) {
1185 $hive = $regPath['hive'];
1186 $basePath = $regPath['key'];
1187
1188 foreach ($knownBrowsers as $browserName) {
1189 try {
1190 // Try to read the browser's command path
1191 $commandPath = self::registryGetValue(
1192 $hive,
1193 $basePath . '\\' . $browserName . '\\shell\\open\\command',
1194 ''
1195 );
1196
1197 if ($commandPath !== null && !empty($commandPath)) {
1198 // Extract executable path
1199 $exePath = self::extractBrowserExecutablePath($commandPath);
1200
1201 if ($exePath && !in_array($exePath, $browsers)) {
1202 Log::debug('getInstalledBrowsers: Found ' . $browserName . ': ' . $exePath);
1203 $browsers[] = $exePath;
1204 }
1205 }
1206 } catch (Exception $e) {
1207 // Browser not found, continue
1208 continue;
1209 }
1210 }
1211 }
1212
1213 $duration = round((microtime(true) - $startTime) * 1000, 2);
1214 Log::debug('getInstalledBrowsers: Found ' . count($browsers) . ' browser(s) in ' . $duration . 'ms (Hybrid - No VBS)');
1215
1216 // Always return an array (possibly empty) to simplify call sites
1217 return $browsers;
1218 }
1219
1227 private static function extractBrowserExecutablePath($commandPath)
1228 {
1229 if (empty($commandPath)) {
1230 return false;
1231 }
1232
1233 // Method 1: Extract path from quotes
1234 // Format: "C:\Program Files\Browser\browser.exe" --arguments
1235 if (preg_match('/"([^"]+\\.exe)"/i', $commandPath, $matches)) {
1236 return $matches[1];
1237 }
1238
1239 // Method 2: Extract path without quotes but with .exe
1240 // Format: C:\Program Files\Browser\browser.exe --arguments
1241 if (preg_match('/^([^\\s]+\\.exe)/i', trim($commandPath), $matches)) {
1242 return $matches[1];
1243 }
1244
1245 // Method 3: Handle paths with spaces but no quotes (rare)
1246 // Try to find .exe in the string
1247 if (preg_match('/([A-Z]:\\\\[^"]+\\.exe)/i', $commandPath, $matches)) {
1248 return $matches[1];
1249 }
1250
1251 // Method 4: Fallback - take everything before first space
1252 $parts = explode(' ', trim($commandPath));
1253 $path = str_replace('"', '', $parts[0]);
1254
1255 // Validate it looks like a path
1256 if (stripos($path, '.exe') !== false) {
1257 return $path;
1258 }
1259
1260 return false;
1261 }
1262}
$result
$proc
Definition ajax.php:61
static debug($data, $file=null)
static error($data, $file=null)
static registrySetValue($hive, $key, $value, $data, $type='REG_SZ')
static countFilesFoldersCOM($path)
static countFolderItemsCOM($fso, $path)
static COM $wmiCimv2
static resetConnections()
static getProcessList($properties=[])
static getProcessInfo($pid, $properties=[])
static getDefaultBrowser()
static processExists($pid)
static mapRegistryHive($hive)
static getInstalledBrowsers()
static findProcessesByName($name, $properties=[])
static registryExists($hive, $key, $value=null)
static listServices($properties=[])
static countFilesFolders($path)
static registryDeleteValue($hive, $key, $value)
static killProcess($pid)
static registryGetValue($hive, $key, $value='')
static getServiceState($serviceName)
static extractBrowserExecutablePath($commandPath)
static serviceExists($serviceName)
static getWscriptShell()
static createShortcut($shortcutPath, $targetPath, $workingDir='', $description='', $iconPath='')
static getWmiStdRegProv()
static countFilesFoldersManual($path)
static COM $wmiStdRegProv
static getServiceInfo($serviceName, $properties=[])
static COM $wscriptShell
static getSpecialFolderPath($folderName)
static registryDeleteKey($hive, $key)