From e652891c41c36469a846ed2a180e6ab868d1693b Mon Sep 17 00:00:00 2001 From: Marco Wende Date: Wed, 16 Sep 2026 15:46:56 +0200 Subject: [PATCH] =?UTF-8?q?Cross-Platform:=20Spike=20f=C3=BCr=20Betrieb=20?= =?UTF-8?q?unter=20PowerShell=207=20auf=20macOS/Linux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows-Verhalten bleibt identisch (Pfade/Temp verifiziert). Portable Anpassungen: - Get-AppDataDir/Test-IsWindows (Models.ps1): App-Daten unter macOS in ~/Library/Application Support, Linux in $XDG_CONFIG_HOME/~/.config; auf Windows unveraendert %APPDATA%. Settings- und Policy-Export-Pfad nutzen ihn. - ReportDir ueber [IO.Path]::GetTempPath() statt $env:TEMP (auf macOS/Linux leer). - Start.ps1: Browser plattformneutral oeffnen (open/xdg-open/Start-Process); WMI-Stale-Instanz-Erkennung nur noch auf Windows. - Datei-/Ordner-Dialog: macOS-nativer osascript-Chooser statt System.Windows.Forms; auf nicht unterstuetzten Plattformen klare Fehlermeldung. Auth (Device-Code-Flow) und HttpListener sind bereits plattformneutral. Test auf echtem macOS steht aus; Packaging (.app/.dmg) folgt separat. Co-Authored-By: Claude Opus 4.8 --- Start.ps1 | 23 +++++++++++++++++++---- src/Api.ps1 | 21 +++++++++++++++++++++ src/Models.ps1 | 28 ++++++++++++++++++++++++++-- src/PolicyIO.ps1 | 3 +-- 4 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Start.ps1 b/Start.ps1 index e159a93..aa0d7e9 100644 --- a/Start.ps1 +++ b/Start.ps1 @@ -158,7 +158,8 @@ $script:Config = @{ Scopes = @($script:Settings.connection.scopes) ScopesRo = @($script:Settings.connection.scopesRo) WebRoot = (Join-Path $root "www") - ReportDir = (Join-Path $env:TEMP "IntuneAppManager-Reports") + # Plattformneutrales Temp-Verzeichnis ($env:TEMP ist auf macOS/Linux leer). + ReportDir = (Join-Path ([System.IO.Path]::GetTempPath()) "IntuneAppManager-Reports") } if (-not (Test-Path $script:Config.ReportDir)) { @@ -206,7 +207,12 @@ Write-Host "" # das alte Fenster bedient weiter mit altem Code. Darum alle anderen Start.ps1- # Prozesse DIESES Pfads vorher beenden, damit ein Neustart wirklich neu laedt. # ============================================================ -try { +# Nur auf Windows: die WMI-basierte Instanz-Erkennung (Win32_Process) gibt es +# nur dort. Auf macOS/Linux uebernimmt das der Listener selbst (Port belegt -> +# Fehler beim Start), ein manueller Neustart genuegt. +$isWin = ($env:OS -eq 'Windows_NT') -or ($PSVersionTable.PSVersion.Major -lt 6) -or $IsWindows +if ($isWin) { + try { $thisScript = $PSCommandPath if ([string]::IsNullOrWhiteSpace($thisScript)) { $thisScript = Join-Path $PSScriptRoot 'Start.ps1' } # Nur echte Server-Instanzen treffen: per -File gestartet UND auf diesen @@ -220,8 +226,9 @@ try { catch { Write-Host " konnte PID $($p.ProcessId) nicht beenden: $($_.Exception.Message)" -ForegroundColor DarkYellow } } if ($stale.Count -gt 0) { Start-Sleep -Milliseconds 800 } # kurz warten bis der Port frei ist -} catch { + } catch { Write-Host " (Konnte alte Instanzen nicht pruefen: $($_.Exception.Message))" -ForegroundColor DarkGray + } } $url = "http://localhost:$Port/" @@ -232,7 +239,15 @@ Write-Host " Beenden: Ctrl+C" -ForegroundColor DarkGray Write-Host "" if (-not $NoBrowser) { - Start-Process $url + # Browser plattformneutral oeffnen. + try { + if ($isWin) { Start-Process $url } + elseif ($IsMacOS) { & /usr/bin/open $url } + elseif ($IsLinux) { & xdg-open $url } + else { Start-Process $url } + } catch { + Write-Host " (Browser konnte nicht automatisch geoeffnet werden - bitte $url manuell aufrufen.)" -ForegroundColor DarkGray + } } Start-WebServer -Port $Port diff --git a/src/Api.ps1 b/src/Api.ps1 index 7884c7f..d9ee017 100644 --- a/src/Api.ps1 +++ b/src/Api.ps1 @@ -2840,11 +2840,28 @@ function Update-AppEndpoint { # nie heraus — deshalb der Backend-Dialog. Laeuft in einem STA-Runspace, weil # WinForms-Dialoge zwingend STA brauchen. Owner-Form mit TopMost holt den Dialog # vor das Browser-Fenster. +# macOS-nativer Auswahldialog via osascript. Liefert den POSIX-Pfad oder '' +# (Abbruch). Der Windows-Filter-String wird ignoriert (AppleScript kennt ihn nicht). +function Show-MacChooser { + param([ValidateSet('file','folder')][string]$Kind, [string]$Title = '') + $prompt = ($Title -replace '"', '\"') + $verb = if ($Kind -eq 'folder') { 'choose folder' } else { 'choose file' } + $script = "POSIX path of ($verb with prompt `"$prompt`")" + try { + $out = & /usr/bin/osascript -e $script 2>$null + return ([string]$out).Trim() + } catch { return '' } +} + function Show-OpenFileDialog { param( [string]$Filter = "Alle Dateien (*.*)|*.*", [string]$Title = "Datei auswaehlen" ) + if (-not (Test-IsWindows)) { + if ($IsMacOS) { return (Show-MacChooser -Kind 'file' -Title $Title) } + throw 'Datei-Dialog wird auf dieser Plattform nicht unterstuetzt - Pfad bitte manuell eingeben.' + } # Eigener powershell.exe -STA Prozess: in einem In-Process-Runspace blitzt # der Dialog nur kurz auf (keine echte Windows-Message-Pump, er bleibt nicht # modal). Ein separater STA-Konsolen-Prozess hat einen vollwertigen @@ -2906,6 +2923,10 @@ function Invoke-FilePickerEndpoint { function Show-OpenFolderDialog { # Wie Show-OpenFileDialog, aber FolderBrowserDialog in einem STA-Prozess. param([string]$Title = "Ordner auswaehlen") + if (-not (Test-IsWindows)) { + if ($IsMacOS) { return (Show-MacChooser -Kind 'folder' -Title $Title) } + throw 'Ordner-Dialog wird auf dieser Plattform nicht unterstuetzt - Pfad bitte manuell eingeben.' + } $tEsc = $Title -replace "'", "''" $inner = @" `$ProgressPreference = 'SilentlyContinue' diff --git a/src/Models.ps1 b/src/Models.ps1 index 4bd8687..f695dfc 100644 --- a/src/Models.ps1 +++ b/src/Models.ps1 @@ -26,13 +26,37 @@ function ConvertTo-IsoDate { return $s } +# ============================================================ +# Plattform-Helfer: App-Daten-Verzeichnis (Settings, Exporte). +# Windows: %APPDATA%\IntuneAppManager-Web (unveraendert). +# macOS: ~/Library/Application Support/IntuneAppManager-Web +# Linux: $XDG_CONFIG_HOME bzw. ~/.config/IntuneAppManager-Web +# ============================================================ +function Test-IsWindows { + # PS5.1 kennt $IsWindows nicht -> $env:OS faengt das ab. + return (($env:OS -eq 'Windows_NT') -or ($PSVersionTable.PSVersion.Major -lt 6) -or $IsWindows) +} + +function Get-AppDataDir { + $base = if ($env:APPDATA) { + $env:APPDATA + } elseif ($IsMacOS) { + Join-Path $HOME 'Library/Application Support' + } elseif ($IsLinux) { + if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { Join-Path $HOME '.config' } + } else { + [Environment]::GetFolderPath('ApplicationData') + } + return (Join-Path $base 'IntuneAppManager-Web') +} + # ============================================================ # Settings: editierbare Konfiguration, persistiert als JSON -# unter %APPDATA%\IntuneAppManager-Web\settings.json +# unter \IntuneAppManager-Web\settings.json # ============================================================ function Get-SettingsPath { - $dir = Join-Path $env:APPDATA "IntuneAppManager-Web" + $dir = Get-AppDataDir if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } return Join-Path $dir "settings.json" } diff --git a/src/PolicyIO.ps1 b/src/PolicyIO.ps1 index 09bf75d..613f0fb 100644 --- a/src/PolicyIO.ps1 +++ b/src/PolicyIO.ps1 @@ -14,8 +14,7 @@ # JSON-Inhalt (Frontend-Upload) oder eine Datei aus dem Server-Archiv. function Get-PolicyExportDir { - $dir = Join-Path $env:APPDATA 'IntuneAppManager-Web' - $dir = Join-Path $dir 'policy-exports' + $dir = Join-Path (Get-AppDataDir) 'policy-exports' if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } return $dir }