39 if (self::$wmiCimv2 ===
null) {
40 self::$wmiCimv2 =
new COM(
"winmgmts://./root/cimv2");
42 return self::$wmiCimv2;
50 if (self::$wmiStdRegProv ===
null) {
51 self::$wmiStdRegProv =
new COM(
"winmgmts://./root/default:StdRegProv");
53 return self::$wmiStdRegProv;
61 if (self::$wscriptShell ===
null) {
62 self::$wscriptShell =
new COM(
"WScript.Shell");
64 return self::$wscriptShell;
73 self::$wmiCimv2 =
null;
74 self::$wmiStdRegProv =
null;
75 self::$wscriptShell =
null;
87 Log::debug(
'getProcessList: Listing processes (COM/WMI)');
89 $startTime = microtime(
true);
96 if (empty($properties)) {
97 $properties = [
'Name',
'ProcessID',
'ExecutablePath'];
100 $selectClause = implode(
', ', $properties);
101 $query =
"SELECT {$selectClause} FROM Win32_Process";
104 $processes = $wmi->ExecQuery($query);
108 foreach ($processes as
$proc) {
110 foreach ($properties as $prop) {
113 $value =
$proc->$prop;
114 $process[$prop] = $value ??
'';
115 }
catch (Exception $e) {
116 $process[$prop] =
'';
122 $duration = round((microtime(
true) - $startTime) * 1000, 2);
123 Log::debug(
'getProcessList: Found ' . count(
$result) .
' processes in ' . $duration .
'ms (COM/WMI)');
127 }
catch (Exception $e) {
129 Log::error(
'getProcessList: COM exception: ' . $e->getMessage());
144 if (!is_numeric($pid) || $pid <= 0) {
145 Log::error(
'killProcess: Invalid PID: ' . $pid);
149 Log::debug(
'killProcess: Killing process PID ' . $pid .
' (COM/WMI)');
151 $startTime = microtime(
true);
158 $query =
"SELECT * FROM Win32_Process WHERE ProcessID = {$pid}";
159 $processes = $wmi->ExecQuery($query);
163 $terminateResult =
null;
164 foreach ($processes as
$proc) {
165 $terminateResult =
$proc->Terminate();
170 $duration = round((microtime(
true) - $startTime) * 1000, 2);
173 Log::debug(
'killProcess: Process ' . $pid .
' not found');
178 if ($terminateResult !== 0) {
179 Log::error(
'killProcess: Terminate() failed with status code: ' . $terminateResult);
185 if (self::processExists($pid)) {
186 Log::error(
'killProcess: Process ' . $pid .
' still exists after termination attempt');
190 Log::debug(
'killProcess: Successfully killed process ' . $pid .
' in ' . $duration .
'ms (COM/WMI)');
193 }
catch (Exception $e) {
195 Log::error(
'killProcess: COM exception: ' . $e->getMessage());
208 if (!is_numeric($pid) || $pid <= 0) {
214 $query =
"SELECT ProcessID FROM Win32_Process WHERE ProcessID = {$pid}";
215 $processes = $wmi->ExecQuery($query);
217 foreach ($processes as
$proc) {
223 }
catch (Exception $e) {
238 if (!is_numeric($pid) || $pid <= 0) {
242 if (empty($properties)) {
243 $properties = [
'Name',
'ProcessID',
'ExecutablePath',
'CommandLine'];
248 $selectClause = implode(
', ', $properties);
249 $query =
"SELECT {$selectClause} FROM Win32_Process WHERE ProcessID = {$pid}";
250 $processes = $wmi->ExecQuery($query);
252 foreach ($processes as
$proc) {
254 foreach ($properties as $prop) {
256 $value =
$proc->$prop;
258 }
catch (Exception $e) {
267 }
catch (Exception $e) {
269 Log::error(
'getProcessInfo: COM exception: ' . $e->getMessage());
287 if (empty($properties)) {
288 $properties = [
'Name',
'ProcessID',
'ExecutablePath'];
293 $selectClause = implode(
', ', $properties);
297 $safeName = str_replace(
"'",
"''", $name);
299 $query =
"SELECT {$selectClause} FROM Win32_Process WHERE Name = '{$safeName}'";
300 $processes = $wmi->ExecQuery($query);
303 foreach ($processes as
$proc) {
305 foreach ($properties as $prop) {
307 $value =
$proc->$prop;
308 $process[$prop] = $value ??
'';
309 }
catch (Exception $e) {
310 $process[$prop] =
'';
318 }
catch (Exception $e) {
320 Log::error(
'findProcessesByName: COM exception: ' . $e->getMessage());
352 'HKEY_LOCAL_MACHINE' =>
'HKLM',
353 'HKEY_CURRENT_USER' =>
'HKCU',
354 'HKEY_CLASSES_ROOT' =>
'HKCR',
355 'HKEY_USERS' =>
'HKU',
358 return isset($hiveMap[$hive]) ? $hiveMap[$hive] : $hive;
376 $regPath = $hive .
'\\' . $key;
378 Log::debug(
'registryExists: Checking ' . $regPath . ($value !==
null ?
'\\' . $value :
' (key)') .
' (COM)');
381 if ($value ===
null) {
390 'HKCR' => 0x80000000,
391 'HKCU' => 0x80000001,
392 'HKLM' => 0x80000002,
396 $hConst = $hiveConstMap[$hive] ?? 0x80000002;
400 $pathParts = explode(
'\\', $key);
402 if (count($pathParts) === 1) {
405 $keyName = $pathParts[0];
408 $keyName = array_pop($pathParts);
409 $parentKey = implode(
'\\', $pathParts);
413 $rc = $wmi->EnumKey($hConst, $parentKey, $subKeys);
415 if ($rc !== 0 || !is_array($subKeys)) {
416 Log::debug(
'registryExists: Key not found (EnumKey failed)');
422 foreach ($subKeys as $subKey) {
423 if (strcasecmp($subKey, $keyName) === 0) {
437 }
catch (Exception $e) {
438 self::$wmiStdRegProv =
null;
439 Log::error(
'registryExists: StdRegProv exception during key check: ' . $e->getMessage());
447 $valuePath = $regPath .
'\\' . $value;
448 $shell->RegRead($valuePath);
451 }
catch (Exception $e) {
452 Log::debug(
'registryExists: Value not found');
457 }
catch (Exception $e) {
459 Log::error(
'registryExists: COM exception: ' . $e->getMessage());
476 $regPath = $hive .
'\\' . $key;
479 $regPath .=
'\\' . $value;
485 Log::debug(
'registryGetValue: Reading ' . $regPath .
' (COM)');
487 $startTime = microtime(
true);
491 $result = $shell->RegRead($regPath);
493 $duration = round((microtime(
true) - $startTime) * 1000, 2);
501 Log::debug(
'registryGetValue: Found value in ' . $duration .
'ms (COM)');
504 }
catch (Exception $e) {
505 Log::debug(
'registryGetValue: Value not found');
524 $regPath = $hive .
'\\' . $key .
'\\' . $value;
527 $validTypes = [
'REG_SZ',
'REG_EXPAND_SZ',
'REG_DWORD',
'REG_BINARY'];
528 if (!in_array($type, $validTypes)) {
529 Log::error(
'registrySetValue: Invalid type: ' . $type);
533 Log::debug(
'registrySetValue: Writing ' . $regPath .
' (' . $type .
') (COM)');
535 $startTime = microtime(
true);
541 if ($type ===
'REG_DWORD') {
546 $shell->RegWrite($regPath, $data, $type);
548 $duration = round((microtime(
true) - $startTime) * 1000, 2);
549 Log::debug(
'registrySetValue: Successfully wrote value in ' . $duration .
'ms (COM)');
553 }
catch (Exception $e) {
555 Log::error(
'registrySetValue: COM exception: ' . $e->getMessage());
572 $regPath = $hive .
'\\' . $key .
'\\' . $value;
574 Log::debug(
'registryDeleteValue: Deleting ' . $regPath .
' (COM)');
576 $startTime = microtime(
true);
582 $shell->RegDelete($regPath);
584 $duration = round((microtime(
true) - $startTime) * 1000, 2);
585 Log::debug(
'registryDeleteValue: Successfully deleted value in ' . $duration .
'ms (COM)');
589 }
catch (Exception $e) {
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)');
599 Log::error(
'registryDeleteValue: COM exception: ' . $e->getMessage());
615 $regPath = $hive .
'\\' . $key .
'\\';
617 Log::debug(
'registryDeleteKey: Deleting ' . $regPath .
' (COM)');
623 $shell->RegDelete($regPath);
625 Log::debug(
'registryDeleteKey: Successfully deleted key (COM)');
628 }
catch (Exception $e) {
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)');
638 Log::error(
'registryDeleteKey: COM exception: ' . $e->getMessage());
656 Log::debug(
'getSpecialFolderPath: Getting ' . $folderName .
' path (COM)');
658 $startTime = microtime(
true);
664 $path = $shell->SpecialFolders($folderName);
666 $duration = round((microtime(
true) - $startTime) * 1000, 2);
668 if ($path && !empty($path)) {
670 $path = str_replace(
'\\',
'/', $path);
671 Log::debug(
'getSpecialFolderPath: Found ' . $folderName .
' in ' . $duration .
'ms (COM)');
674 Log::debug(
'getSpecialFolderPath: ' . $folderName .
' not found');
678 }
catch (Exception $e) {
680 Log::error(
'getSpecialFolderPath: COM exception: ' . $e->getMessage());
696 public static function createShortcut($shortcutPath, $targetPath, $workingDir =
'', $description =
'', $iconPath =
'')
698 Log::debug(
'createShortcut: Creating shortcut at ' . $shortcutPath .
' (COM)');
700 $startTime = microtime(
true);
706 $shortcut = $shell->CreateShortcut($shortcutPath);
709 $shortcut->TargetPath = $targetPath;
711 if (!empty($workingDir)) {
712 $shortcut->WorkingDirectory = $workingDir;
715 if (!empty($description)) {
716 $shortcut->Description = $description;
719 if (!empty($iconPath)) {
720 $shortcut->IconLocation = $iconPath;
726 $duration = round((microtime(
true) - $startTime) * 1000, 2);
727 Log::debug(
'createShortcut: Successfully created shortcut in ' . $duration .
'ms (COM)');
731 }
catch (Exception $e) {
733 Log::error(
'createShortcut: COM exception: ' . $e->getMessage());
751 Log::debug(
'countFilesFolders: Counting in ' . $path .
' (Native PHP)');
753 $startTime = microtime(
true);
756 if (!is_dir($path)) {
757 Log::error(
'countFilesFolders: Path is not a directory: ' . $path);
765 $iterator =
new RecursiveIteratorIterator(
766 new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
767 RecursiveIteratorIterator::SELF_FIRST
770 foreach ($iterator as $item) {
773 }
catch (Exception $e) {
775 Log::debug(
'countFilesFolders: RecursiveIterator failed, using manual recursion');
779 $duration = round((microtime(
true) - $startTime) * 1000, 2);
780 Log::debug(
'countFilesFolders: Counted ' . $count .
' items in ' . $duration .
'ms (Native PHP)');
784 }
catch (Exception $e) {
785 Log::error(
'countFilesFolders: Exception: ' . $e->getMessage());
802 $items = @scandir($path);
804 if ($items ===
false) {
808 foreach ($items as $item) {
809 if ($item ===
'.' || $item ===
'..') {
813 $fullPath = $path . DIRECTORY_SEPARATOR . $item;
816 if (is_dir($fullPath)) {
821 }
catch (Exception $e) {
837 Log::debug(
'countFilesFoldersCOM: Counting in ' . $path .
' (COM/FSO)');
839 $startTime = microtime(
true);
842 $fso =
new COM(
"Scripting.FileSystemObject");
844 if (!$fso->FolderExists($path)) {
845 Log::error(
'countFilesFoldersCOM: Path does not exist: ' . $path);
851 $duration = round((microtime(
true) - $startTime) * 1000, 2);
852 Log::debug(
'countFilesFoldersCOM: Counted ' . $count .
' items in ' . $duration .
'ms (COM/FSO)');
856 }
catch (Exception $e) {
857 Log::error(
'countFilesFoldersCOM: COM exception: ' . $e->getMessage());
873 $folder = $fso->GetFolder($path);
876 $count = $folder->Files->Count + $folder->SubFolders->Count;
879 foreach ($folder->SubFolders as $subFolder) {
885 }
catch (Exception $e) {
905 Log::debug(
'getServiceInfo: Getting info for service ' . $serviceName .
' (COM/WMI)');
907 $startTime = microtime(
true);
914 if (empty($properties)) {
930 $selectClause = implode(
', ', $properties);
931 $safeServiceName = str_replace(
"'",
"''", $serviceName);
932 $query =
"SELECT {$selectClause} FROM Win32_Service WHERE Name = '{$safeServiceName}'";
935 $services = $wmi->ExecQuery($query);
938 foreach ($services as $service) {
940 foreach ($properties as $prop) {
942 $value = $service->$prop;
944 }
catch (Exception $e) {
949 $duration = round((microtime(
true) - $startTime) * 1000, 2);
950 Log::debug(
'getServiceInfo: Found service in ' . $duration .
'ms (COM/WMI)');
956 Log::debug(
'getServiceInfo: Service not found');
959 }
catch (Exception $e) {
961 Log::error(
'getServiceInfo: COM exception: ' . $e->getMessage());
975 Log::debug(
'listServices: Listing all services (COM/WMI)');
977 $startTime = microtime(
true);
984 if (empty($properties)) {
985 $properties = [
'Name',
'DisplayName',
'State',
'StartMode'];
989 $selectClause = implode(
', ', $properties);
990 $query =
"SELECT {$selectClause} FROM Win32_Service";
993 $services = $wmi->ExecQuery($query);
997 foreach ($services as $service) {
999 foreach ($properties as $prop) {
1001 $value = $service->$prop;
1002 $serviceInfo[$prop] = $value ??
'';
1003 }
catch (Exception $e) {
1004 $serviceInfo[$prop] =
'';
1010 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1011 Log::debug(
'listServices: Found ' . count(
$result) .
' services in ' . $duration .
'ms (COM/WMI)');
1015 }
catch (Exception $e) {
1017 Log::error(
'listServices: COM exception: ' . $e->getMessage());
1035 $safeServiceName = str_replace(
"'",
"''", $serviceName);
1036 $query =
"SELECT Name FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1039 $services = $wmi->ExecQuery($query);
1041 foreach ($services as $service) {
1047 }
catch (Exception $e) {
1066 $safeServiceName = str_replace(
"'",
"''", $serviceName);
1067 $query =
"SELECT State FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1070 $services = $wmi->ExecQuery($query);
1072 foreach ($services as $service) {
1073 return $service->State;
1078 }
catch (Exception $e) {
1096 Log::debug(
'getDefaultBrowser: Reading default browser (COM)');
1098 $startTime = microtime(
true);
1103 if ($browserPath ===
null) {
1104 Log::debug(
'getDefaultBrowser: No default browser found');
1108 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1112 if (preg_match(
'/"([^"]+)"/', $browserPath, $matches)) {
1113 $path = $matches[1];
1116 $path = trim(explode(
' ', $browserPath)[0]);
1117 $path = str_replace(
'"',
'', $path);
1120 Log::debug(
'getDefaultBrowser: Found browser in ' . $duration .
'ms (COM)');
1132 Log::debug(
'getInstalledBrowsers: Enumerating installed browsers (Hybrid - No VBS)');
1134 $startTime = microtime(
true);
1149 'Firefox-308046B0AF4A39CB',
1150 'Firefox Developer Edition',
1166 'Epic Privacy Browser',
1172 'Avast Secure Browser',
1173 'AVG Secure Browser',
1178 [
'hive' =>
'HKLM',
'key' =>
'SOFTWARE\\WOW6432Node\\Clients\\StartMenuInternet'],
1179 [
'hive' =>
'HKLM',
'key' =>
'SOFTWARE\\Clients\\StartMenuInternet'],
1180 [
'hive' =>
'HKCU',
'key' =>
'SOFTWARE\\Clients\\StartMenuInternet'],
1184 foreach ($registryPaths as $regPath) {
1185 $hive = $regPath[
'hive'];
1186 $basePath = $regPath[
'key'];
1188 foreach ($knownBrowsers as $browserName) {
1193 $basePath .
'\\' . $browserName .
'\\shell\\open\\command',
1197 if ($commandPath !==
null && !empty($commandPath)) {
1201 if ($exePath && !in_array($exePath, $browsers)) {
1202 Log::debug(
'getInstalledBrowsers: Found ' . $browserName .
': ' . $exePath);
1203 $browsers[] = $exePath;
1206 }
catch (Exception $e) {
1213 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1214 Log::debug(
'getInstalledBrowsers: Found ' . count($browsers) .
' browser(s) in ' . $duration .
'ms (Hybrid - No VBS)');
1229 if (empty($commandPath)) {
1235 if (preg_match(
'/"([^"]+\\.exe)"/i', $commandPath, $matches)) {
1241 if (preg_match(
'/^([^\\s]+\\.exe)/i', trim($commandPath), $matches)) {
1247 if (preg_match(
'/([A-Z]:\\\\[^"]+\\.exe)/i', $commandPath, $matches)) {
1252 $parts = explode(
' ', trim($commandPath));
1253 $path = str_replace(
'"',
'', $parts[0]);
1256 if (stripos($path,
'.exe') !==
false) {
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 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 registryGetValue($hive, $key, $value='')
static getServiceState($serviceName)
static extractBrowserExecutablePath($commandPath)
static serviceExists($serviceName)
static createShortcut($shortcutPath, $targetPath, $workingDir='', $description='', $iconPath='')
static getWmiStdRegProv()
static countFilesFoldersManual($path)
static COM $wmiStdRegProv
static getServiceInfo($serviceName, $properties=[])
static getSpecialFolderPath($folderName)
static registryDeleteKey($hive, $key)