diff --git a/README.md b/README.md index 716eece..621eaa2 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,30 @@ Dieser Bereich muss von allen Entwicklern gelesen werden, bevor am Projekt gearb --- +## PowerShell Integration (Benutzererstellung) + +Die Weboberfläche nutzt PowerShell-Skripte, um Active Directory Benutzer anzulegen. Damit dies funktioniert, sind folgende Voraussetzungen erforderlich: + +- Der Webserver läuft auf Windows und PHP kann PowerShell ausführen (`powershell` oder `pwsh`). +- Die PowerShell-Module `ActiveDirectory` müssen installiert (RSAT) und verfügbar sein. +- Der Benutzer, unter dem der Webserver läuft, muss ausreichende Rechte besitzen, um `New-ADUser` und `Add-ADGroupMember` auszuführen. +- Im `config/config.php` kann `powershell.dry_run` auf `true` gesetzt werden, um Tests ohne Änderungen durchzuführen. + +Konfigurationsoptionen (in `config/config.php`): +- `powershell.exe`: Name oder Pfad zur PowerShell-Executable (standard `powershell`). +- `powershell.script_dir`: Pfad zu den PowerShell-Skripten (standard `scripts/powershell`). +- `powershell.execution_policy`: Auszuführende ExecutionPolicy (z. B. `Bypass`). +- `powershell.dry_run`: Wenn `true`, werden keine echten AD-Änderungen durchgeführt; das Skript meldet nur, was es tun würde. + +Die grundlegende Funktionalität wurde mit folgenden Komponenten implementiert: +- `public/api/create_user.php`: API-Endpoint zur Erstellung eines einzelnen Benutzers. +- `public/api/create_users_csv.php`: API-Endpoint zur Erstellung mehrerer Benutzer aus CSV. +- `scripts/powershell/create_user.ps1`: PowerShell-Skript zum Erstellen eines einzelnen Benutzers. +- `scripts/powershell/create_users_csv.ps1`: PowerShell-Skript zum Erstellen mehrerer Benutzer aus CSV. + +Bitte testen zuerst mit `powershell.dry_run = true` und prüfen sie die resultierenden Meldungen in UI. + + ## Mitwirken Wer etwas ändern oder erweitern möchte: diff --git a/app/Controllers/UserManagementController.php b/app/Controllers/UserManagementController.php index 41499c0..c84695e 100644 --- a/app/Controllers/UserManagementController.php +++ b/app/Controllers/UserManagementController.php @@ -110,12 +110,36 @@ class UserManagementController { $viewPath = __DIR__ . '/../../public/views/createuser.php'; + // Use session flash messages if available + $error = null; + $success = null; + if (session_status() !== PHP_SESSION_ACTIVE) { + @session_start(); + } + if (isset($_SESSION['flash_error'])) { + $error = $_SESSION['flash_error']; + unset($_SESSION['flash_error']); + } + if (isset($_SESSION['flash_success'])) { + $success = $_SESSION['flash_success']; + unset($_SESSION['flash_success']); + } + $csvDetails = null; + if (isset($_SESSION['csv_details'])) { + $csvDetails = $_SESSION['csv_details']; + unset($_SESSION['csv_details']); + } + + $powershellDryRun = $this->config['powershell']['dry_run'] ?? false; + return [ 'view' => $viewPath, 'data' => [ - 'error' => null, - 'success' => null, + 'error' => $error, + 'success' => $success, 'loginPage' => false, + 'csvDetails' => $csvDetails, + 'powershellDryRun' => $powershellDryRun, ], 'pageTitle' => 'Benutzer erstellen', 'activeMenu' => 'createuser', diff --git a/config/config.php b/config/config.php index a65cdb3..ac439bd 100644 --- a/config/config.php +++ b/config/config.php @@ -60,4 +60,14 @@ return [ // Minimale Stufe: debug, info, warning, error 'min_level' => 'info', ], + 'powershell' => [ + // Executable name: 'powershell' on Windows, 'pwsh' for PowerShell core. + 'exe' => 'powershell', + // Script directory where the PS1 scripts live (relative to config dir) + 'script_dir' => __DIR__ . '/../scripts/powershell', + // Execution policy to pass to the PowerShell invocation + 'execution_policy' => 'Bypass', + // For testing; if true, the scripts will run in dry-run mode (no real AD changes) + 'dry_run' => false, + ], ]; diff --git a/public/api/create_user.php b/public/api/create_user.php new file mode 100644 index 0000000..ce59401 --- /dev/null +++ b/public/api/create_user.php @@ -0,0 +1,109 @@ + $sam, + 'displayname' => $display, + 'mail' => $mail, + 'password' => $pass, + 'ou' => $ou, + 'groups' => $groups, + 'dry_run' => (bool)($config['powershell']['dry_run'] ?? false), +]; + +// Write payload to temp file +$tmpFile = tempnam(sys_get_temp_dir(), 'create_user_') . '.json'; +file_put_contents($tmpFile, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + +// Build PS script path +$scriptDir = $config['powershell']['script_dir'] ?? __DIR__ . '/../../scripts/powershell'; +$script = $scriptDir . DIRECTORY_SEPARATOR . 'create_user.ps1'; + +$exe = $config['powershell']['exe'] ?? 'powershell'; +$executionPolicy = $config['powershell']['execution_policy'] ?? 'Bypass'; + +$cmd = sprintf( + '%s -NoProfile -NonInteractive -ExecutionPolicy %s -File "%s" -InputFile "%s"', + $exe, + $executionPolicy, + $script, + $tmpFile +); + +// Execute and capture output and exit code +$output = []; +$returnVar = null; +if (!file_exists($script)) { + $_SESSION['flash_error'] = 'PowerShell-Skript nicht gefunden: ' . $script; + @unlink($tmpFile); + header('Location: ../index.php?route=createuser'); + exit; +} + +// Try to locate the PowerShell executable +$exePathCheck = shell_exec(sprintf('where %s 2>NUL', escapeshellarg($exe))); +if ($exePathCheck === null) { + // 'where' returns null when command fails; continue anyways, exec will fail if not found +} + +exec($cmd . ' 2>&1', $output, $returnVar); +$json = implode("\n", $output); + +@unlink($tmpFile); + +// Try to parse JSON output +$result = null; +if ($json !== '') { + $decoded = json_decode($json, true); + if (is_array($decoded)) { + $result = $decoded; + } +} + +if ($result === null) { + $_SESSION['flash_error'] = 'Unbekannter Fehler beim Ausführen des PowerShell-Skripts: ' . ($json ?: 'Keine Ausgabe'); + header('Location: ../index.php?route=createuser'); + exit; +} + +if (!empty($result['success'])) { + $_SESSION['flash_success'] = $result['message'] ?? 'Benutzer erfolgreich erstellt.'; +} else { + $_SESSION['flash_error'] = $result['message'] ?? 'Fehler beim Erstellen des Benutzers.'; +} + +header('Location: ../index.php?route=createuser'); +exit; diff --git a/public/api/create_users_csv.php b/public/api/create_users_csv.php new file mode 100644 index 0000000..3b01536 --- /dev/null +++ b/public/api/create_users_csv.php @@ -0,0 +1,107 @@ + $tmpFile, + 'delimiter' => $delimiter, + 'has_header' => (bool)((int)$hasHeader), + 'dry_run' => (bool)($config['powershell']['dry_run'] ?? false), +]; + +// Save options as JSON as the input to the PS script +$metaFile = tempnam(sys_get_temp_dir(), 'create_users_meta_') . '.json'; +file_put_contents($metaFile, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + +$scriptDir = $config['powershell']['script_dir'] ?? __DIR__ . '/../../scripts/powershell'; +$script = $scriptDir . DIRECTORY_SEPARATOR . 'create_users_csv.ps1'; +$exe = $config['powershell']['exe'] ?? 'powershell'; +$executionPolicy = $config['powershell']['execution_policy'] ?? 'Bypass'; + +$cmd = sprintf( + '%s -NoProfile -NonInteractive -ExecutionPolicy %s -File "%s" -InputFile "%s"', + $exe, + $executionPolicy, + $script, + $metaFile +); + +$output = []; +$returnVar = null; +if (!file_exists($script)) { + $_SESSION['flash_error'] = 'PowerShell-Skript nicht gefunden: ' . $script; + @unlink($tmpFile); + @unlink($metaFile); + header('Location: ../index.php?route=createuser'); + exit; +} + +exec($cmd . ' 2>&1', $output, $returnVar); +$json = implode("\n", $output); + +@unlink($tmpFile); +@unlink($metaFile); + +$result = null; +if ($json !== '') { + $decoded = json_decode($json, true); + if (is_array($decoded)) { + $result = $decoded; + } +} + +if ($result === null) { + $_SESSION['flash_error'] = 'Unbekannter Fehler beim Ausführen des PowerShell-Skripts: ' . ($json ?: 'Keine Ausgabe'); + header('Location: ../index.php?route=createuser'); + exit; +} + +if (!empty($result['success'])) { + $_SESSION['flash_success'] = $result['message'] ?? 'CSV verarbeitet.'; + if (!empty($result['details'])) { + $_SESSION['csv_details'] = $result['details']; + } +} else { + $_SESSION['flash_error'] = $result['message'] ?? 'Fehler beim Verarbeiten der CSV.'; +} + +header('Location: ../index.php?route=createuser'); +exit; diff --git a/public/views/createuser.php b/public/views/createuser.php index 53ab95d..9e0ca82 100644 --- a/public/views/createuser.php +++ b/public/views/createuser.php @@ -38,6 +38,12 @@ declare(strict_types=1); + + + +

