Bearsampp 2026.9.23
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
39 private static function getWmiCimv2(): COM
40 {
41 if (self::$wmiCimv2 === null) {
42 self::$wmiCimv2 = new COM("winmgmts://./root/cimv2");
43 }
44
45 return self::$wmiCimv2;
46 }
47
53 private static function getWmiStdRegProv(): COM
54 {
55 if (self::$wmiStdRegProv === null) {
56 self::$wmiStdRegProv = new COM("winmgmts://./root/default:StdRegProv");
57 }
58
59 return self::$wmiStdRegProv;
60 }
61
67 private static function getWscriptShell(): COM
68 {
69 if (self::$wscriptShell === null) {
70 self::$wscriptShell = new COM("WScript.Shell");
71 }
72
73 return self::$wscriptShell;
74 }
75
82 public static function resetConnections(): void
83 {
84 self::$wmiCimv2 = null;
85 self::$wmiStdRegProv = null;
86 self::$wscriptShell = null;
87 }
88
97 public static function getProcessList($properties = [])
98 {
99 Log::debug('getProcessList: Listing processes (COM/WMI)');
100
101 $startTime = microtime(true);
102
103 try {
104 // Create WMI connection
105 $wmi = self::getWmiCimv2();
106
107 // Build WQL query
108 if (empty($properties)) {
109 $properties = ['Name', 'ProcessID', 'ExecutablePath'];
110 }
111
112 $selectClause = implode(', ', $properties);
113 $query = "SELECT {$selectClause} FROM Win32_Process";
114
115 // Execute query
116 $processes = $wmi->ExecQuery($query);
117
118 // Convert to array
119 $result = [];
120 foreach ($processes as $proc) {
121 $process = [];
122 foreach ($properties as $prop) {
123 // Handle property access
124 try {
125 $value = $proc->$prop;
126 $process[$prop] = $value ?? '';
127 } catch (Exception $e) {
128 $process[$prop] = '';
129 }
130 }
131 $result[] = $process;
132 }
133
134 $duration = round((microtime(true) - $startTime) * 1000, 2);
135 Log::debug('getProcessList: Found ' . count($result) . ' processes in ' . $duration . 'ms (COM/WMI)');
136
137 return $result;
138 } catch (Exception $e) {
140 Log::error('getProcessList: COM exception: ' . $e->getMessage());
141
142 return [];
143 }
144 }
145
154 public static function killProcess($pid)
155 {
156 // Validate PID
157 if (!is_numeric($pid) || $pid <= 0) {
158 Log::error('killProcess: Invalid PID: ' . $pid);
159
160 return false;
161 }
162
163 Log::debug('killProcess: Killing process PID ' . $pid . ' (COM/WMI)');
164
165 $startTime = microtime(true);
166
167 try {
168 // Create WMI connection
169 $wmi = self::getWmiCimv2();
170
171 // Query for specific process
172 $query = "SELECT * FROM Win32_Process WHERE ProcessID = {$pid}";
173 $processes = $wmi->ExecQuery($query);
174
175 // Terminate the process
176 $found = false;
177 $terminateResult = null;
178 foreach ($processes as $proc) {
179 $terminateResult = $proc->Terminate();
180 $found = true;
181 break;
182 }
183
184 $duration = round((microtime(true) - $startTime) * 1000, 2);
185
186 if (!$found) {
187 Log::debug('killProcess: Process ' . $pid . ' not found');
188
189 return false;
190 }
191
192 // Check if Terminate() returned success (0 = success)
193 if ($terminateResult !== 0) {
194 Log::error('killProcess: Terminate() failed with status code: ' . $terminateResult);
195
196 return false;
197 }
198
199 // Additional verification: check if process still exists after a short delay
200 usleep(100000); // Wait 100ms for termination to take effect
201 if (self::processExists($pid)) {
202 Log::error('killProcess: Process ' . $pid . ' still exists after termination attempt');
203
204 return false;
205 }
206
207 Log::debug('killProcess: Successfully killed process ' . $pid . ' in ' . $duration . 'ms (COM/WMI)');
208
209 return true;
210 } catch (Exception $e) {
212 Log::error('killProcess: COM exception: ' . $e->getMessage());
213
214 return false;
215 }
216 }
217
225 public static function processExists($pid)
226 {
227 if (!is_numeric($pid) || $pid <= 0) {
228 return false;
229 }
230
231 try {
232 $wmi = self::getWmiCimv2();
233 $query = "SELECT ProcessID FROM Win32_Process WHERE ProcessID = {$pid}";
234 $processes = $wmi->ExecQuery($query);
235
236 foreach ($processes as $proc) {
237 return true;
238 }
239
240 return false;
241 } catch (Exception $e) {
243
244 return false;
245 }
246 }
247
256 public static function getProcessInfo($pid, $properties = [])
257 {
258 if (!is_numeric($pid) || $pid <= 0) {
259 return false;
260 }
261
262 if (empty($properties)) {
263 $properties = ['Name', 'ProcessID', 'ExecutablePath', 'CommandLine'];
264 }
265
266 try {
267 $wmi = self::getWmiCimv2();
268 $selectClause = implode(', ', $properties);
269 $query = "SELECT {$selectClause} FROM Win32_Process WHERE ProcessID = {$pid}";
270 $processes = $wmi->ExecQuery($query);
271
272 foreach ($processes as $proc) {
273 $result = [];
274 foreach ($properties as $prop) {
275 try {
276 $value = $proc->$prop;
277 $result[$prop] = $value ?? '';
278 } catch (Exception $e) {
279 $result[$prop] = '';
280 }
281 }
282
283 return $result;
284 }
285
286 return false;
287 } catch (Exception $e) {
289 Log::error('getProcessInfo: COM exception: ' . $e->getMessage());
290
291 return false;
292 }
293 }
294
303 public static function findProcessesByName($name, $properties = [])
304 {
305 if (empty($name)) {
306 return [];
307 }
308
309 if (empty($properties)) {
310 $properties = ['Name', 'ProcessID', 'ExecutablePath'];
311 }
312
313 try {
314 $wmi = self::getWmiCimv2();
315 $selectClause = implode(', ', $properties);
316
317 // Sanitize the name parameter to prevent WQL injection
318 // Escape single quotes by doubling them (WQL standard)
319 $safeName = str_replace("'", "''", $name);
320
321 $query = "SELECT {$selectClause} FROM Win32_Process WHERE Name = '{$safeName}'";
322 $processes = $wmi->ExecQuery($query);
323
324 $result = [];
325 foreach ($processes as $proc) {
326 $process = [];
327 foreach ($properties as $prop) {
328 try {
329 $value = $proc->$prop;
330 $process[$prop] = $value ?? '';
331 } catch (Exception $e) {
332 $process[$prop] = '';
333 }
334 }
335 $result[] = $process;
336 }
337
338 return $result;
339 } catch (Exception $e) {
341 Log::error('findProcessesByName: COM exception: ' . $e->getMessage());
342
343 return [];
344 }
345 }
346
352 public static function getCurrentPid()
353 {
354 return getmypid();
355 }
356
357 // ========================================================================
358 // Registry Operations (COM/WScript.Shell)
359 // ========================================================================
360
368 private static function mapRegistryHive($hive)
369 {
370 $hiveMap = [
371 'HKLM' => 'HKLM',
372 'HKCU' => 'HKCU',
373 'HKCR' => 'HKCR',
374 'HKU' => 'HKU',
375 'HKEY_LOCAL_MACHINE' => 'HKLM',
376 'HKEY_CURRENT_USER' => 'HKCU',
377 'HKEY_CLASSES_ROOT' => 'HKCR',
378 'HKEY_USERS' => 'HKU',
379 ];
380
381 return isset($hiveMap[$hive]) ? $hiveMap[$hive] : $hive;
382 }
383
397 public static function registryExists($hive, $key, $value = null)
398 {
399 $hive = self::mapRegistryHive($hive);
400 $regPath = $hive . '\\' . $key;
401
402 Log::debug('registryExists: Checking ' . $regPath . ($value !== null ? '\\' . $value : ' (key)') . ' (COM)');
403
404 try {
405 if ($value === null) {
406 // Check if the key itself exists
407 // Use StdRegProv::EnumKey for reliable key existence checking
408 // This avoids false negatives from checking for a default value that may not exist
409 try {
410 $wmi = self::getWmiStdRegProv();
411
412 // Map hive names to WMI constants
413 $hiveConstMap = [
414 'HKCR' => 0x80000000, // HKEY_CLASSES_ROOT
415 'HKCU' => 0x80000001, // HKEY_CURRENT_USER
416 'HKLM' => 0x80000002, // HKEY_LOCAL_MACHINE
417 'HKU' => 0x80000003, // HKEY_USERS
418 ];
419
420 $hConst = $hiveConstMap[$hive] ?? 0x80000002; // Default to HKLM
421
422 // EnumKey checks if parent key's subkeys contain the requested key
423 // For root-level checks, pass empty parent
424 $pathParts = explode('\\', $key);
425
426 if (count($pathParts) === 1) {
427 // Top-level key: parent is root
428 $parentKey = '';
429 $keyName = $pathParts[0];
430 } else {
431 // Nested key: split parent from key name
432 $keyName = array_pop($pathParts);
433 $parentKey = implode('\\', $pathParts);
434 }
435
436 $subKeys = null;
437 $rc = $wmi->EnumKey($hConst, $parentKey, $subKeys);
438
439 if ($rc !== 0 || !is_array($subKeys)) {
440 Log::debug('registryExists: Key not found (EnumKey failed)');
441
442 return false;
443 }
444
445 // Check if our key name is in the list of subkeys
446 $exists = false;
447 foreach ($subKeys as $subKey) {
448 if (strcasecmp($subKey, $keyName) === 0) {
449 $exists = true;
450 break;
451 }
452 }
453
454 if ($exists) {
455 Log::debug('registryExists: Key found');
456
457 return true;
458 } else {
459 Log::debug('registryExists: Key not found');
460
461 return false;
462 }
463 } catch (Exception $e) {
464 self::$wmiStdRegProv = null;
465 Log::error('registryExists: StdRegProv exception during key check: ' . $e->getMessage());
466
467 return false;
468 }
469 } else {
470 // Check if a specific value exists within the key
471 // Use WScript.Shell::RegRead for value existence
472 try {
473 $shell = self::getWscriptShell();
474 $valuePath = $regPath . '\\' . $value;
475 $shell->RegRead($valuePath);
476 Log::debug('registryExists: Value found');
477
478 return true;
479 } catch (Exception $e) {
480 Log::debug('registryExists: Value not found');
481
482 return false;
483 }
484 }
485 } catch (Exception $e) {
487 Log::error('registryExists: COM exception: ' . $e->getMessage());
488
489 return false;
490 }
491 }
492
503 public static function registryGetValue($hive, $key, $value = '')
504 {
505 $hive = self::mapRegistryHive($hive);
506 $regPath = $hive . '\\' . $key;
507
508 if ($value !== '') {
509 $regPath .= '\\' . $value;
510 } else {
511 // For default value, append backslash
512 $regPath .= '\\';
513 }
514
515 Log::debug('registryGetValue: Reading ' . $regPath . ' (COM)');
516
517 $startTime = microtime(true);
518
519 try {
520 $shell = self::getWscriptShell();
521 $result = $shell->RegRead($regPath);
522
523 $duration = round((microtime(true) - $startTime) * 1000, 2);
524
525 // Convert result to appropriate PHP type
526 if (is_object($result)) {
527 // COM objects need special handling
528 $result = (string)$result;
529 }
530
531 Log::debug('registryGetValue: Found value in ' . $duration . 'ms (COM)');
532
533 return $result;
534 } catch (Exception $e) {
535 Log::debug('registryGetValue: Value not found');
536
537 return null;
538 }
539 }
540
553 public static function registrySetValue($hive, $key, $value, $data, $type = 'REG_SZ')
554 {
555 $hive = self::mapRegistryHive($hive);
556 $regPath = $hive . '\\' . $key . '\\' . $value;
557
558 // Validate type
559 $validTypes = ['REG_SZ', 'REG_EXPAND_SZ', 'REG_DWORD', 'REG_BINARY'];
560 if (!in_array($type, $validTypes)) {
561 Log::error('registrySetValue: Invalid type: ' . $type);
562
563 return false;
564 }
565
566 Log::debug('registrySetValue: Writing ' . $regPath . ' (' . $type . ') (COM)');
567
568 $startTime = microtime(true);
569
570 try {
571 $shell = self::getWscriptShell();
572
573 // Convert data based on type
574 if ($type === 'REG_DWORD') {
575 $data = (int)$data;
576 }
577
578 // Write the value
579 $shell->RegWrite($regPath, $data, $type);
580
581 $duration = round((microtime(true) - $startTime) * 1000, 2);
582 Log::debug('registrySetValue: Successfully wrote value in ' . $duration . 'ms (COM)');
583
584 return true;
585 } catch (Exception $e) {
587 Log::error('registrySetValue: COM exception: ' . $e->getMessage());
588
589 return false;
590 }
591 }
592
603 public static function registryDeleteValue($hive, $key, $value)
604 {
605 $hive = self::mapRegistryHive($hive);
606 $regPath = $hive . '\\' . $key . '\\' . $value;
607
608 Log::debug('registryDeleteValue: Deleting ' . $regPath . ' (COM)');
609
610 $startTime = microtime(true);
611
612 try {
613 $shell = self::getWscriptShell();
614
615 // Delete the value
616 $shell->RegDelete($regPath);
617
618 $duration = round((microtime(true) - $startTime) * 1000, 2);
619 Log::debug('registryDeleteValue: Successfully deleted value in ' . $duration . 'ms (COM)');
620
621 return true;
622 } catch (Exception $e) {
623 // If the value doesn't exist, that's OK
624 $errorMsg = $e->getMessage();
625 if (strpos($errorMsg, 'Unable to remove') !== false ||
626 strpos($errorMsg, 'Invalid root') !== false) {
627 Log::debug('registryDeleteValue: Value does not exist (already deleted)');
628
629 return true;
630 }
631
633 Log::error('registryDeleteValue: COM exception: ' . $e->getMessage());
634
635 return false;
636 }
637 }
638
648 public static function registryDeleteKey($hive, $key)
649 {
650 $hive = self::mapRegistryHive($hive);
651 $regPath = $hive . '\\' . $key . '\\';
652
653 Log::debug('registryDeleteKey: Deleting ' . $regPath . ' (COM)');
654
655 try {
656 $shell = self::getWscriptShell();
657
658 // Delete the key (note the trailing backslash)
659 $shell->RegDelete($regPath);
660
661 Log::debug('registryDeleteKey: Successfully deleted key (COM)');
662
663 return true;
664 } catch (Exception $e) {
665 // If the key doesn't exist, that's OK
666 $errorMsg = $e->getMessage();
667 if (strpos($errorMsg, 'Unable to remove') !== false ||
668 strpos($errorMsg, 'Invalid root') !== false) {
669 Log::debug('registryDeleteKey: Key does not exist (already deleted)');
670
671 return true;
672 }
673
675 Log::error('registryDeleteKey: COM exception: ' . $e->getMessage());
676
677 return false;
678 }
679 }
680
681 // ========================================================================
682 // Shortcuts & Special Paths (COM/WScript.Shell)
683 // ========================================================================
684
693 public static function getSpecialFolderPath($folderName)
694 {
695 Log::debug('getSpecialFolderPath: Getting ' . $folderName . ' path (COM)');
696
697 $startTime = microtime(true);
698
699 try {
700 $shell = self::getWscriptShell();
701
702 // Get the special folder path
703 $path = $shell->SpecialFolders($folderName);
704
705 $duration = round((microtime(true) - $startTime) * 1000, 2);
706
707 if ($path && !empty($path)) {
708 // Convert to Unix-style path
709 $path = str_replace('\\', '/', $path);
710 Log::debug('getSpecialFolderPath: Found ' . $folderName . ' in ' . $duration . 'ms (COM)');
711
712 return $path;
713 } else {
714 Log::debug('getSpecialFolderPath: ' . $folderName . ' not found');
715
716 return false;
717 }
718 } catch (Exception $e) {
720 Log::error('getSpecialFolderPath: COM exception: ' . $e->getMessage());
721
722 return false;
723 }
724 }
725
738 public static function createShortcut($shortcutPath, $targetPath, $workingDir = '', $description = '', $iconPath = '')
739 {
740 Log::debug('createShortcut: Creating shortcut at ' . $shortcutPath . ' (COM)');
741
742 $startTime = microtime(true);
743
744 try {
745 $shell = self::getWscriptShell();
746
747 // Create the shortcut object
748 $shortcut = $shell->CreateShortcut($shortcutPath);
749
750 // Set shortcut properties
751 $shortcut->TargetPath = $targetPath;
752
753 if (!empty($workingDir)) {
754 $shortcut->WorkingDirectory = $workingDir;
755 }
756
757 if (!empty($description)) {
758 $shortcut->Description = $description;
759 }
760
761 if (!empty($iconPath)) {
762 $shortcut->IconLocation = $iconPath;
763 }
764
765 // Save the shortcut
766 $shortcut->Save();
767
768 $duration = round((microtime(true) - $startTime) * 1000, 2);
769 Log::debug('createShortcut: Successfully created shortcut in ' . $duration . 'ms (COM)');
770
771 return true;
772 } catch (Exception $e) {
774 Log::error('createShortcut: COM exception: ' . $e->getMessage());
775
776 return false;
777 }
778 }
779
780 // ========================================================================
781 // File Operations (COM/FileSystemObject or Native PHP)
782 // ========================================================================
783
792 public static function countFilesFolders($path)
793 {
794 Log::debug('countFilesFolders: Counting in ' . $path . ' (Native PHP)');
795
796 $startTime = microtime(true);
797
798 try {
799 if (!is_dir($path)) {
800 Log::error('countFilesFolders: Path is not a directory: ' . $path);
801
802 return false;
803 }
804
805 $count = 0;
806
807 // Use RecursiveDirectoryIterator for efficient recursive counting
808 try {
809 $iterator = new RecursiveIteratorIterator(
810 new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
811 RecursiveIteratorIterator::SELF_FIRST
812 );
813
814 foreach ($iterator as $item) {
815 $count++;
816 }
817 } catch (Exception $e) {
818 // If RecursiveIterator fails, fall back to manual recursion
819 Log::debug('countFilesFolders: RecursiveIterator failed, using manual recursion');
820 $count = self::countFilesFoldersManual($path);
821 }
822
823 $duration = round((microtime(true) - $startTime) * 1000, 2);
824 Log::debug('countFilesFolders: Counted ' . $count . ' items in ' . $duration . 'ms (Native PHP)');
825
826 return $count;
827 } catch (Exception $e) {
828 Log::error('countFilesFolders: Exception: ' . $e->getMessage());
829
830 return false;
831 }
832 }
833
842 private static function countFilesFoldersManual($path)
843 {
844 $count = 0;
845
846 try {
847 $items = @scandir($path);
848
849 if ($items === false) {
850 return 0;
851 }
852
853 foreach ($items as $item) {
854 if ($item === '.' || $item === '..') {
855 continue;
856 }
857
858 $fullPath = $path . DIRECTORY_SEPARATOR . $item;
859 $count++; // Count this item
860
861 if (is_dir($fullPath)) {
862 // Recursively count subdirectory
863 $count += self::countFilesFoldersManual($fullPath);
864 }
865 }
866 } catch (Exception $e) {
867 // Silently handle errors (permission denied, etc.)
868 }
869
870 return $count;
871 }
872
881 public static function countFilesFoldersCOM($path)
882 {
883 Log::debug('countFilesFoldersCOM: Counting in ' . $path . ' (COM/FSO)');
884
885 $startTime = microtime(true);
886
887 try {
888 $fso = new COM("Scripting.FileSystemObject");
889
890 if (!$fso->FolderExists($path)) {
891 Log::error('countFilesFoldersCOM: Path does not exist: ' . $path);
892
893 return false;
894 }
895
896 $count = self::countFolderItemsCOM($fso, $path);
897
898 $duration = round((microtime(true) - $startTime) * 1000, 2);
899 Log::debug('countFilesFoldersCOM: Counted ' . $count . ' items in ' . $duration . 'ms (COM/FSO)');
900
901 return $count;
902 } catch (Exception $e) {
903 Log::error('countFilesFoldersCOM: COM exception: ' . $e->getMessage());
904
905 return false;
906 }
907 }
908
918 private static function countFolderItemsCOM($fso, $path)
919 {
920 try {
921 $folder = $fso->GetFolder($path);
922
923 // Count files and subfolders in this folder
924 $count = $folder->Files->Count + $folder->SubFolders->Count;
925
926 // Recursively count subfolders
927 foreach ($folder->SubFolders as $subFolder) {
928 $count += self::countFolderItemsCOM($fso, $subFolder->Path);
929 }
930
931 return $count;
932 } catch (Exception $e) {
933 // Silently handle errors (permission denied, etc.)
934 return 0;
935 }
936 }
937
938 // ========================================================================
939 // Service Operations (COM/WMI)
940 // ========================================================================
941
951 public static function getServiceInfo($serviceName, $properties = [])
952 {
953 Log::debug('getServiceInfo: Getting info for service ' . $serviceName . ' (COM/WMI)');
954
955 $startTime = microtime(true);
956
957 try {
958 // Create WMI connection
959 $wmi = self::getWmiCimv2();
960
961 // Default properties if none specified
962 if (empty($properties)) {
963 $properties = [
964 'Name',
965 'DisplayName',
966 'State',
967 'Status',
968 'StartMode',
969 'PathName',
970 'ProcessId',
971 'Started',
972 'StartName',
973 'Description'
974 ];
975 }
976
977 // Build WQL query
978 $selectClause = implode(', ', $properties);
979 $safeServiceName = str_replace("'", "''", $serviceName);
980 $query = "SELECT {$selectClause} FROM Win32_Service WHERE Name = '{$safeServiceName}'";
981
982 // Execute query
983 $services = $wmi->ExecQuery($query);
984
985 // Get the first (and should be only) result
986 foreach ($services as $service) {
987 $result = [];
988 foreach ($properties as $prop) {
989 try {
990 $value = $service->$prop;
991 $result[$prop] = $value ?? '';
992 } catch (Exception $e) {
993 $result[$prop] = '';
994 }
995 }
996
997 $duration = round((microtime(true) - $startTime) * 1000, 2);
998 Log::debug('getServiceInfo: Found service in ' . $duration . 'ms (COM/WMI)');
999
1000 return $result;
1001 }
1002
1003 // Service not found
1004 Log::debug('getServiceInfo: Service not found');
1005
1006 return false;
1007 } catch (Exception $e) {
1009 Log::error('getServiceInfo: COM exception: ' . $e->getMessage());
1010
1011 return false;
1012 }
1013 }
1014
1023 public static function listServices($properties = [])
1024 {
1025 Log::debug('listServices: Listing all services (COM/WMI)');
1026
1027 $startTime = microtime(true);
1028
1029 try {
1030 // Create WMI connection
1031 $wmi = self::getWmiCimv2();
1032
1033 // Default properties if none specified
1034 if (empty($properties)) {
1035 $properties = ['Name', 'DisplayName', 'State', 'StartMode'];
1036 }
1037
1038 // Build WQL query
1039 $selectClause = implode(', ', $properties);
1040 $query = "SELECT {$selectClause} FROM Win32_Service";
1041
1042 // Execute query
1043 $services = $wmi->ExecQuery($query);
1044
1045 // Convert to array
1046 $result = [];
1047 foreach ($services as $service) {
1048 $serviceInfo = [];
1049 foreach ($properties as $prop) {
1050 try {
1051 $value = $service->$prop;
1052 $serviceInfo[$prop] = $value ?? '';
1053 } catch (Exception $e) {
1054 $serviceInfo[$prop] = '';
1055 }
1056 }
1057 $result[] = $serviceInfo;
1058 }
1059
1060 $duration = round((microtime(true) - $startTime) * 1000, 2);
1061 Log::debug('listServices: Found ' . count($result) . ' services in ' . $duration . 'ms (COM/WMI)');
1062
1063 return $result;
1064 } catch (Exception $e) {
1066 Log::error('listServices: COM exception: ' . $e->getMessage());
1067
1068 return [];
1069 }
1070 }
1071
1080 public static function serviceExists($serviceName)
1081 {
1082 try {
1083 $wmi = self::getWmiCimv2();
1084 // Sanitize the serviceName parameter to prevent WQL injection
1085 // Escape single quotes by doubling them (WQL standard)
1086 $safeServiceName = str_replace("'", "''", $serviceName);
1087 $query = "SELECT Name FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1088
1089 // Execute query
1090 $services = $wmi->ExecQuery($query);
1091
1092 foreach ($services as $service) {
1093 return true;
1094 }
1095
1096 return false;
1097 } catch (Exception $e) {
1099
1100 return false;
1101 }
1102 }
1103
1112 public static function getServiceState($serviceName)
1113 {
1114 try {
1115 $wmi = self::getWmiCimv2();
1116 // Sanitize the serviceName parameter to prevent WQL injection
1117 // Escape single quotes by doubling them (WQL standard)
1118 $safeServiceName = str_replace("'", "''", $serviceName);
1119 $query = "SELECT State FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1120
1121 // Execute query
1122 $services = $wmi->ExecQuery($query);
1123
1124 foreach ($services as $service) {
1125 return $service->State;
1126 }
1127
1128 return false;
1129 } catch (Exception $e) {
1131
1132 return false;
1133 }
1134 }
1135
1136 // ========================================================================
1137 // Browser Detection (COM/Registry)
1138 // ========================================================================
1139
1146 public static function getDefaultBrowser()
1147 {
1148 Log::debug('getDefaultBrowser: Reading default browser (COM)');
1149
1150 $startTime = microtime(true);
1151
1152 // Try to read the default browser from registry
1153 $browserPath = self::registryGetValue('HKLM', 'SOFTWARE\\Classes\\http\\shell\\open\\command', '');
1154
1155 if ($browserPath === null) {
1156 Log::debug('getDefaultBrowser: No default browser found');
1157
1158 return false;
1159 }
1160
1161 $duration = round((microtime(true) - $startTime) * 1000, 2);
1162
1163 // Extract the executable path from the command
1164 // Format is usually: "C:\Program Files\Browser\browser.exe" -- "%1"
1165 if (preg_match('/"([^"]+)"/', $browserPath, $matches)) {
1166 $path = $matches[1];
1167 } else {
1168 // No quotes, take everything before the first space or use as-is
1169 $path = trim(explode(' ', $browserPath)[0]);
1170 $path = str_replace('"', '', $path);
1171 }
1172
1173 Log::debug('getDefaultBrowser: Found browser in ' . $duration . 'ms (COM)');
1174
1175 return $path;
1176 }
1177
1184 public static function getInstalledBrowsers()
1185 {
1186 Log::debug('getInstalledBrowsers: Enumerating installed browsers (Hybrid - No VBS)');
1187
1188 $startTime = microtime(true);
1189 $browsers = [];
1190
1191 // Known browser registry names (covers 99% of browsers)
1192 $knownBrowsers = [
1193 // Major browsers
1194 'Google Chrome',
1195 'Microsoft Edge',
1196 'Opera',
1197 'Brave',
1198 'Vivaldi',
1199 'Chromium',
1200
1201 // Firefox variants
1202 'Firefox',
1203 'Firefox-308046B0AF4A39CB', // Common Firefox GUID
1204 'Firefox Developer Edition',
1205 'Firefox Nightly',
1206
1207 // Other browsers
1208 'Safari',
1209 'Waterfox',
1210 'Pale Moon',
1211 'Yandex',
1212 'IEXPLORE.EXE',
1213 'Total Browser',
1214 'Maxthon',
1215 'Seamonkey',
1216 'K-Meleon',
1217 'Basilisk',
1218 'Tor Browser',
1219 'Slimjet',
1220 'Epic Privacy Browser',
1221 'Comodo Dragon',
1222 'SRWare Iron',
1223 'Cent Browser',
1224 '360 Browser',
1225 'UC Browser',
1226 'Avast Secure Browser',
1227 'AVG Secure Browser',
1228 ];
1229
1230 // Registry paths to check
1231 $registryPaths = [
1232 ['hive' => 'HKLM', 'key' => 'SOFTWARE\\WOW6432Node\\Clients\\StartMenuInternet'],
1233 ['hive' => 'HKLM', 'key' => 'SOFTWARE\\Clients\\StartMenuInternet'],
1234 ['hive' => 'HKCU', 'key' => 'SOFTWARE\\Clients\\StartMenuInternet'],
1235 ];
1236
1237 // Check each known browser in each registry path
1238 foreach ($registryPaths as $regPath) {
1239 $hive = $regPath['hive'];
1240 $basePath = $regPath['key'];
1241
1242 foreach ($knownBrowsers as $browserName) {
1243 try {
1244 // Try to read the browser's command path
1245 $commandPath = self::registryGetValue(
1246 $hive,
1247 $basePath . '\\' . $browserName . '\\shell\\open\\command',
1248 ''
1249 );
1250
1251 if ($commandPath !== null && !empty($commandPath)) {
1252 // Extract executable path
1253 $exePath = self::extractBrowserExecutablePath($commandPath);
1254
1255 if ($exePath && !in_array($exePath, $browsers)) {
1256 Log::debug('getInstalledBrowsers: Found ' . $browserName . ': ' . $exePath);
1257 $browsers[] = $exePath;
1258 }
1259 }
1260 } catch (Exception $e) {
1261 // Browser not found, continue
1262 continue;
1263 }
1264 }
1265 }
1266
1267 $duration = round((microtime(true) - $startTime) * 1000, 2);
1268 Log::debug('getInstalledBrowsers: Found ' . count($browsers) . ' browser(s) in ' . $duration . 'ms (Hybrid - No VBS)');
1269
1270 // Always return an array (possibly empty) to simplify call sites
1271 return $browsers;
1272 }
1273
1282 private static function extractBrowserExecutablePath($commandPath)
1283 {
1284 if (empty($commandPath)) {
1285 return false;
1286 }
1287
1288 // Method 1: Extract path from quotes
1289 // Format: "C:\Program Files\Browser\browser.exe" --arguments
1290 if (preg_match('/"([^"]+\\.exe)"/i', $commandPath, $matches)) {
1291 return $matches[1];
1292 }
1293
1294 // Method 2: Extract path without quotes but with .exe
1295 // Format: C:\Program Files\Browser\browser.exe --arguments
1296 if (preg_match('/^([^\\s]+\\.exe)/i', trim($commandPath), $matches)) {
1297 return $matches[1];
1298 }
1299
1300 // Method 3: Handle paths with spaces but no quotes (rare)
1301 // Try to find .exe in the string
1302 if (preg_match('/([A-Z]:\\\\[^"]+\\.exe)/i', $commandPath, $matches)) {
1303 return $matches[1];
1304 }
1305
1306 // Method 4: Fallback - take everything before first space
1307 $parts = explode(' ', trim($commandPath));
1308 $path = str_replace('"', '', $parts[0]);
1309
1310 // Validate it looks like a path
1311 if (stripos($path, '.exe') !== false) {
1312 return $path;
1313 }
1314
1315 return false;
1316 }
1317}
1318
$result
$proc
Definition ajax.php:130
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)