Multi-Tenant-Umschaltung + Pro-Tenant-Vorgaben, RPA deaktiviert
- Multi-Tenant: Settings-Schalter Single/Multi, Profile mit je eigener App-Registrierung, Topbar-Umschalter mit Sofort-Reconnect; verlustfreie Migration (Get-ActiveConnection/-TenantProfile, /api/tenants[/switch]). - Pro-Tenant-Overrides mit globalem Fallback: Abteilungs-Praefixe sowie Required-/Available-Gruppen-Naming (Get-DepartmentPrefixes/Get-GroupNaming tenant-bewusst; New-GroupEndpoint nutzt Resolver). - RPA komplett deaktiviert: Mode-Tab, globaler Settings-Abschnitt und Test-Anzeige entfernt. - Setup-Zwang gelockert: nur Tenant ID + Client ID Pflicht; Abteilungs- Praefixe optional (kein harter Setup-Blocker mehr). - api(): "Failed to fetch" -> klare Meldung "Server nicht erreichbar…". - Hilfe-Footer: "Entwickelt von WendeIT – Marco Wende". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+104
-21
@@ -47,6 +47,9 @@ function Invoke-ApiHandler {
|
||||
|
||||
"POST /api/assignments/apply" { return Invoke-ApplyEndpoint -Body $Body }
|
||||
|
||||
"GET /api/tenants" { return Get-TenantsEndpoint }
|
||||
"POST /api/tenants/switch" { return Switch-TenantEndpoint -Body $Body }
|
||||
|
||||
"GET /api/policies/compliance" { return Get-CompliancePoliciesEndpoint }
|
||||
"GET /api/policies/configuration" { return Get-ConfigurationProfilesEndpoint }
|
||||
"GET /api/policies/settingscatalog" { return Get-SettingsCatalogPoliciesEndpoint }
|
||||
@@ -167,9 +170,11 @@ function Get-SettingsEndpoint {
|
||||
function Sync-ConfigFromSettings {
|
||||
# $script:Config spiegelt die settings.connection-Werte. Wird nach jedem
|
||||
# Save aufgerufen damit Connect-Aufrufe sofort die neuen IDs verwenden.
|
||||
$script:Config.TenantId = $script:Settings.connection.tenantId
|
||||
$script:Config.ClientId = $script:Settings.connection.clientId
|
||||
$script:Config.ClientIdRo = $script:Settings.connection.clientIdRo
|
||||
# Im Multi-Tenant-Modus kommen TenantId/ClientId aus dem aktiven Profil.
|
||||
$active = Get-ActiveConnection -Settings $script:Settings
|
||||
$script:Config.TenantId = $active.tenantId
|
||||
$script:Config.ClientId = $active.clientId
|
||||
$script:Config.ClientIdRo = $active.clientIdRo
|
||||
$script:Config.Scopes = @($script:Settings.connection.scopes)
|
||||
$script:Config.ScopesRo = @($script:Settings.connection.scopesRo)
|
||||
# Policy Export/Import braucht den Configuration-Scope. Immer sicherstellen —
|
||||
@@ -207,17 +212,26 @@ function Save-SettingsEndpoint {
|
||||
# Vergleich altes vs. neues Setting — Cache nur leeren wo wirklich noetig.
|
||||
$old = $script:Settings
|
||||
|
||||
# Aktive Verbindung vergleichen (deckt Single-Felder UND das aktive
|
||||
# Multi-Tenant-Profil ab), plus Moduswechsel Single<->Multi.
|
||||
$activeOld = Get-ActiveConnection -Settings $old
|
||||
$activeNew = Get-ActiveConnection -Settings $merged
|
||||
$multiOld = ($old.connection.PSObject.Properties['multiTenant'] -and [bool]$old.connection.multiTenant)
|
||||
$multiNew = ($merged.connection.PSObject.Properties['multiTenant'] -and [bool]$merged.connection.multiTenant)
|
||||
$connectionChanged = (
|
||||
$merged.connection.tenantId -ne $old.connection.tenantId -or
|
||||
$merged.connection.clientId -ne $old.connection.clientId -or
|
||||
(($merged.connection.scopes -join "|") -ne ($old.connection.scopes -join "|"))
|
||||
$activeNew.tenantId -ne $activeOld.tenantId -or
|
||||
$activeNew.clientId -ne $activeOld.clientId -or
|
||||
$activeNew.clientIdRo -ne $activeOld.clientIdRo -or
|
||||
(($merged.connection.scopes -join "|") -ne ($old.connection.scopes -join "|")) -or
|
||||
($multiNew -ne $multiOld)
|
||||
)
|
||||
# Normalisierte Praefix-Listen vergleichen (deckt sowohl 'prefixes' als auch
|
||||
# den alten Single-String 'prefix' ab; Reihenfolge ignoriert, Case ignoriert).
|
||||
$deptOld = @(Get-DepartmentPrefixes -Settings $old) | Sort-Object -Property { $_.ToLowerInvariant() }
|
||||
$deptNew = @(Get-DepartmentPrefixes -Settings $merged) | Sort-Object -Property { $_.ToLowerInvariant() }
|
||||
$deptChanged = (($deptOld -join '|') -ne ($deptNew -join '|'))
|
||||
$rpaChanged = (($merged.rpa.groupNames -join "|") -ne ($old.rpa.groupNames -join "|"))
|
||||
# Tenant-bewusst: vergleicht die aufgeloesten RPA-Namen (Profil-Override oder global).
|
||||
$rpaChanged = ((@(Get-RpaGroupNames -Settings $merged) -join "|") -ne (@(Get-RpaGroupNames -Settings $old) -join "|"))
|
||||
$userSearchChanged = (
|
||||
(($merged.userSearch.fields -join "|") -ne ($old.userSearch.fields -join "|"))
|
||||
)
|
||||
@@ -573,16 +587,24 @@ function Get-StatusEndpoint {
|
||||
error = $cs.Error
|
||||
}
|
||||
}
|
||||
$activeConn = Get-ActiveConnection -Settings $script:Settings
|
||||
$activeId = ""
|
||||
if ($script:Settings.connection.PSObject.Properties['activeTenantId']) {
|
||||
$activeId = [string]$script:Settings.connection.activeTenantId
|
||||
}
|
||||
return @{
|
||||
connected = $script:State.Connected
|
||||
account = $script:State.Account
|
||||
tenantId = $script:State.TenantId
|
||||
readOnly = [bool]$script:State.ReadOnly
|
||||
groupsLoaded = $script:State.Groups.Count
|
||||
rpaLoaded = $script:State.RpaGroups.Count
|
||||
appsLoaded = $script:State.Apps.Count
|
||||
sessionCount = $script:State.Session.Count
|
||||
connect = $connect
|
||||
connected = $script:State.Connected
|
||||
account = $script:State.Account
|
||||
tenantId = $script:State.TenantId
|
||||
readOnly = [bool]$script:State.ReadOnly
|
||||
groupsLoaded = $script:State.Groups.Count
|
||||
rpaLoaded = $script:State.RpaGroups.Count
|
||||
appsLoaded = $script:State.Apps.Count
|
||||
sessionCount = $script:State.Session.Count
|
||||
connect = $connect
|
||||
multiTenant = (Test-ConnectionMultiTenant)
|
||||
tenantLabel = $activeConn.label
|
||||
activeTenantId = $activeId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1021,6 +1043,66 @@ function Test-Connected {
|
||||
return $null
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Multi-Tenant
|
||||
# ============================================================
|
||||
|
||||
function Test-ConnectionMultiTenant {
|
||||
$c = $script:Settings.connection
|
||||
return ($c -and $c.PSObject.Properties['multiTenant'] -and [bool]$c.multiTenant)
|
||||
}
|
||||
|
||||
function Get-TenantsEndpoint {
|
||||
# Liste der Tenant-Profile fuer den Topbar-Umschalter. Client-IDs werden
|
||||
# nicht mitgeliefert (fuer die Anzeige nicht noetig).
|
||||
$c = $script:Settings.connection
|
||||
$isMulti = Test-ConnectionMultiTenant
|
||||
$items = @()
|
||||
if ($isMulti -and $c.PSObject.Properties['tenants']) {
|
||||
foreach ($t in @($c.tenants | Where-Object { $_ })) {
|
||||
$items += @{
|
||||
id = [string]$t.id
|
||||
label = [string]$t.label
|
||||
tenantId = [string]$t.tenantId
|
||||
hasRo = [bool]($t.clientIdRo -and ([string]$t.clientIdRo).Trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
$activeId = if ($c.PSObject.Properties['activeTenantId']) { [string]$c.activeTenantId } else { "" }
|
||||
if ($isMulti -and $items.Count -gt 0 -and -not (@($items | Where-Object { $_.id -eq $activeId }).Count)) {
|
||||
$activeId = $items[0].id
|
||||
}
|
||||
return @{ multiTenant = $isMulti; activeId = $activeId; items = @($items) }
|
||||
}
|
||||
|
||||
function Switch-TenantEndpoint {
|
||||
param($Body)
|
||||
if (-not (Test-ConnectionMultiTenant)) {
|
||||
return @{ __status = 400; error = "Multi-Tenant-Modus ist nicht aktiv." }
|
||||
}
|
||||
$id = if ($Body) { [string]$Body.id } else { "" }
|
||||
if ([string]::IsNullOrWhiteSpace($id)) { return @{ __status = 400; error = "Tenant-id fehlt." } }
|
||||
|
||||
$c = $script:Settings.connection
|
||||
$tenants = @()
|
||||
if ($c.PSObject.Properties['tenants']) { $tenants = @($c.tenants | Where-Object { $_ }) }
|
||||
$profile = $tenants | Where-Object { [string]$_.id -eq $id } | Select-Object -First 1
|
||||
if (-not $profile) { return @{ __status = 404; error = "Tenant-Profil nicht gefunden." } }
|
||||
|
||||
# Aktives Profil setzen + persistieren, Config spiegeln.
|
||||
$script:Settings.connection.activeTenantId = $id
|
||||
try { Write-Settings -Settings $script:Settings } catch {
|
||||
Write-Host "[TENANT] Speichern des aktiven Profils fehlgeschlagen: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
Sync-ConfigFromSettings
|
||||
|
||||
# Bestehende Verbindung trennen (raeumt alle Tenant-Caches ab), dann sofort
|
||||
# mit dem neuen Tenant neu verbinden.
|
||||
Invoke-DisconnectEndpoint | Out-Null
|
||||
Write-Host "[TENANT] Wechsel zu '$([string]$profile.label)' ($($profile.tenantId))" -ForegroundColor Cyan
|
||||
return Invoke-ConnectEndpoint
|
||||
}
|
||||
|
||||
function Get-AppCategoryNames {
|
||||
# Extrahiert die Kategorie-Namen aus dem rohen Graph-Categories-Feld.
|
||||
# Robust gegen alle Source-Types die MgGraph/Invoke-MgGraphRequest in
|
||||
@@ -1152,7 +1234,8 @@ function Get-RpaGroupsEndpoint {
|
||||
$err = Test-Connected
|
||||
if ($err) { return $err }
|
||||
|
||||
$rpaNames = @($script:Settings.rpa.groupNames | Where-Object { $_ })
|
||||
# Tenant-bewusst: aktives Profil kann eigene RPA-Gruppen definieren, sonst global.
|
||||
$rpaNames = @(Get-RpaGroupNames -Settings $script:Settings)
|
||||
if ($rpaNames.Count -eq 0) {
|
||||
# Settings leer -> ehrlich melden, das Frontend zeigt einen Konfig-Hinweis.
|
||||
return @{ items = @(); count = 0; notConfigured = $true }
|
||||
@@ -1211,10 +1294,10 @@ function New-GroupEndpoint {
|
||||
return @{ __status = 400; error = "Intent muss 'required' oder 'available' sein" }
|
||||
}
|
||||
|
||||
$namingObj = if ($intent -eq "available") { $script:Settings.availableGroupNaming } else { $script:Settings.requiredGroupNaming }
|
||||
$defaultSuffix = if ($intent -eq "available") { "-available" } else { "-required" }
|
||||
$namingPrefix = if ($namingObj -and $namingObj.prefix) { $namingObj.prefix } else { "intune-win-app-" }
|
||||
$namingSuffix = if ($namingObj -and $namingObj.suffix) { $namingObj.suffix } else { $defaultSuffix }
|
||||
# Naming tenant-bewusst aufloesen (aktives Profil kann ueberschreiben).
|
||||
$naming = Get-GroupNaming -Settings $script:Settings -Intent $intent
|
||||
$namingPrefix = $naming.prefix
|
||||
$namingSuffix = $naming.suffix
|
||||
|
||||
$cleaned = if ($customName) { Format-GroupNameSlug -Name $customName } else { Format-GroupNameSlug -Name $appName }
|
||||
$displayName = "$namingPrefix$cleaned$namingSuffix".ToLower()
|
||||
|
||||
Reference in New Issue
Block a user