# Intune App-Zuweisungs-Manager - Erweiterte Version (v5.2.0 - Gruppenabfrage hinzugefügt) # Mit App-Suche, Filter, Required-Gruppen erstellen, Session-Übersicht, RPA-Tab, Gruppenabfrage und target-spezifischen Zuweisungen Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms # Funktion zum Laden des Microsoft.Graph.Authentication Moduls (wird beim Connect aufgerufen) # NUR dieses Modul wird benötigt - alle anderen Aufrufe gehen über Invoke-MgGraphRequest function Initialize-GraphModules { if (Get-Module Microsoft.Graph.Authentication) { return $true } try { Import-Module Microsoft.Graph.Authentication -ErrorAction Stop return $true } catch { [System.Windows.MessageBox]::Show( "Fehler beim Laden von Microsoft.Graph.Authentication:`n$($_.Exception.Message)`n`nBitte führen Sie aus:`nInstall-Module Microsoft.Graph.Authentication -Force", "Modul-Fehler", "OK", "Error") return $false } } # ===== GRAPH API HILFSFUNKTIONEN ===== # Diese ersetzen die Microsoft.Graph.* Cmdlets um Versionskonflikte zu vermeiden function Get-GraphGroup { param( [string]$GroupId, [string]$Filter, [string[]]$Property, [switch]$All ) try { $select = if ($Property) { "&`$select=" + ($Property -join ",").ToLower() } else { "" } if ($GroupId) { $selectPart = if ($Property) { "?`$select=" + ($Property -join ",").ToLower() } else { "" } $uri = "https://graph.microsoft.com/v1.0/groups/$GroupId$selectPart" return Invoke-MgGraphRequest -Uri $uri -Method GET } else { $uri = "https://graph.microsoft.com/v1.0/groups" if ($Filter) { $uri += "?`$filter=$Filter$select" } elseif ($select) { $uri += "?$($select.TrimStart('&'))" } $results = @() do { $response = Invoke-MgGraphRequest -Uri $uri -Method GET if ($response.value) { $results += $response.value } $uri = $response.'@odata.nextLink' } while ($All -and $uri) return $results } } catch { Write-Warning "Get-GraphGroup Fehler: $_" return $null } } function Get-GraphGroupMember { param([string]$GroupId, [switch]$All) try { $uri = "https://graph.microsoft.com/v1.0/groups/$GroupId/members" $results = @() do { $response = Invoke-MgGraphRequest -Uri $uri -Method GET if ($response.value) { $results += $response.value } $uri = $response.'@odata.nextLink' } while ($All -and $uri) return $results } catch { Write-Warning "Get-GraphGroupMember Fehler: $_" return @() } } function Find-GraphGroupsByName { # Sucht beliebige Gruppen im Tenant per Teilstring (nicht nur Präfix wie Get-GraphGroup -Filter startswith) param([string]$SearchTerm) try { $encoded = $SearchTerm.Replace("'", "''") $uri = "https://graph.microsoft.com/v1.0/groups?`$filter=contains(displayName,'$encoded')&`$count=true&`$top=999&`$select=id,displayName" $results = @() do { $response = Invoke-MgGraphRequest -Uri $uri -Method GET -Headers @{ ConsistencyLevel = "eventual" } if ($response.value) { $results += $response.value } $uri = $response.'@odata.nextLink' } while ($uri) return $results } catch { Write-Warning "Find-GraphGroupsByName Fehler: $_" return @() } } function Get-ResolvedGroupMembers { # Liest die Mitglieder einer Gruppe aus (User, Geräte, ggf. verschachtelte Gruppen) param( [string]$GroupId, [string]$SourceGroupName, [switch]$ResolveNested, [System.Collections.Generic.HashSet[string]]$Visited ) if (-not $Visited) { $Visited = [System.Collections.Generic.HashSet[string]]::new() } if ($Visited.Contains($GroupId)) { return @() } [void]$Visited.Add($GroupId) $members = Get-GraphGroupMember -GroupId $GroupId -All $results = [System.Collections.Generic.List[object]]::new() foreach ($m in $members) { $odataType = $m.'@odata.type' switch ($odataType) { '#microsoft.graph.user' { $results.Add([PSCustomObject]@{ DisplayName = $m.displayName UserPrincipalName = $m.userPrincipalName Id = $m.id ObjectType = "Benutzer" SourceGroup = $SourceGroupName }) } '#microsoft.graph.device' { $results.Add([PSCustomObject]@{ DisplayName = $m.displayName UserPrincipalName = "" Id = $m.id ObjectType = "Gerät" SourceGroup = $SourceGroupName }) } '#microsoft.graph.group' { if ($ResolveNested) { $nested = Get-ResolvedGroupMembers -GroupId $m.id -SourceGroupName $m.displayName -ResolveNested -Visited $Visited foreach ($n in $nested) { $results.Add($n) } } else { $results.Add([PSCustomObject]@{ DisplayName = $m.displayName UserPrincipalName = "" Id = $m.id ObjectType = "Gruppe (nicht aufgelöst)" SourceGroup = $SourceGroupName }) } } default { $results.Add([PSCustomObject]@{ DisplayName = $m.displayName UserPrincipalName = "" Id = $m.id ObjectType = "Andere ($odataType)" SourceGroup = $SourceGroupName }) } } } return $results } function Get-GraphUser { param( [string]$UserId, [string]$Filter, [string[]]$Property, [switch]$All ) try { $select = if ($Property) { "`$select=" + ($Property -join ",").ToLower() } else { "" } if ($UserId) { $uri = "https://graph.microsoft.com/v1.0/users/$UserId" if ($select) { $uri += "?$select" } return Invoke-MgGraphRequest -Uri $uri -Method GET } else { $uri = "https://graph.microsoft.com/v1.0/users" $params = @() if ($Filter) { $params += "`$filter=$Filter" } if ($select) { $params += $select } if ($params) { $uri += "?" + ($params -join "&") } $results = @() do { $response = Invoke-MgGraphRequest -Uri $uri -Method GET if ($response.value) { $results += $response.value } $uri = $response.'@odata.nextLink' } while ($All -and $uri) return $results } } catch { Write-Warning "Get-GraphUser Fehler: $_" return $null } } function New-GraphGroup { param( [string]$DisplayName, [bool]$MailEnabled = $false, [bool]$SecurityEnabled = $true, [string]$MailNickname ) $body = @{ displayName = $DisplayName mailEnabled = $MailEnabled securityEnabled = $SecurityEnabled mailNickname = $MailNickname } return Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/groups" -Method POST -Body $body } function Add-GraphGroupMember { param([string]$GroupId, [string]$DirectoryObjectId) $body = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$DirectoryObjectId" } Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/groups/$GroupId/members/`$ref" -Method POST -Body $body } function New-AssignmentReport { param( $Targets, $Assignments, $IsUserMode, $SuccessCount, $ErrorCount, $SkippedCount, $DetailedResults ) $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $dateForFile = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" $targetType = if ($IsUserMode) { "Benutzer" } else { "Gruppen/Abteilungen" } $executedBy = $env:USERNAME $totalOps = $SuccessCount + $ErrorCount + $SkippedCount # Gruppiere Assignments nach Typ $availableApps = @($Assignments | Where-Object { $_.Type -eq "Available" }) $requiredApps = @($Assignments | Where-Object { $_.Type -eq "Required" }) $html = @" Intune Zuweisungs-Report - $dateForFile

