web app mit hinzugefügt
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Intune App-Zuweisungs-Manager - Web Edition
|
||||
# Startet einen lokalen HTTP-Server und oeffnet das moderne Web-Frontend.
|
||||
#
|
||||
# Aufruf:
|
||||
# .\Start.ps1
|
||||
# .\Start.ps1 -Port 8088 -NoBrowser
|
||||
# .\Start.ps1 -TenantId "..." -ClientId "..."
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int]$Port = 8077,
|
||||
# Optional: ueberschreibt die persistierten Settings nur fuer diesen Lauf.
|
||||
# Wenn nicht gesetzt -> Werte aus %APPDATA%\IntuneAppManager-Web\settings.json
|
||||
# (bzw. den Defaults beim Erststart) werden verwendet.
|
||||
[string]$TenantId = "",
|
||||
[string]$ClientId = "",
|
||||
[switch]$NoBrowser,
|
||||
[switch]$AutoInstall, # ohne Rueckfrage installieren
|
||||
[switch]$SkipModuleCheck # Pre-Flight ueberspringen (z.B. CI)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# ============================================================
|
||||
# Pre-Flight: PowerShell-Module pruefen / installieren
|
||||
# (nur User-Scope, nichts systemweites)
|
||||
# ============================================================
|
||||
|
||||
function Test-RequiredModules {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[hashtable[]]$Required,
|
||||
[switch]$AutoInstall
|
||||
)
|
||||
|
||||
Write-Host "Pruefe PowerShell-Module..." -ForegroundColor Cyan
|
||||
$missing = @()
|
||||
$outdated = @()
|
||||
|
||||
foreach ($req in $Required) {
|
||||
$name = $req.Name
|
||||
$minV = [version]$req.MinVersion
|
||||
$available = @(Get-Module -Name $name -ListAvailable -ErrorAction SilentlyContinue)
|
||||
if ($available.Count -eq 0) {
|
||||
Write-Host " [FEHLT] $name (>= $minV)" -ForegroundColor Yellow
|
||||
$missing += $req
|
||||
} else {
|
||||
$maxV = ($available | Sort-Object Version -Descending | Select-Object -First 1).Version
|
||||
if ($maxV -lt $minV) {
|
||||
Write-Host " [ALT] $name v$maxV (<$minV)" -ForegroundColor Yellow
|
||||
$outdated += @{ Name = $name; Have = $maxV; Need = $minV }
|
||||
} else {
|
||||
Write-Host " [OK] $name v$maxV" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing.Count -eq 0 -and $outdated.Count -eq 0) {
|
||||
return $true
|
||||
}
|
||||
|
||||
# NuGet-Provider sicherstellen, sonst schlaegt Install-Module schweigend fehl
|
||||
$nuget = Get-PackageProvider -Name NuGet -ErrorAction SilentlyContinue
|
||||
if (-not $nuget -or $nuget.Version -lt [version]"2.8.5.201") {
|
||||
Write-Host ""
|
||||
Write-Host "NuGet-PackageProvider fehlt — wird im User-Scope nachgezogen..." -ForegroundColor Yellow
|
||||
try {
|
||||
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Scope CurrentUser -Force -ErrorAction Stop | Out-Null
|
||||
} catch {
|
||||
throw "NuGet-Provider konnte nicht installiert werden: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# PSGallery als trusted markieren, sonst kommt fuer jede Install-Aktion ein Prompt
|
||||
$gallery = Get-PSRepository -Name PSGallery -ErrorAction SilentlyContinue
|
||||
if ($gallery -and $gallery.InstallationPolicy -ne 'Trusted') {
|
||||
try { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop } catch {}
|
||||
}
|
||||
|
||||
if (-not $AutoInstall) {
|
||||
Write-Host ""
|
||||
Write-Host "Folgendes wird im User-Scope (CurrentUser) installiert/aktualisiert:" -ForegroundColor Cyan
|
||||
foreach ($m in $missing) { Write-Host " + $($m.Name) min. $($m.MinVersion)" -ForegroundColor White }
|
||||
foreach ($o in $outdated) { Write-Host " ~ $($o.Name) v$($o.Have) -> min. $($o.Need)" -ForegroundColor White }
|
||||
Write-Host ""
|
||||
$answer = Read-Host "Jetzt automatisch installieren? [J]a / [N]ein"
|
||||
if ($answer -notmatch '^(j|J|y|Y)') {
|
||||
Write-Host ""
|
||||
Write-Host "Abbruch. Du kannst die Module manuell installieren:" -ForegroundColor Yellow
|
||||
foreach ($m in $missing) { Write-Host " Install-Module $($m.Name) -Scope CurrentUser -Force" -ForegroundColor Gray }
|
||||
foreach ($o in $outdated) { Write-Host " Update-Module $($o.Name) -Scope CurrentUser -Force" -ForegroundColor Gray }
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($m in $missing) {
|
||||
Write-Host ""
|
||||
Write-Host "Installiere $($m.Name) (CurrentUser)..." -ForegroundColor Cyan
|
||||
try {
|
||||
Install-Module -Name $m.Name -MinimumVersion $m.MinVersion -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
|
||||
Write-Host " -> OK" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " -> Fehler: $($_.Exception.Message)" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
foreach ($o in $outdated) {
|
||||
Write-Host ""
|
||||
Write-Host "Aktualisiere $($o.Name) auf min. $($o.Need)..." -ForegroundColor Cyan
|
||||
try {
|
||||
Install-Module -Name $o.Name -MinimumVersion $o.Need -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
|
||||
Write-Host " -> OK" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " -> Fehler: $($_.Exception.Message)" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
if (-not $SkipModuleCheck) {
|
||||
$required = @(
|
||||
@{ Name = "Microsoft.Graph.Authentication"; MinVersion = "2.0.0" }
|
||||
)
|
||||
if (-not (Test-RequiredModules -Required $required -AutoInstall:$AutoInstall)) {
|
||||
Write-Host ""
|
||||
Write-Host "Server-Start abgebrochen — benoetigte Module fehlen." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Quellmodule laden (Reihenfolge wichtig)
|
||||
. (Join-Path $root "src/Models.ps1")
|
||||
. (Join-Path $root "src/Graph.ps1")
|
||||
. (Join-Path $root "src/Api.ps1")
|
||||
. (Join-Path $root "src/Router.ps1")
|
||||
. (Join-Path $root "src/Server.ps1")
|
||||
|
||||
# Settings aus Persistenz (Datei) laden, optional per Parameter ueberschreiben
|
||||
$script:Settings = Read-Settings
|
||||
if ($TenantId) { $script:Settings.connection.tenantId = $TenantId }
|
||||
if ($ClientId) { $script:Settings.connection.clientId = $ClientId }
|
||||
|
||||
# Globale Konfiguration. TenantId/ClientId/Scopes spiegeln die Settings —
|
||||
# Connect-Endpoint und Co. lesen weiterhin $script:Config, das nach jedem
|
||||
# Settings-Save aktualisiert wird.
|
||||
$script:Config = @{
|
||||
TenantId = $script:Settings.connection.tenantId
|
||||
ClientId = $script:Settings.connection.clientId
|
||||
Scopes = @($script:Settings.connection.scopes)
|
||||
WebRoot = (Join-Path $root "www")
|
||||
ReportDir = (Join-Path $env:TEMP "IntuneAppManager-Reports")
|
||||
}
|
||||
|
||||
if (-not (Test-Path $script:Config.ReportDir)) {
|
||||
New-Item -ItemType Directory -Path $script:Config.ReportDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Session-State (im PowerShell-Prozess gehalten)
|
||||
$script:State = [pscustomobject]@{
|
||||
Connected = $false
|
||||
Account = $null
|
||||
TenantId = $null
|
||||
Groups = @() # abt-hm Gruppen
|
||||
RpaGroups = @()
|
||||
Apps = @()
|
||||
Users = @{} # cache by id
|
||||
GroupMembers = @{} # cache groupId -> member ids
|
||||
Session = @() # geplante Zuweisungen
|
||||
}
|
||||
|
||||
$script:ToolVersion = "0.1.23"
|
||||
$script:BuildStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "==============================================" -ForegroundColor Cyan
|
||||
Write-Host " Intune App-Zuweisungs-Manager - Web Edition" -ForegroundColor Cyan
|
||||
Write-Host " Version: $script:ToolVersion" -ForegroundColor Green
|
||||
Write-Host " BUILD: $script:BuildStamp" -ForegroundColor DarkGray
|
||||
Write-Host "==============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# ============================================================
|
||||
# Alte Instanzen beenden: laeuft das Tool bereits, haelt der alte Prozess den
|
||||
# Port (HttpListener via http.sys) — ein zweiter Start scheitert dann still und
|
||||
# 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 {
|
||||
$thisScript = $PSCommandPath
|
||||
if ([string]::IsNullOrWhiteSpace($thisScript)) { $thisScript = Join-Path $PSScriptRoot 'Start.ps1' }
|
||||
# Nur echte Server-Instanzen treffen: per -File gestartet UND auf diesen
|
||||
# Start.ps1-Pfad zeigend. So werden -Command-Prozesse (Diagnose etc.), die
|
||||
# den Pfad nur als String enthalten, NICHT versehentlich beendet.
|
||||
$stale = @(Get-CimInstance Win32_Process -Filter "Name='powershell.exe' OR Name='pwsh.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -and ($_.CommandLine -like "*$thisScript*") -and ($_.CommandLine -like "*-File*") })
|
||||
foreach ($p in $stale) {
|
||||
Write-Host " Beende alte Instanz (PID $($p.ProcessId))..." -ForegroundColor Yellow
|
||||
try { Stop-Process -Id $p.ProcessId -Force -ErrorAction Stop }
|
||||
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 {
|
||||
Write-Host " (Konnte alte Instanzen nicht pruefen: $($_.Exception.Message))" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
$url = "http://localhost:$Port/"
|
||||
Write-Host " URL: $url" -ForegroundColor Green
|
||||
Write-Host " WebRoot: $($script:Config.WebRoot)" -ForegroundColor DarkGray
|
||||
Write-Host " Reports: $($script:Config.ReportDir)" -ForegroundColor DarkGray
|
||||
Write-Host " Beenden: Ctrl+C" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
if (-not $NoBrowser) {
|
||||
Start-Process $url
|
||||
}
|
||||
|
||||
Start-WebServer -Port $Port
|
||||
Reference in New Issue
Block a user