psExe = $psExe ?? 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; // Alternative PowerShell 7: // $this->psExe = $psExe ?? 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'; } /** * @return array{exitCode:int, stdout:string, stderr:string} */ public function runScript(string $scriptPath, array $namedArgs, int $timeoutSeconds = 30): array { $scriptPath = realpath($scriptPath) ?: $scriptPath; if (!is_file($scriptPath)) { throw new RuntimeException("PowerShell script not found: {$scriptPath}"); } $args = []; foreach ($namedArgs as $name => $value) { if (!preg_match('/^[A-Za-z][A-Za-z0-9]*$/', (string)$name)) { throw new InvalidArgumentException("Invalid argument name: {$name}"); } $args[] = '-' . $name; $args[] = (string)$value; // wird als eigenes Argument übergeben (kein Command-String-Basteln) } $cmd = array_merge([ $this->psExe, '-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $scriptPath, ], $args); $descriptors = [ 0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"], ]; $proc = proc_open($cmd, $descriptors, $pipes, null, null, [ 'bypass_shell' => true, 'suppress_errors' => true, ]); if (!is_resource($proc)) { throw new RuntimeException("Failed to start PowerShell process."); } fclose($pipes[0]); stream_set_blocking($pipes[1], false); stream_set_blocking($pipes[2], false); $stdout = ''; $stderr = ''; $start = time(); while (true) { $stdout .= stream_get_contents($pipes[1]); $stderr .= stream_get_contents($pipes[2]); $status = proc_get_status($proc); if (!$status['running']) { break; } if ((time() - $start) > $timeoutSeconds) { proc_terminate($proc, 9); $stderr .= "\n[PHP] Timeout after {$timeoutSeconds}s"; break; } usleep(50_000); } fclose($pipes[1]); fclose($pipes[2]); $exitCode = proc_close($proc); return [ 'exitCode' => (int)$exitCode, 'stdout' => trim($stdout), 'stderr' => trim($stderr), ]; } }