Intune App-Zuweisungs-Report

HanseMerkur | Automatisierte App-Zuweisung
Datum & Uhrzeit $timestamp
Ausgefuhrt von $executedBy
Ziel-Typ $targetType
Anzahl Ziele $($Targets.Count)
$totalOps
Gesamt
$SuccessCount
Erfolgreich
$SkippedCount
Ubersprungen
$ErrorCount
Fehler
Zugewiesene $targetType
"@ foreach ($target in $Targets) { $html += " $($target.DisplayName)`n" } $html += @"
Available-Zuweisungen $($availableApps.Count) Apps
"@ if ($availableApps.Count -gt 0) { $html += @" "@ foreach ($app in $availableApps) { $html += " `n" } $html += "
App-Name Ziel-Gruppe
$($app.AppName)$($app.GroupName)
`n" } else { $html += "
Keine Available-Zuweisungen
`n" } $html += @"
Required-Zuweisungen $($requiredApps.Count) Apps
"@ if ($requiredApps.Count -gt 0) { $html += @" "@ foreach ($app in $requiredApps) { $html += " `n" } $html += "
App-Name Ziel-Gruppe
$($app.AppName)$($app.GroupName)
`n" } else { $html += "
Keine Required-Zuweisungen
`n" } $html += @"
"@ # Report auf Desktop speichern $desktopPath = [Environment]::GetFolderPath("Desktop") $reportPath = Join-Path $desktopPath "Intune-Zuweisungs-Report_$dateForFile.html" $html | Out-File -FilePath $reportPath -Encoding UTF8 return $reportPath } $xaml = @"