41 if (self::$wmiCimv2 ===
null) {
42 self::$wmiCimv2 =
new COM(
"winmgmts://./root/cimv2");
45 return self::$wmiCimv2;
55 if (self::$wmiStdRegProv ===
null) {
56 self::$wmiStdRegProv =
new COM(
"winmgmts://./root/default:StdRegProv");
59 return self::$wmiStdRegProv;
69 if (self::$wscriptShell ===
null) {
70 self::$wscriptShell =
new COM(
"WScript.Shell");
73 return self::$wscriptShell;
84 self::$wmiCimv2 =
null;
85 self::$wmiStdRegProv =
null;
86 self::$wscriptShell =
null;
99 Log::debug(
'getProcessList: Listing processes (COM/WMI)');
101 $startTime = microtime(
true);
108 if (empty($properties)) {
109 $properties = [
'Name',
'ProcessID',
'ExecutablePath'];
112 $selectClause = implode(
', ', $properties);
113 $query =
"SELECT {$selectClause} FROM Win32_Process";
116 $processes = $wmi->ExecQuery($query);
120 foreach ($processes as
$proc) {
122 foreach ($properties as $prop) {
125 $value =
$proc->$prop;
126 $process[$prop] = $value ??
'';
127 }
catch (Exception $e) {
128 $process[$prop] =
'';
134 $duration = round((microtime(
true) - $startTime) * 1000, 2);
135 Log::debug(
'getProcessList: Found ' . count(
$result) .
' processes in ' . $duration .
'ms (COM/WMI)');
138 }
catch (Exception $e) {
140 Log::error(
'getProcessList: COM exception: ' . $e->getMessage());
157 if (!is_numeric($pid) || $pid <= 0) {
158 Log::error(
'killProcess: Invalid PID: ' . $pid);
163 Log::debug(
'killProcess: Killing process PID ' . $pid .
' (COM/WMI)');
165 $startTime = microtime(
true);
172 $query =
"SELECT * FROM Win32_Process WHERE ProcessID = {$pid}";
173 $processes = $wmi->ExecQuery($query);
177 $terminateResult =
null;
178 foreach ($processes as
$proc) {
179 $terminateResult =
$proc->Terminate();
184 $duration = round((microtime(
true) - $startTime) * 1000, 2);
187 Log::debug(
'killProcess: Process ' . $pid .
' not found');
193 if ($terminateResult !== 0) {
194 Log::error(
'killProcess: Terminate() failed with status code: ' . $terminateResult);
201 if (self::processExists($pid)) {
202 Log::error(
'killProcess: Process ' . $pid .
' still exists after termination attempt');
207 Log::debug(
'killProcess: Successfully killed process ' . $pid .
' in ' . $duration .
'ms (COM/WMI)');
210 }
catch (Exception $e) {
212 Log::error(
'killProcess: COM exception: ' . $e->getMessage());
227 if (!is_numeric($pid) || $pid <= 0) {
233 $query =
"SELECT ProcessID FROM Win32_Process WHERE ProcessID = {$pid}";
234 $processes = $wmi->ExecQuery($query);
236 foreach ($processes as
$proc) {
241 }
catch (Exception $e) {
258 if (!is_numeric($pid) || $pid <= 0) {
262 if (empty($properties)) {
263 $properties = [
'Name',
'ProcessID',
'ExecutablePath',
'CommandLine'];
268 $selectClause = implode(
', ', $properties);
269 $query =
"SELECT {$selectClause} FROM Win32_Process WHERE ProcessID = {$pid}";
270 $processes = $wmi->ExecQuery($query);
272 foreach ($processes as
$proc) {
274 foreach ($properties as $prop) {
276 $value =
$proc->$prop;
278 }
catch (Exception $e) {
287 }
catch (Exception $e) {
289 Log::error(
'getProcessInfo: COM exception: ' . $e->getMessage());
309 if (empty($properties)) {
310 $properties = [
'Name',
'ProcessID',
'ExecutablePath'];
315 $selectClause = implode(
', ', $properties);
319 $safeName = str_replace(
"'",
"''", $name);
321 $query =
"SELECT {$selectClause} FROM Win32_Process WHERE Name = '{$safeName}'";
322 $processes = $wmi->ExecQuery($query);
325 foreach ($processes as
$proc) {
327 foreach ($properties as $prop) {
329 $value =
$proc->$prop;
330 $process[$prop] = $value ??
'';
331 }
catch (Exception $e) {
332 $process[$prop] =
'';
339 }
catch (Exception $e) {
341 Log::error(
'findProcessesByName: COM exception: ' . $e->getMessage());
375 'HKEY_LOCAL_MACHINE' =>
'HKLM',
376 'HKEY_CURRENT_USER' =>
'HKCU',
377 'HKEY_CLASSES_ROOT' =>
'HKCR',
378 'HKEY_USERS' =>
'HKU',
381 return isset($hiveMap[$hive]) ? $hiveMap[$hive] : $hive;
400 $regPath = $hive .
'\\' . $key;
402 Log::debug(
'registryExists: Checking ' . $regPath . ($value !==
null ?
'\\' . $value :
' (key)') .
' (COM)');
405 if ($value ===
null) {
414 'HKCR' => 0x80000000,
415 'HKCU' => 0x80000001,
416 'HKLM' => 0x80000002,
420 $hConst = $hiveConstMap[$hive] ?? 0x80000002;
424 $pathParts = explode(
'\\', $key);
426 if (count($pathParts) === 1) {
429 $keyName = $pathParts[0];
432 $keyName = array_pop($pathParts);
433 $parentKey = implode(
'\\', $pathParts);
437 $rc = $wmi->EnumKey($hConst, $parentKey, $subKeys);
439 if ($rc !== 0 || !is_array($subKeys)) {
440 Log::debug(
'registryExists: Key not found (EnumKey failed)');
447 foreach ($subKeys as $subKey) {
448 if (strcasecmp($subKey, $keyName) === 0) {
463 }
catch (Exception $e) {
464 self::$wmiStdRegProv =
null;
465 Log::error(
'registryExists: StdRegProv exception during key check: ' . $e->getMessage());
474 $valuePath = $regPath .
'\\' . $value;
475 $shell->RegRead($valuePath);
479 }
catch (Exception $e) {
480 Log::debug(
'registryExists: Value not found');
485 }
catch (Exception $e) {
487 Log::error(
'registryExists: COM exception: ' . $e->getMessage());
506 $regPath = $hive .
'\\' . $key;
509 $regPath .=
'\\' . $value;
515 Log::debug(
'registryGetValue: Reading ' . $regPath .
' (COM)');
517 $startTime = microtime(
true);
521 $result = $shell->RegRead($regPath);
523 $duration = round((microtime(
true) - $startTime) * 1000, 2);
531 Log::debug(
'registryGetValue: Found value in ' . $duration .
'ms (COM)');
534 }
catch (Exception $e) {
535 Log::debug(
'registryGetValue: Value not found');
556 $regPath = $hive .
'\\' . $key .
'\\' . $value;
559 $validTypes = [
'REG_SZ',
'REG_EXPAND_SZ',
'REG_DWORD',
'REG_BINARY'];
560 if (!in_array($type, $validTypes)) {
561 Log::error(
'registrySetValue: Invalid type: ' . $type);
566 Log::debug(
'registrySetValue: Writing ' . $regPath .
' (' . $type .
') (COM)');
568 $startTime = microtime(
true);
574 if ($type ===
'REG_DWORD') {
579 $shell->RegWrite($regPath, $data, $type);
581 $duration = round((microtime(
true) - $startTime) * 1000, 2);
582 Log::debug(
'registrySetValue: Successfully wrote value in ' . $duration .
'ms (COM)');
585 }
catch (Exception $e) {
587 Log::error(
'registrySetValue: COM exception: ' . $e->getMessage());
606 $regPath = $hive .
'\\' . $key .
'\\' . $value;
608 Log::debug(
'registryDeleteValue: Deleting ' . $regPath .
' (COM)');
610 $startTime = microtime(
true);
616 $shell->RegDelete($regPath);
618 $duration = round((microtime(
true) - $startTime) * 1000, 2);
619 Log::debug(
'registryDeleteValue: Successfully deleted value in ' . $duration .
'ms (COM)');
622 }
catch (Exception $e) {
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)');
633 Log::error(
'registryDeleteValue: COM exception: ' . $e->getMessage());
651 $regPath = $hive .
'\\' . $key .
'\\';
653 Log::debug(
'registryDeleteKey: Deleting ' . $regPath .
' (COM)');
659 $shell->RegDelete($regPath);
661 Log::debug(
'registryDeleteKey: Successfully deleted key (COM)');
664 }
catch (Exception $e) {
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)');
675 Log::error(
'registryDeleteKey: COM exception: ' . $e->getMessage());
695 Log::debug(
'getSpecialFolderPath: Getting ' . $folderName .
' path (COM)');
697 $startTime = microtime(
true);
703 $path = $shell->SpecialFolders($folderName);
705 $duration = round((microtime(
true) - $startTime) * 1000, 2);
707 if ($path && !empty($path)) {
709 $path = str_replace(
'\\',
'/', $path);
710 Log::debug(
'getSpecialFolderPath: Found ' . $folderName .
' in ' . $duration .
'ms (COM)');
714 Log::debug(
'getSpecialFolderPath: ' . $folderName .
' not found');
718 }
catch (Exception $e) {
720 Log::error(
'getSpecialFolderPath: COM exception: ' . $e->getMessage());
738 public static function createShortcut($shortcutPath, $targetPath, $workingDir =
'', $description =
'', $iconPath =
'')
740 Log::debug(
'createShortcut: Creating shortcut at ' . $shortcutPath .
' (COM)');
742 $startTime = microtime(
true);
748 $shortcut = $shell->CreateShortcut($shortcutPath);
751 $shortcut->TargetPath = $targetPath;
753 if (!empty($workingDir)) {
754 $shortcut->WorkingDirectory = $workingDir;
757 if (!empty($description)) {
758 $shortcut->Description = $description;
761 if (!empty($iconPath)) {
762 $shortcut->IconLocation = $iconPath;
768 $duration = round((microtime(
true) - $startTime) * 1000, 2);
769 Log::debug(
'createShortcut: Successfully created shortcut in ' . $duration .
'ms (COM)');
772 }
catch (Exception $e) {
774 Log::error(
'createShortcut: COM exception: ' . $e->getMessage());
794 Log::debug(
'countFilesFolders: Counting in ' . $path .
' (Native PHP)');
796 $startTime = microtime(
true);
799 if (!is_dir($path)) {
800 Log::error(
'countFilesFolders: Path is not a directory: ' . $path);
809 $iterator =
new RecursiveIteratorIterator(
810 new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
811 RecursiveIteratorIterator::SELF_FIRST
814 foreach ($iterator as $item) {
817 }
catch (Exception $e) {
819 Log::debug(
'countFilesFolders: RecursiveIterator failed, using manual recursion');
823 $duration = round((microtime(
true) - $startTime) * 1000, 2);
824 Log::debug(
'countFilesFolders: Counted ' . $count .
' items in ' . $duration .
'ms (Native PHP)');
827 }
catch (Exception $e) {
828 Log::error(
'countFilesFolders: Exception: ' . $e->getMessage());
847 $items = @scandir($path);
849 if ($items ===
false) {
853 foreach ($items as $item) {
854 if ($item ===
'.' || $item ===
'..') {
858 $fullPath = $path . DIRECTORY_SEPARATOR . $item;
861 if (is_dir($fullPath)) {
866 }
catch (Exception $e) {
883 Log::debug(
'countFilesFoldersCOM: Counting in ' . $path .
' (COM/FSO)');
885 $startTime = microtime(
true);
888 $fso =
new COM(
"Scripting.FileSystemObject");
890 if (!$fso->FolderExists($path)) {
891 Log::error(
'countFilesFoldersCOM: Path does not exist: ' . $path);
898 $duration = round((microtime(
true) - $startTime) * 1000, 2);
899 Log::debug(
'countFilesFoldersCOM: Counted ' . $count .
' items in ' . $duration .
'ms (COM/FSO)');
902 }
catch (Exception $e) {
903 Log::error(
'countFilesFoldersCOM: COM exception: ' . $e->getMessage());
921 $folder = $fso->GetFolder($path);
924 $count = $folder->Files->Count + $folder->SubFolders->Count;
927 foreach ($folder->SubFolders as $subFolder) {
932 }
catch (Exception $e) {
953 Log::debug(
'getServiceInfo: Getting info for service ' . $serviceName .
' (COM/WMI)');
955 $startTime = microtime(
true);
962 if (empty($properties)) {
978 $selectClause = implode(
', ', $properties);
979 $safeServiceName = str_replace(
"'",
"''", $serviceName);
980 $query =
"SELECT {$selectClause} FROM Win32_Service WHERE Name = '{$safeServiceName}'";
983 $services = $wmi->ExecQuery($query);
986 foreach ($services as $service) {
988 foreach ($properties as $prop) {
990 $value = $service->$prop;
992 }
catch (Exception $e) {
997 $duration = round((microtime(
true) - $startTime) * 1000, 2);
998 Log::debug(
'getServiceInfo: Found service in ' . $duration .
'ms (COM/WMI)');
1004 Log::debug(
'getServiceInfo: Service not found');
1007 }
catch (Exception $e) {
1009 Log::error(
'getServiceInfo: COM exception: ' . $e->getMessage());
1025 Log::debug(
'listServices: Listing all services (COM/WMI)');
1027 $startTime = microtime(
true);
1034 if (empty($properties)) {
1035 $properties = [
'Name',
'DisplayName',
'State',
'StartMode'];
1039 $selectClause = implode(
', ', $properties);
1040 $query =
"SELECT {$selectClause} FROM Win32_Service";
1043 $services = $wmi->ExecQuery($query);
1047 foreach ($services as $service) {
1049 foreach ($properties as $prop) {
1051 $value = $service->$prop;
1052 $serviceInfo[$prop] = $value ??
'';
1053 }
catch (Exception $e) {
1054 $serviceInfo[$prop] =
'';
1060 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1061 Log::debug(
'listServices: Found ' . count(
$result) .
' services in ' . $duration .
'ms (COM/WMI)');
1064 }
catch (Exception $e) {
1066 Log::error(
'listServices: COM exception: ' . $e->getMessage());
1086 $safeServiceName = str_replace(
"'",
"''", $serviceName);
1087 $query =
"SELECT Name FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1090 $services = $wmi->ExecQuery($query);
1092 foreach ($services as $service) {
1097 }
catch (Exception $e) {
1118 $safeServiceName = str_replace(
"'",
"''", $serviceName);
1119 $query =
"SELECT State FROM Win32_Service WHERE Name = '{$safeServiceName}'";
1122 $services = $wmi->ExecQuery($query);
1124 foreach ($services as $service) {
1125 return $service->State;
1129 }
catch (Exception $e) {
1148 Log::debug(
'getDefaultBrowser: Reading default browser (COM)');
1150 $startTime = microtime(
true);
1155 if ($browserPath ===
null) {
1156 Log::debug(
'getDefaultBrowser: No default browser found');
1161 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1165 if (preg_match(
'/"([^"]+)"/', $browserPath, $matches)) {
1166 $path = $matches[1];
1169 $path = trim(explode(
' ', $browserPath)[0]);
1170 $path = str_replace(
'"',
'', $path);
1173 Log::debug(
'getDefaultBrowser: Found browser in ' . $duration .
'ms (COM)');
1186 Log::debug(
'getInstalledBrowsers: Enumerating installed browsers (Hybrid - No VBS)');
1188 $startTime = microtime(
true);
1203 'Firefox-308046B0AF4A39CB',
1204 'Firefox Developer Edition',
1220 'Epic Privacy Browser',
1226 'Avast Secure Browser',
1227 'AVG Secure Browser',
1232 [
'hive' =>
'HKLM',
'key' =>
'SOFTWARE\\WOW6432Node\\Clients\\StartMenuInternet'],
1233 [
'hive' =>
'HKLM',
'key' =>
'SOFTWARE\\Clients\\StartMenuInternet'],
1234 [
'hive' =>
'HKCU',
'key' =>
'SOFTWARE\\Clients\\StartMenuInternet'],
1238 foreach ($registryPaths as $regPath) {
1239 $hive = $regPath[
'hive'];
1240 $basePath = $regPath[
'key'];
1242 foreach ($knownBrowsers as $browserName) {
1247 $basePath .
'\\' . $browserName .
'\\shell\\open\\command',
1251 if ($commandPath !==
null && !empty($commandPath)) {
1255 if ($exePath && !in_array($exePath, $browsers)) {
1256 Log::debug(
'getInstalledBrowsers: Found ' . $browserName .
': ' . $exePath);
1257 $browsers[] = $exePath;
1260 }
catch (Exception $e) {
1267 $duration = round((microtime(
true) - $startTime) * 1000, 2);
1268 Log::debug(
'getInstalledBrowsers: Found ' . count($browsers) .
' browser(s) in ' . $duration .
'ms (Hybrid - No VBS)');
1284 if (empty($commandPath)) {
1290 if (preg_match(
'/"([^"]+\\.exe)"/i', $commandPath, $matches)) {
1296 if (preg_match(
'/^([^\\s]+\\.exe)/i', trim($commandPath), $matches)) {
1302 if (preg_match(
'/([A-Z]:\\\\[^"]+\\.exe)/i', $commandPath, $matches)) {
1307 $parts = explode(
' ', trim($commandPath));
1308 $path = str_replace(
'"',
'', $parts[0]);
1311 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)