Hier können Sie einzelne Active-Directory-Benutzer anlegen oder eine CSV-Datei hochladen, um mehrere Benutzer gleichzeitig zu erstellen. Sie können die CSV in der Vorschau bearbeiten bevor Sie die Erstellung auslösen.

@@ -132,6 +138,40 @@ declare(strict_types=1);
+ +
+
+
+
+
CSV Verarbeitungsergebnisse
+
+
+
+ + + + + + + + + + + + + + + + + +
AnmeldenameStatusHinweis
+
+
+
+
+
+ +
diff --git a/scripts/powershell/create_user.ps1 b/scripts/powershell/create_user.ps1 new file mode 100644 index 0000000..ee2216c --- /dev/null +++ b/scripts/powershell/create_user.ps1 @@ -0,0 +1,86 @@ +param( + [Parameter(Mandatory=$true)] + [string]$InputFile +) + +# Read input JSON +try { + $json = Get-Content -Raw -Path $InputFile -ErrorAction Stop + $payload = $json | ConvertFrom-Json +} catch { + $err = $_.Exception.Message + Write-Output (@{ success = $false; message = "Failed to read/parse input JSON: $err" } | ConvertTo-Json -Compress) + exit 1 +} + +# Default result +$result = @{ success = $false; message = "Unspecified error" } + +# Validate +if (-not $payload.samaccountname -or -not $payload.password) { + $result.message = "Required fields: samaccountname and password" + Write-Output ($result | ConvertTo-Json -Compress) + exit 1 +} + +# Convert to strings +$sam = [string]$payload.samaccountname +$display = [string]($payload.displayname) +$mail = [string]($payload.mail) +$pass = [string]$payload.password +$ou = [string]($payload.ou) +$groups = [string]($payload.groups) +$dryRun = [bool]($payload.dry_run -as [bool]) + +# Ensure ActiveDirectory module available +try { + Import-Module ActiveDirectory -ErrorAction Stop +} catch { + $result.message = "ActiveDirectory PowerShell module not available: $($_.Exception.Message)" + Write-Output ($result | ConvertTo-Json -Compress) + exit 1 +} + +# Build New-ADUser parameters +$props = @{ + Name = if ($display -and $display -ne '') { $display } else { $sam } + SamAccountName = $sam + Enabled = $true +} + +if ($mail -and $mail -ne '') { $props['EmailAddress'] = $mail } +if ($ou -and $ou -ne '') { $props['Path'] = $ou } + +# Build secure password +$securePass = ConvertTo-SecureString $pass -AsPlainText -Force +$props['AccountPassword'] = $securePass + +# Execute +if ($dryRun) { + $result.success = $true + $result.message = "DRY RUN: would create user $sam" + Write-Output ($result | ConvertTo-Json -Compress) + exit 0 +} + +try { + # Create the AD user + New-ADUser @props -ErrorAction Stop + + # Add to groups, if provided + if ($groups -and $groups -ne '') { + $groupList = $groups -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + foreach ($g in $groupList) { + Add-ADGroupMember -Identity $g -Members $sam -ErrorAction Stop + } + } + + $result.success = $true + $result.message = "User $sam created successfully" + Write-Output ($result | ConvertTo-Json -Compress) + exit 0 +} catch { + $result.message = "Error creating user $sam: $($_.Exception.Message)" + Write-Output ($result | ConvertTo-Json -Compress) + exit 1 +} diff --git a/scripts/powershell/create_users_csv.ps1 b/scripts/powershell/create_users_csv.ps1 new file mode 100644 index 0000000..66912c7 --- /dev/null +++ b/scripts/powershell/create_users_csv.ps1 @@ -0,0 +1,97 @@ +param( + [Parameter(Mandatory=$true)] + [string]$InputFile +) + +# Read meta JSON +try { + $json = Get-Content -Raw -Path $InputFile -ErrorAction Stop + $meta = $json | ConvertFrom-Json +} catch { + Write-Output (@{ success = $false; message = "Failed to read/parse meta JSON: $($_.Exception.Message)" } | ConvertTo-Json -Compress) + exit 1 +} + +$csvFile = [string]$meta.input_file +$delimiter = [string]($meta.delimiter ?? ',') +$hasHeader = [bool]($meta.has_header -as [bool]) +$dryRun = [bool]($meta.dry_run -as [bool]) + +if (-not (Test-Path -Path $csvFile)) { + Write-Output (@{ success = $false; message = "CSV file not found: $csvFile" } | ConvertTo-Json -Compress) + exit 1 +} + +# Ensure ActiveDirectory module is available +try { + Import-Module ActiveDirectory -ErrorAction Stop +} catch { + Write-Output (@{ success = $false; message = "ActiveDirectory PowerShell module not available: $($_.Exception.Message)" } | ConvertTo-Json -Compress) + exit 1 +} + +# Read CSV +try { + if ($hasHeader) { + $items = Import-Csv -Path $csvFile -Delimiter $delimiter -ErrorAction Stop + } else { + # Use default headers + $headers = 'samaccountname','displayname','mail','password','ou','groups' + $items = Import-Csv -Path $csvFile -Delimiter $delimiter -Header $headers -ErrorAction Stop + } +} catch { + Write-Output (@{ success = $false; message = "Failed to parse CSV: $($_.Exception.Message)" } | ConvertTo-Json -Compress) + exit 1 +} + +$results = @() +$successCount = 0 +$failCount = 0 + +foreach ($row in $items) { + $sam = $row.samaccountname + $display = $row.displayname + $mail = $row.mail + $pass = $row.password + $ou = $row.ou + $groups = $row.groups + + if ([string]::IsNullOrWhiteSpace($sam) -or [string]::IsNullOrWhiteSpace($pass)) { + $results += @{ sam = $sam; success = $false; message = 'Missing samaccountname or password' } + $failCount++ + continue + } + + if ($dryRun) { + $results += @{ sam = $sam; success = $true; message = 'DRY RUN: would create' } + $successCount++ + continue + } + + try { + $props = @{ Name = if ($display -and $display -ne '') { $display } else { $sam }; SamAccountName = $sam; Enabled = $true } + if ($mail -and $mail -ne '') { $props['EmailAddress'] = $mail } + if ($ou -and $ou -ne '') { $props['Path'] = $ou } + $securePass = ConvertTo-SecureString $pass -AsPlainText -Force + $props['AccountPassword'] = $securePass + + New-ADUser @props -ErrorAction Stop + + if ($groups -and $groups -ne '') { + $groupList = $groups -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' } + foreach ($g in $groupList) { + Add-ADGroupMember -Identity $g -Members $sam -ErrorAction Stop + } + } + + $results += @{ sam = $sam; success = $true; message = 'Created' } + $successCount++ + } catch { + $results += @{ sam = $sam; success = $false; message = $_.Exception.Message } + $failCount++ + } +} + +$output = @{ success = $failCount -eq 0; message = "Created $successCount users, $failCount failures"; details = $results } +Write-Output ($output | ConvertTo-Json -Compress) +exit 0