# 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
$SuccessCount
Erfolgreich
$SkippedCount
Ubersprungen
Zugewiesene $targetType
"@
foreach ($target in $Targets) {
$html += " $($target.DisplayName)`n"
}
$html += @"
Available-Zuweisungen
$($availableApps.Count) Apps
"@
if ($availableApps.Count -gt 0) {
$html += @"
| App-Name |
Ziel-Gruppe |
"@
foreach ($app in $availableApps) {
$html += " | $($app.AppName) | $($app.GroupName) |
`n"
}
$html += "
`n"
} else {
$html += "
Keine Available-Zuweisungen
`n"
}
$html += @"
Required-Zuweisungen
$($requiredApps.Count) Apps
"@
if ($requiredApps.Count -gt 0) {
$html += @"
| App-Name |
Ziel-Gruppe |
"@
foreach ($app in $requiredApps) {
$html += " | $($app.AppName) | $($app.GroupName) |
`n"
}
$html += "
`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 = @"
"@
$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
$btnConnect = $window.FindName("btnConnect")
$txtStatus = $window.FindName("txtStatus")
$tabMain = $window.FindName("tabMain")
$btnLoadGroups = $window.FindName("btnLoadGroups")
$btnLoadApps = $window.FindName("btnLoadApps")
$btnCheckMembership = $window.FindName("btnCheckMembership")
$dgGroups = $window.FindName("dgGroups")
$dgUsers = $window.FindName("dgUsers")
$dgRpaGroups = $window.FindName("dgRpaGroups")
$btnLoadRpaGroups = $window.FindName("btnLoadRpaGroups")
$txtRpaGroupCount = $window.FindName("txtRpaGroupCount")
$dgApps = $window.FindName("dgApps")
$txtGroupCount = $window.FindName("txtGroupCount")
$txtUserCount = $window.FindName("txtUserCount")
$txtAppCount = $window.FindName("txtAppCount")
$btnApply = $window.FindName("btnApply")
$txtSummary = $window.FindName("txtSummary")
$pbProgress = $window.FindName("pbProgress")
$txtProgress = $window.FindName("txtProgress")
$txtUserSearch = $window.FindName("txtUserSearch")
$btnSearchUsers = $window.FindName("btnSearchUsers")
$btnClearUserSearch = $window.FindName("btnClearUserSearch")
$txtDeptSearch = $window.FindName("txtDeptSearch")
$btnSearchDept = $window.FindName("btnSearchDept")
$btnClearDeptSearch = $window.FindName("btnClearDeptSearch")
$cmbDepartmentFilter = $window.FindName("cmbDepartmentFilter")
$txtGroupSearch = $window.FindName("txtGroupSearch")
$btnClearGroupSearch = $window.FindName("btnClearGroupSearch")
$txtAppSearch = $window.FindName("txtAppSearch")
$btnClearAppSearch = $window.FindName("btnClearAppSearch")
$cmbAppFilter = $window.FindName("cmbAppFilter")
$btnClearFilter = $window.FindName("btnClearFilter")
$txtActiveFilter = $window.FindName("txtActiveFilter")
$txtSessionCount = $window.FindName("txtSessionCount")
$btnClearSession = $window.FindName("btnClearSession")
$pnlSessionAssignments = $window.FindName("pnlSessionAssignments")
$txtExistingAssignmentsInfo = $window.FindName("txtExistingAssignmentsInfo")
$pnlExistingAssignments = $window.FindName("pnlExistingAssignments")
$txtGroupQuerySearch = $window.FindName("txtGroupQuerySearch")
$btnGroupQuerySearch = $window.FindName("btnGroupQuerySearch")
$chkResolveNestedGroups = $window.FindName("chkResolveNestedGroups")
$txtGroupQueryCount = $window.FindName("txtGroupQueryCount")
$dgGroupQueryResults = $window.FindName("dgGroupQueryResults")
$script:connected = $false
$script:groups = @()
$script:allGroups = @()
$script:users = @()
$script:apps = @()
$script:allApps = @()
$script:currentFilter = "Alle Apps"
# Session-Zuweisungen: Speichert alle Zuweisungen die in dieser Session gemacht wurden
# Format: @{ TargetId_AppId_Type = @{ TargetId; TargetName; TargetType; AppId; AppName; AssignmentType; GroupId; GroupName } }
$script:sessionAssignments = @{}
# Separate App-Auswahl für Gruppen-Modus, Benutzer-Modus und RPA-Modus
# Format: @{ AppId = @{ UseAvailable = $true/$false; UseRequired = $true/$false } }
$script:groupModeAppSelections = @{}
$script:userModeAppSelections = @{}
$script:rpaModeAppSelections = @{}
$script:lastTabIndex = 0 # Speichert den letzten Tab-Index für Wechsel-Erkennung
# RPA-Gruppen
$script:rpaGroups = @()
# Gruppenabfrage (freie Gruppensuche + Mitgliederanzeige)
$script:groupQueryResults = @()
try {
Add-Type @"
using System.Collections.ObjectModel;
public class IntuneGroupItem {
public bool IsSelected { get; set; }
public string Id { get; set; }
public string DisplayName { get; set; }
}
public class IntuneUserItem {
public bool IsSelected { get; set; }
public string Id { get; set; }
public string DisplayName { get; set; }
public string UserPrincipalName { get; set; }
}
public class IntuneGroupAssignment {
public string GroupId { get; set; }
public string DisplayName { get; set; }
public override string ToString() {
return DisplayName;
}
}
public class IntuneAppItem {
public string AppName { get; set; }
public string AppId { get; set; }
public bool UseAvailable { get; set; }
public ObservableCollection AvailableGroups { get; set; }
public IntuneGroupAssignment SelectedAvailableGroup { get; set; }
public int AvailableGroupsCount { get; set; }
public string AvailableGroupDisplayText { get; set; }
public bool UseRequired { get; set; }
public ObservableCollection RequiredGroups { get; set; }
public IntuneGroupAssignment SelectedRequiredGroup { get; set; }
public int RequiredGroupsCount { get; set; }
public string RequiredGroupDisplayText { get; set; }
public string StatusInfo { get; set; }
public string StatusDetails { get; set; }
// Mitgliedschafts-Status: "None", "Partial", "Full", "Native"
public string AvailableMembershipStatus { get; set; }
public string RequiredMembershipStatus { get; set; }
public string AvailableMembershipTooltip { get; set; }
public string RequiredMembershipTooltip { get; set; }
public IntuneAppItem() {
AvailableGroups = new ObservableCollection();
RequiredGroups = new ObservableCollection();
AvailableMembershipStatus = "None";
RequiredMembershipStatus = "None";
AvailableMembershipTooltip = "";
RequiredMembershipTooltip = "";
}
}
"@
} catch { }
function Clear-GroupName {
param([string]$Name)
$cleaned = $Name -replace '[äÄ]', 'ae' -replace '[öÖ]', 'oe' -replace '[üÜ]', 'ue' -replace '[ß]', 'ss' -replace '[^a-zA-Z0-9\-_]', '-' -replace '\-+', '-'
return $cleaned.Trim('-').ToLower()
}
# ===== SESSION-ZUWEISUNGS-FUNKTIONEN =====
function Add-SessionAssignment {
param(
[string]$TargetId,
[string]$TargetName,
[string]$TargetType, # "Group" oder "User"
[string]$AppId,
[string]$AppName,
[string]$AssignmentType, # "Available" oder "Required"
[string]$GroupId,
[string]$GroupName
)
$key = "$TargetId`_$AppId`_$AssignmentType"
$script:sessionAssignments[$key] = @{
TargetId = $TargetId
TargetName = $TargetName
TargetType = $TargetType
AppId = $AppId
AppName = $AppName
AssignmentType = $AssignmentType
GroupId = $GroupId
GroupName = $GroupName
}
Update-SessionPanel
}
function Remove-SessionAssignment {
param([string]$Key)
if ($script:sessionAssignments.ContainsKey($Key)) {
$script:sessionAssignments.Remove($Key)
Update-SessionPanel
}
}
function Clear-AllSessionAssignments {
$script:sessionAssignments = @{}
# Auch alle Checkboxen zurücksetzen
foreach ($group in $script:allGroups) { $group.IsSelected = $false }
foreach ($user in $script:users) { $user.IsSelected = $false }
foreach ($app in $script:allApps) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
$dgGroups.Items.Refresh()
$dgUsers.Items.Refresh()
$dgApps.Items.Refresh()
Update-SessionPanel
Update-Summary
}
function Update-SessionPanel {
$pnlSessionAssignments.Children.Clear()
if ($script:sessionAssignments.Count -eq 0) {
$txtSessionCount.Text = "0 Zuweisungen in dieser Session"
$emptyText = New-Object System.Windows.Controls.TextBlock
$emptyText.Text = "Keine Zuweisungen vorhanden.`n`nWählen Sie Gruppen/Benutzer und Apps aus, um Zuweisungen zu erstellen."
$emptyText.Foreground = "Gray"
$emptyText.FontStyle = "Italic"
$emptyText.TextWrapping = "Wrap"
$emptyText.Margin = "5"
$pnlSessionAssignments.Children.Add($emptyText) | Out-Null
return
}
# Gruppiere nach Target
$byTarget = @{}
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
$targetKey = $assignment.TargetId
if (-not $byTarget.ContainsKey($targetKey)) {
$byTarget[$targetKey] = @{
TargetName = $assignment.TargetName
TargetType = $assignment.TargetType
Assignments = @()
}
}
$byTarget[$targetKey].Assignments += @{
Key = $key
AppName = $assignment.AppName
AssignmentType = $assignment.AssignmentType
GroupName = $assignment.GroupName
}
}
$totalCount = $script:sessionAssignments.Count
$txtSessionCount.Text = "$totalCount Zuweisung(en) in dieser Session"
foreach ($targetId in $byTarget.Keys) {
$target = $byTarget[$targetId]
# Target-Header
$targetBorder = New-Object System.Windows.Controls.Border
$targetBorder.Background = if ($target.TargetType -eq "Group") { "#E8F5E9" } else { "#E3F2FD" }
$targetBorder.Padding = "8"
$targetBorder.Margin = "0,0,0,8"
$targetBorder.CornerRadius = "4"
$targetStack = New-Object System.Windows.Controls.StackPanel
# Target-Name mit Icon
$headerStack = New-Object System.Windows.Controls.StackPanel
$headerStack.Orientation = "Horizontal"
$icon = New-Object System.Windows.Controls.TextBlock
$icon.Text = if ($target.TargetType -eq "Group") { "👥 " } else { "👤 " }
$icon.FontSize = 12
$headerStack.Children.Add($icon) | Out-Null
$nameText = New-Object System.Windows.Controls.TextBlock
$nameText.Text = $target.TargetName
$nameText.FontWeight = "SemiBold"
$nameText.FontSize = 11
$nameText.TextTrimming = "CharacterEllipsis"
$headerStack.Children.Add($nameText) | Out-Null
$targetStack.Children.Add($headerStack) | Out-Null
# Zuweisungen für dieses Target
foreach ($assign in $target.Assignments) {
$assignGrid = New-Object System.Windows.Controls.Grid
$assignGrid.Margin = "0,4,0,0"
$col1 = New-Object System.Windows.Controls.ColumnDefinition
$col1.Width = "*"
$col2 = New-Object System.Windows.Controls.ColumnDefinition
$col2.Width = "Auto"
$col3 = New-Object System.Windows.Controls.ColumnDefinition
$col3.Width = "Auto"
$assignGrid.ColumnDefinitions.Add($col1) | Out-Null
$assignGrid.ColumnDefinitions.Add($col2) | Out-Null
$assignGrid.ColumnDefinitions.Add($col3) | Out-Null
$appText = New-Object System.Windows.Controls.TextBlock
$appText.Text = $assign.AppName
$appText.FontSize = 10
$appText.TextTrimming = "CharacterEllipsis"
$appText.ToolTip = "$($assign.AppName) -> $($assign.GroupName)"
$appText.VerticalAlignment = "Center"
[System.Windows.Controls.Grid]::SetColumn($appText, 0)
$assignGrid.Children.Add($appText) | Out-Null
$typeLabel = New-Object System.Windows.Controls.TextBlock
$typeLabel.Text = if ($assign.AssignmentType -eq "Available") { "A" } else { "R" }
$typeLabel.FontSize = 9
$typeLabel.FontWeight = "Bold"
$typeLabel.Padding = "4,1"
$typeLabel.Margin = "4,0"
$typeLabel.VerticalAlignment = "Center"
if ($assign.AssignmentType -eq "Available") {
$typeLabel.Foreground = "#2E7D32"
} else {
$typeLabel.Foreground = "#C62828"
}
$typeLabel.ToolTip = $assign.AssignmentType
[System.Windows.Controls.Grid]::SetColumn($typeLabel, 1)
$assignGrid.Children.Add($typeLabel) | Out-Null
# Löschen-Button
$removeBtn = New-Object System.Windows.Controls.Button
$removeBtn.Content = "✕"
$removeBtn.Width = 18
$removeBtn.Height = 18
$removeBtn.FontSize = 9
$removeBtn.Padding = "0"
$removeBtn.ToolTip = "Diese Zuweisung entfernen"
$removeBtn.Tag = $assign.Key
$removeBtn.Add_Click({
param($sender, $e)
$keyToRemove = $sender.Tag
Remove-SessionAssignment -Key $keyToRemove
})
[System.Windows.Controls.Grid]::SetColumn($removeBtn, 2)
$assignGrid.Children.Add($removeBtn) | Out-Null
$targetStack.Children.Add($assignGrid) | Out-Null
}
$targetBorder.Child = $targetStack
$pnlSessionAssignments.Children.Add($targetBorder) | Out-Null
}
}
function Save-CurrentSelectionsToSession {
# Diese Funktion speichert alle aktuell ausgewählten Kombinationen in die Session
$selectedTargets = @()
$targetType = ""
switch ($tabMain.SelectedIndex) {
0 {
# Gruppen-Tab
if ($dgGroups.ItemsSource) {
foreach ($item in $dgGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "Group" }
}
}
}
$targetType = "Group"
}
1 {
# Benutzer-Tab
if ($dgUsers.ItemsSource) {
foreach ($item in $dgUsers.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "User" }
}
}
}
$targetType = "User"
}
2 {
# RPA-Tab (RPA-Gruppen sind auch Gruppen)
if ($dgRpaGroups.ItemsSource) {
foreach ($item in $dgRpaGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "Group" }
}
}
}
$targetType = "Group"
}
}
# Für jedes ausgewählte Target und jede ausgewählte App: Session-Zuweisung erstellen
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
foreach ($target in $selectedTargets) {
if ($app.UseAvailable -eq $true -and $null -ne $app.SelectedAvailableGroup -and $app.SelectedAvailableGroup.GroupId -ne "") {
Add-SessionAssignment -TargetId $target.Id -TargetName $target.Name -TargetType $target.Type `
-AppId $app.AppId -AppName $app.AppName -AssignmentType "Available" `
-GroupId $app.SelectedAvailableGroup.GroupId -GroupName $app.SelectedAvailableGroup.DisplayName
}
if ($app.UseRequired -eq $true -and $null -ne $app.SelectedRequiredGroup -and $app.SelectedRequiredGroup.GroupId -ne "") {
Add-SessionAssignment -TargetId $target.Id -TargetName $target.Name -TargetType $target.Type `
-AppId $app.AppId -AppName $app.AppName -AssignmentType "Required" `
-GroupId $app.SelectedRequiredGroup.GroupId -GroupName $app.SelectedRequiredGroup.DisplayName
}
}
}
}
}
}
function Restore-SelectionsFromSession {
# Diese Funktion stellt Auswahlen aus der Session wieder her für die aktuell geladenen Items
# Gruppen wiederherstellen
if ($script:allGroups.Count -gt 0) {
foreach ($group in $script:allGroups) {
$hasAssignment = $false
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
if ($assignment.TargetId -eq $group.Id -and $assignment.TargetType -eq "Group") {
$hasAssignment = $true
break
}
}
$group.IsSelected = $hasAssignment
}
$dgGroups.Items.Refresh()
}
# Benutzer wiederherstellen
if ($script:users.Count -gt 0) {
foreach ($user in $script:users) {
$hasAssignment = $false
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
if ($assignment.TargetId -eq $user.Id -and $assignment.TargetType -eq "User") {
$hasAssignment = $true
break
}
}
$user.IsSelected = $hasAssignment
}
$dgUsers.Items.Refresh()
}
# RPA-Gruppen wiederherstellen
if ($script:rpaGroups.Count -gt 0) {
foreach ($rpaGroup in $script:rpaGroups) {
$hasAssignment = $false
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
if ($assignment.TargetId -eq $rpaGroup.Id -and $assignment.TargetType -eq "Group") {
$hasAssignment = $true
break
}
}
$rpaGroup.IsSelected = $hasAssignment
}
$dgRpaGroups.Items.Refresh()
}
# App-Checkboxen können nicht einfach wiederhergestellt werden, da sie target-spezifisch sind
# Sie werden nur zurückgesetzt wenn keine aktiven Zuweisungen mehr vorhanden sind
}
function Invoke-SessionAssignments {
# Diese Funktion führt alle Session-Zuweisungen aus
$pbProgress.Visibility = "Visible"
$btnApply.IsEnabled = $false
$totalOperations = $script:sessionAssignments.Count
$currentOperation = 0
$successCount = 0
$errorCount = 0
$skippedCount = 0
# Sammle Daten für den Report
$reportTargets = @()
$reportAssignments = @()
$hasUsers = $false
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
$currentOperation++
$pbProgress.Value = ($currentOperation / $totalOperations) * 100
$txtProgress.Text = "$($assignment.TargetName) -> $($assignment.AppName) ($($assignment.GroupName) - $($assignment.AssignmentType))"
[System.Windows.Forms.Application]::DoEvents()
# Report-Daten sammeln
if (-not ($reportTargets | Where-Object { $_.Id -eq $assignment.TargetId })) {
$reportTargets += [PSCustomObject]@{ Id = $assignment.TargetId; DisplayName = $assignment.TargetName }
}
if (-not ($reportAssignments | Where-Object { $_.AppName -eq $assignment.AppName -and $_.GroupId -eq $assignment.GroupId -and $_.Type -eq $assignment.AssignmentType })) {
$reportAssignments += @{
AppName = $assignment.AppName
GroupId = $assignment.GroupId
GroupName = $assignment.GroupName
Type = $assignment.AssignmentType
}
}
if ($assignment.TargetType -eq "User") { $hasUsers = $true }
# Native Zuweisungen überspringen
if ($assignment.GroupId -eq "ALL_USERS" -or $assignment.GroupId -eq "ALL_DEVICES") {
$skippedCount++
continue
}
try {
Add-GraphGroupMember -GroupId $assignment.GroupId -DirectoryObjectId $assignment.TargetId
$successCount++
}
catch {
if ($_.Exception.Message -like "*already exist*" -or $_.Exception.Message -like "*bereits vorhanden*" -or $_.Exception.Message -like "*One or more added object references already exist*") {
$successCount++
}
else {
$errorCount++
}
}
}
$pbProgress.Visibility = "Collapsed"
$btnApply.IsEnabled = $true
$txtProgress.Text = ""
# HTML-Report erstellen
$reportPath = New-AssignmentReport -Targets $reportTargets -Assignments $reportAssignments -IsUserMode $hasUsers `
-SuccessCount $successCount -ErrorCount $errorCount -SkippedCount $skippedCount
$resultMessage = "Zuweisungen abgeschlossen:`n`nErfolgreich: $successCount`n"
if ($skippedCount -gt 0) { $resultMessage += "Übersprungen (Native): $skippedCount`n" }
$resultMessage += "Fehler: $errorCount`nGesamt: $totalOperations"
$resultMessage += "`n`nReport erstellt: $reportPath`n`nReport jetzt öffnen?"
$openReport = [System.Windows.Forms.MessageBox]::Show($resultMessage, "Abgeschlossen", "YesNo", "Information")
if ($openReport -eq "Yes") {
Start-Process $reportPath
}
# Session und UI zurücksetzen
Clear-AllSessionAssignments
# App-Auswahl für beide Modi zurücksetzen
$script:groupModeAppSelections = @{}
$script:userModeAppSelections = @{}
}
function Save-AppSelectionsForCurrentMode {
# Speichert die aktuelle App-Auswahl für den aktuellen Modus (Gruppen oder Benutzer)
$currentSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app -and ($app.UseAvailable -eq $true -or $app.UseRequired -eq $true)) {
$currentSelections[$app.AppId] = @{
UseAvailable = $app.UseAvailable
UseRequired = $app.UseRequired
SelectedAvailableGroup = $app.SelectedAvailableGroup
SelectedRequiredGroup = $app.SelectedRequiredGroup
}
}
}
}
switch ($script:lastTabIndex) {
0 { $script:groupModeAppSelections = $currentSelections }
1 { $script:userModeAppSelections = $currentSelections }
2 { $script:rpaModeAppSelections = $currentSelections }
}
}
function Restore-AppSelectionsForMode {
param([int]$TabIndex)
# Stellt die App-Auswahl für den angegebenen Modus wieder her
$selections = switch ($TabIndex) {
0 { $script:groupModeAppSelections }
1 { $script:userModeAppSelections }
2 { $script:rpaModeAppSelections }
default { @{} }
}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
if ($selections.ContainsKey($app.AppId)) {
$saved = $selections[$app.AppId]
$app.UseAvailable = $saved.UseAvailable
$app.UseRequired = $saved.UseRequired
if ($null -ne $saved.SelectedAvailableGroup) {
$app.SelectedAvailableGroup = $saved.SelectedAvailableGroup
}
if ($null -ne $saved.SelectedRequiredGroup) {
$app.SelectedRequiredGroup = $saved.SelectedRequiredGroup
}
}
else {
# Keine gespeicherte Auswahl -> zurücksetzen
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
}
$dgApps.Items.Refresh()
}
}
function Clear-CurrentModeAppSelections {
# Setzt die App-Checkboxen für den aktuellen Modus zurück (nach dem Speichern in Session)
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# Auch die gespeicherte Auswahl für den aktuellen Modus löschen
switch ($tabMain.SelectedIndex) {
0 { $script:groupModeAppSelections = @{} }
1 { $script:userModeAppSelections = @{} }
2 { $script:rpaModeAppSelections = @{} }
}
}
# Cache für Gruppenmitgliedschaften (um wiederholte API-Aufrufe zu vermeiden)
$script:membershipCache = @{}
function Update-MembershipIndicators {
# Prüft für alle ausgewählten Targets, ob sie bereits Mitglied der App-Zuweisungsgruppen sind
# Sammle ausgewählte Targets
$selectedTargets = @()
switch ($tabMain.SelectedIndex) {
0 {
# Gruppen-Tab
if ($dgGroups.ItemsSource) {
foreach ($item in $dgGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "Group" }
}
}
}
}
1 {
# Benutzer-Tab
if ($dgUsers.ItemsSource) {
foreach ($item in $dgUsers.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "User" }
}
}
}
}
2 {
# RPA-Tab (RPA-Gruppen sind auch Gruppen)
if ($dgRpaGroups.ItemsSource) {
foreach ($item in $dgRpaGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$selectedTargets += @{ Id = $item.Id; Name = $item.DisplayName; Type = "Group" }
}
}
}
}
}
# Wenn keine Targets ausgewählt, alle Status zurücksetzen
if ($selectedTargets.Count -eq 0) {
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.AvailableMembershipStatus = "None"
$app.RequiredMembershipStatus = "None"
$app.AvailableMembershipTooltip = ""
$app.RequiredMembershipTooltip = ""
}
}
$dgApps.Items.Refresh()
}
return
}
# Lade Gruppenmitgliedschaften für alle Targets (mit Cache)
$targetMemberships = @{}
foreach ($target in $selectedTargets) {
if (-not $script:membershipCache.ContainsKey($target.Id)) {
try {
$memberships = @()
if ($target.Type -eq "User") {
# Benutzer: Direkte Gruppenmitgliedschaften laden
$uri = "https://graph.microsoft.com/v1.0/users/$($target.Id)/memberOf?`$select=id"
do {
$response = Invoke-MgGraphRequest -Uri $uri -Method GET
foreach ($g in $response.value) {
if ($g.'@odata.type' -eq '#microsoft.graph.group') {
$memberships += $g.id
}
}
$uri = $response.'@odata.nextLink'
} while ($uri)
}
else {
# Gruppe: Transitive Mitgliedschaften laden (Gruppe ist Mitglied von...)
$uri = "https://graph.microsoft.com/v1.0/groups/$($target.Id)/memberOf?`$select=id"
do {
$response = Invoke-MgGraphRequest -Uri $uri -Method GET
foreach ($g in $response.value) {
if ($g.'@odata.type' -eq '#microsoft.graph.group') {
$memberships += $g.id
}
}
$uri = $response.'@odata.nextLink'
} while ($uri)
}
$script:membershipCache[$target.Id] = $memberships
}
catch {
$script:membershipCache[$target.Id] = @()
}
}
$targetMemberships[$target.Id] = $script:membershipCache[$target.Id]
}
# Für jede App den Mitgliedschaftsstatus prüfen
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -eq $app) { continue }
# Available-Gruppe prüfen
$availableGroupId = $null
if ($null -ne $app.SelectedAvailableGroup) {
$availableGroupId = $app.SelectedAvailableGroup.GroupId
}
if ([string]::IsNullOrEmpty($availableGroupId)) {
$app.AvailableMembershipStatus = "None"
$app.AvailableMembershipTooltip = ""
}
elseif ($availableGroupId -eq "ALL_USERS" -or $availableGroupId -eq "ALL_DEVICES") {
$app.AvailableMembershipStatus = "Native"
$app.AvailableMembershipTooltip = "Native Zuweisung (All Users/All Devices)"
}
else {
$membersInGroup = @()
$membersNotInGroup = @()
foreach ($target in $selectedTargets) {
if ($targetMemberships[$target.Id] -contains $availableGroupId) {
$membersInGroup += $target.Name
}
else {
$membersNotInGroup += $target.Name
}
}
if ($membersNotInGroup.Count -eq 0) {
$app.AvailableMembershipStatus = "Full"
$app.AvailableMembershipTooltip = "Bereits Mitglied: " + ($membersInGroup -join ", ")
}
elseif ($membersInGroup.Count -gt 0) {
$app.AvailableMembershipStatus = "Partial"
$app.AvailableMembershipTooltip = "Mitglied: " + ($membersInGroup -join ", ") + "`nNicht Mitglied: " + ($membersNotInGroup -join ", ")
}
else {
$app.AvailableMembershipStatus = "None"
$app.AvailableMembershipTooltip = "Nicht Mitglied: " + ($membersNotInGroup -join ", ")
}
}
# Required-Gruppe prüfen
$requiredGroupId = $null
if ($null -ne $app.SelectedRequiredGroup) {
$requiredGroupId = $app.SelectedRequiredGroup.GroupId
}
if ([string]::IsNullOrEmpty($requiredGroupId)) {
$app.RequiredMembershipStatus = "None"
$app.RequiredMembershipTooltip = ""
}
elseif ($requiredGroupId -eq "ALL_USERS" -or $requiredGroupId -eq "ALL_DEVICES") {
$app.RequiredMembershipStatus = "Native"
$app.RequiredMembershipTooltip = "Native Zuweisung (All Users/All Devices)"
}
else {
$membersInGroup = @()
$membersNotInGroup = @()
foreach ($target in $selectedTargets) {
if ($targetMemberships[$target.Id] -contains $requiredGroupId) {
$membersInGroup += $target.Name
}
else {
$membersNotInGroup += $target.Name
}
}
if ($membersNotInGroup.Count -eq 0) {
$app.RequiredMembershipStatus = "Full"
$app.RequiredMembershipTooltip = "Bereits Mitglied: " + ($membersInGroup -join ", ")
}
elseif ($membersInGroup.Count -gt 0) {
$app.RequiredMembershipStatus = "Partial"
$app.RequiredMembershipTooltip = "Mitglied: " + ($membersInGroup -join ", ") + "`nNicht Mitglied: " + ($membersNotInGroup -join ", ")
}
else {
$app.RequiredMembershipStatus = "None"
$app.RequiredMembershipTooltip = "Nicht Mitglied: " + ($membersNotInGroup -join ", ")
}
}
}
$dgApps.Items.Refresh()
}
}
function Clear-MembershipCache {
$script:membershipCache = @{}
}
function Update-ExistingAssignmentsPanel {
# Zeigt die bestehenden App-Zuweisungen für die ausgewählten Targets an
$pnlExistingAssignments.Children.Clear()
# Sammle Apps mit Mitgliedschaft (Full oder Partial)
$existingAssignments = @()
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -eq $app) { continue }
$hasAvailable = $app.AvailableMembershipStatus -eq "Full" -or $app.AvailableMembershipStatus -eq "Partial" -or $app.AvailableMembershipStatus -eq "Native"
$hasRequired = $app.RequiredMembershipStatus -eq "Full" -or $app.RequiredMembershipStatus -eq "Partial" -or $app.RequiredMembershipStatus -eq "Native"
if ($hasAvailable -or $hasRequired) {
$existingAssignments += @{
AppName = $app.AppName
AvailableStatus = $app.AvailableMembershipStatus
AvailableTooltip = $app.AvailableMembershipTooltip
AvailableGroup = if ($null -ne $app.SelectedAvailableGroup) { $app.SelectedAvailableGroup.DisplayName } else { "" }
RequiredStatus = $app.RequiredMembershipStatus
RequiredTooltip = $app.RequiredMembershipTooltip
RequiredGroup = if ($null -ne $app.SelectedRequiredGroup) { $app.SelectedRequiredGroup.DisplayName } else { "" }
}
}
}
}
if ($existingAssignments.Count -eq 0) {
$txtExistingAssignmentsInfo.Text = "Keine bestehenden Zuweisungen für die Auswahl gefunden"
$emptyText = New-Object System.Windows.Controls.TextBlock
$emptyText.Text = "Die ausgewählten Gruppen/Benutzer sind noch keinen App-Zuweisungsgruppen zugeordnet."
$emptyText.Foreground = "Gray"
$emptyText.FontStyle = "Italic"
$emptyText.TextWrapping = "Wrap"
$emptyText.FontSize = 10
$emptyText.Margin = "5"
$pnlExistingAssignments.Children.Add($emptyText) | Out-Null
return
}
$txtExistingAssignmentsInfo.Text = "$($existingAssignments.Count) App(s) mit bestehenden Zuweisungen"
# App-Zeilen (Header ist jetzt in XAML als sticky header)
$rowIndex = 0
foreach ($assign in $existingAssignments) {
$rowGrid = New-Object System.Windows.Controls.Grid
$rowGrid.Margin = "0,2,0,2"
$rowCol1 = New-Object System.Windows.Controls.ColumnDefinition
$rowCol1.Width = "*"
$rowCol2 = New-Object System.Windows.Controls.ColumnDefinition
$rowCol2.Width = "50"
$rowCol3 = New-Object System.Windows.Controls.ColumnDefinition
$rowCol3.Width = "50"
$rowGrid.ColumnDefinitions.Add($rowCol1) | Out-Null
$rowGrid.ColumnDefinitions.Add($rowCol2) | Out-Null
$rowGrid.ColumnDefinitions.Add($rowCol3) | Out-Null
# App Name
$appText = New-Object System.Windows.Controls.TextBlock
$appText.Text = $assign.AppName
$appText.FontSize = 10
$appText.TextTrimming = "CharacterEllipsis"
$appText.VerticalAlignment = "Center"
$appText.ToolTip = $assign.AppName
[System.Windows.Controls.Grid]::SetColumn($appText, 0)
$rowGrid.Children.Add($appText) | Out-Null
# Available Indikator
$availBorder = New-Object System.Windows.Controls.Border
$availBorder.Width = 20
$availBorder.Height = 16
$availBorder.CornerRadius = "3"
$availBorder.HorizontalAlignment = "Center"
if ($assign.AvailableStatus -eq "Full") {
$availBorder.Background = "#C8E6C9"
$availBorder.ToolTip = "Bereits Mitglied: $($assign.AvailableGroup)`n$($assign.AvailableTooltip)"
}
elseif ($assign.AvailableStatus -eq "Partial") {
$availBorder.Background = "#FFF9C4"
$availBorder.ToolTip = "Teilweise Mitglied: $($assign.AvailableGroup)`n$($assign.AvailableTooltip)"
}
elseif ($assign.AvailableStatus -eq "Native") {
$availBorder.Background = "#E3F2FD"
$availBorder.ToolTip = "Native: $($assign.AvailableGroup)"
}
else {
$availBorder.Background = "Transparent"
}
$availText = New-Object System.Windows.Controls.TextBlock
$availText.Text = if ($assign.AvailableStatus -ne "None") { "✓" } else { "" }
$availText.FontSize = 10
$availText.HorizontalAlignment = "Center"
$availText.VerticalAlignment = "Center"
$availBorder.Child = $availText
[System.Windows.Controls.Grid]::SetColumn($availBorder, 1)
$rowGrid.Children.Add($availBorder) | Out-Null
# Required Indikator
$reqBorder = New-Object System.Windows.Controls.Border
$reqBorder.Width = 20
$reqBorder.Height = 16
$reqBorder.CornerRadius = "3"
$reqBorder.HorizontalAlignment = "Center"
if ($assign.RequiredStatus -eq "Full") {
$reqBorder.Background = "#C8E6C9"
$reqBorder.ToolTip = "Bereits Mitglied: $($assign.RequiredGroup)`n$($assign.RequiredTooltip)"
}
elseif ($assign.RequiredStatus -eq "Partial") {
$reqBorder.Background = "#FFF9C4"
$reqBorder.ToolTip = "Teilweise Mitglied: $($assign.RequiredGroup)`n$($assign.RequiredTooltip)"
}
elseif ($assign.RequiredStatus -eq "Native") {
$reqBorder.Background = "#E3F2FD"
$reqBorder.ToolTip = "Native: $($assign.RequiredGroup)"
}
else {
$reqBorder.Background = "Transparent"
}
$reqText = New-Object System.Windows.Controls.TextBlock
$reqText.Text = if ($assign.RequiredStatus -ne "None") { "✓" } else { "" }
$reqText.FontSize = 10
$reqText.HorizontalAlignment = "Center"
$reqText.VerticalAlignment = "Center"
$reqBorder.Child = $reqText
[System.Windows.Controls.Grid]::SetColumn($reqBorder, 2)
$rowGrid.Children.Add($reqBorder) | Out-Null
$pnlExistingAssignments.Children.Add($rowGrid) | Out-Null
# Trennlinie zwischen Apps hinzufügen (außer nach der letzten)
$rowIndex++
if ($rowIndex -lt $existingAssignments.Count) {
$rowSeparator = New-Object System.Windows.Controls.Border
$rowSeparator.BorderBrush = "#E0E0E0"
$rowSeparator.BorderThickness = "0,0,0,1"
$rowSeparator.Margin = "0,2,0,2"
$pnlExistingAssignments.Children.Add($rowSeparator) | Out-Null
}
}
}
function Update-UIForced {
$window.Dispatcher.Invoke([action]{}, [System.Windows.Threading.DispatcherPriority]::Render)
}
function Initialize-AppFilters {
$cmbAppFilter.Items.Clear()
$cmbAppFilter.Items.Add("Alle Apps")
$cmbAppFilter.Items.Add("--- Keine Zuweisungen ---")
$cmbAppFilter.Items.Add("Apps ohne Zuweisungen")
$cmbAppFilter.Items.Add("--- Nach Zuweisungstyp ---")
$cmbAppFilter.Items.Add("Apps nur mit Available-Gruppen")
$cmbAppFilter.Items.Add("Apps nur mit Required-Gruppen")
$cmbAppFilter.Items.Add("--- Mehrfache Zuweisungen ---")
$cmbAppFilter.Items.Add("Apps mit mehreren Available-Gruppen")
$cmbAppFilter.Items.Add("Apps mit mehreren Required-Gruppen")
$cmbAppFilter.Items.Add("--- Native Zuweisungen ---")
$cmbAppFilter.Items.Add("Apps mit 'All Users' in Available")
$cmbAppFilter.Items.Add("Apps mit 'All Users' in Required")
$cmbAppFilter.Items.Add("Apps mit 'All Devices' in Available")
$cmbAppFilter.Items.Add("Apps mit 'All Devices' in Required")
$cmbAppFilter.SelectedIndex = 0
}
function Set-GroupFilter {
if ($script:allGroups.Count -eq 0) { return }
$searchText = $txtGroupSearch.Text
# Prüfe ob der Text leer ist, der Platzhalter ist, oder nur Whitespace
$hasSearch = -not ([string]::IsNullOrWhiteSpace($searchText) -or $searchText -eq $txtGroupSearch.Tag)
$filtered = $script:allGroups
if ($hasSearch) {
# Suche nach dem Präfix "abt-hm-" + Suchtext (case-insensitive)
$filtered = @($filtered | Where-Object {
# Entferne das Präfix für die Suche
$deptName = $_.DisplayName -replace '^abt-hm-', ''
$deptName -ilike "*$searchText*"
})
}
# Sicherstellen, dass $filtered ein Array ist
if ($null -eq $filtered) { $filtered = @() }
if ($filtered -isnot [array]) { $filtered = @($filtered) }
# DataGrid aktualisieren mit Refresh
$script:groups = $filtered
$dgGroups.ItemsSource = $null
$dgGroups.ItemsSource = $script:groups
$dgGroups.Items.Refresh()
if ($hasSearch) {
$txtGroupCount.Text = "$($filtered.Count) von $($script:allGroups.Count) Gruppen (Suche: '$searchText')"
}
else {
$txtGroupCount.Text = "$($script:allGroups.Count) Gruppen geladen"
}
}
function Set-AppFilter {
if ($script:allApps.Count -eq 0) { return }
$searchText = $txtAppSearch.Text
# Prüfe ob der Text leer ist, der Platzhalter ist, oder nur Whitespace
$hasSearch = -not ([string]::IsNullOrWhiteSpace($searchText) -or $searchText -eq $txtAppSearch.Tag)
$filtered = $script:allApps
if ($hasSearch) {
# Case-insensitive Suche mit -ilike
$filtered = @($filtered | Where-Object { $_.AppName -ilike "*$searchText*" })
}
switch ($script:currentFilter) {
"Apps ohne Zuweisungen" {
$filtered = @($filtered | Where-Object { $_.AvailableGroupsCount -eq 0 -and $_.RequiredGroupsCount -eq 0 })
}
"Apps nur mit Available-Gruppen" {
$filtered = @($filtered | Where-Object { $_.AvailableGroupsCount -gt 0 -and $_.RequiredGroupsCount -eq 0 })
}
"Apps nur mit Required-Gruppen" {
$filtered = @($filtered | Where-Object { $_.RequiredGroupsCount -gt 0 -and $_.AvailableGroupsCount -eq 0 })
}
"Apps mit mehreren Available-Gruppen" {
$filtered = @($filtered | Where-Object { $_.AvailableGroups.Count -gt 1 })
}
"Apps mit mehreren Required-Gruppen" {
$filtered = @($filtered | Where-Object { $_.RequiredGroups.Count -gt 1 })
}
"Apps mit 'All Users' in Available" {
$filtered = @($filtered | Where-Object {
$hasAllUsers = $false
foreach ($group in $_.AvailableGroups) { if ($group.GroupId -eq "ALL_USERS") { $hasAllUsers = $true; break } }
$hasAllUsers
})
}
"Apps mit 'All Users' in Required" {
$filtered = @($filtered | Where-Object {
$hasAllUsers = $false
foreach ($group in $_.RequiredGroups) { if ($group.GroupId -eq "ALL_USERS") { $hasAllUsers = $true; break } }
$hasAllUsers
})
}
"Apps mit 'All Devices' in Available" {
$filtered = @($filtered | Where-Object {
$hasAllDevices = $false
foreach ($group in $_.AvailableGroups) { if ($group.GroupId -eq "ALL_DEVICES") { $hasAllDevices = $true; break } }
$hasAllDevices
})
}
"Apps mit 'All Devices' in Required" {
$filtered = @($filtered | Where-Object {
$hasAllDevices = $false
foreach ($group in $_.RequiredGroups) { if ($group.GroupId -eq "ALL_DEVICES") { $hasAllDevices = $true; break } }
$hasAllDevices
})
}
}
# Sicherstellen, dass $filtered ein Array ist
if ($null -eq $filtered) { $filtered = @() }
if ($filtered -isnot [array]) { $filtered = @($filtered) }
# DataGrid aktualisieren mit Refresh
$script:apps = $filtered
$dgApps.ItemsSource = $null
$dgApps.ItemsSource = $script:apps
$dgApps.Items.Refresh()
if ($hasSearch -or $script:currentFilter -ne "Alle Apps") {
$filterInfo = ""
if ($script:currentFilter -ne "Alle Apps") { $filterInfo = "Filter: $($script:currentFilter)" }
if ($hasSearch) {
if ($filterInfo) { $filterInfo += " | " }
$filterInfo += "Suche: '$searchText'"
}
$txtAppCount.Text = "$($filtered.Count) von $($script:allApps.Count) Apps ($filterInfo)"
}
else {
$txtAppCount.Text = "$($script:allApps.Count) Apps geladen"
}
}
function Get-AllGroupMembersRecursive {
param([string]$GroupId, [hashtable]$ProcessedGroups = @{}, [int]$Level = 0)
if ($ProcessedGroups.ContainsKey($GroupId)) { return @() }
$ProcessedGroups[$GroupId] = $true
$allUsers = @{}
$members = Get-GraphGroupMember -GroupId $GroupId -All
foreach ($member in $members) {
$memberType = $member.'@odata.type'
if ($memberType -eq "#microsoft.graph.user") {
try {
$user = Get-GraphUser -UserId $member.id -Property Id,DisplayName,UserPrincipalName
if ($user -and -not $allUsers.ContainsKey($user.id)) { $allUsers[$user.id] = $user }
} catch {}
}
elseif ($memberType -eq "#microsoft.graph.group") {
$subGroupUsers = Get-AllGroupMembersRecursive -GroupId $member.id -ProcessedGroups $ProcessedGroups -Level ($Level + 1)
foreach ($subUser in $subGroupUsers) {
if (-not $allUsers.ContainsKey($subUser.id)) { $allUsers[$subUser.id] = $subUser }
}
}
}
return $allUsers.Values
}
function Update-Summary {
$selectedTargets = @()
$targetType = ""
if ($tabMain.SelectedIndex -eq 0) {
if ($dgGroups.ItemsSource) {
foreach ($item in $dgGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) { $selectedTargets += $item }
}
}
$targetType = "Gruppen"
}
else {
if ($dgUsers.ItemsSource) {
foreach ($item in $dgUsers.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) { $selectedTargets += $item }
}
}
$targetType = "Benutzer"
}
$assignmentCount = 0
if ($dgApps.ItemsSource) {
foreach ($item in $dgApps.ItemsSource) {
if ($null -ne $item) {
if ($item.UseAvailable -eq $true -and $null -ne $item.SelectedAvailableGroup -and $item.SelectedAvailableGroup.GroupId -ne "") { $assignmentCount++ }
if ($item.UseRequired -eq $true -and $null -ne $item.SelectedRequiredGroup -and $item.SelectedRequiredGroup.GroupId -ne "") { $assignmentCount++ }
}
}
}
# Session-Zuweisungen zählen
$sessionCount = $script:sessionAssignments.Count
# Zusammenfassung erstellen
$summaryParts = @()
if ($selectedTargets.Count -gt 0 -and $assignmentCount -gt 0) {
$currentOps = $selectedTargets.Count * $assignmentCount
$summaryParts += "Aktuell: $($selectedTargets.Count) $targetType × $assignmentCount Apps = $currentOps Ops"
}
if ($sessionCount -gt 0) {
$summaryParts += "Session: $sessionCount Zuweisung(en)"
}
if ($summaryParts.Count -gt 0) {
$txtSummary.Text = $summaryParts -join " | "
$btnApply.IsEnabled = $true
$btnApply.Background = "#0da075"
}
else {
if ($selectedTargets.Count -eq 0) { $txtSummary.Text = "Bitte wählen Sie mindestens eine Gruppe oder einen Benutzer aus" }
elseif ($assignmentCount -eq 0) { $txtSummary.Text = "Bitte aktivieren Sie mindestens eine App-Zuweisung" }
else { $txtSummary.Text = "Wählen Sie Ziele und Apps aus" }
$btnApply.IsEnabled = $false
$btnApply.Background = "LightGray"
}
}
function Show-CreateGroupDialog {
param($AppItem)
$dialogXaml = @"
"@
$dialogReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$dialogXaml)
$dialog = [Windows.Markup.XamlReader]::Load($dialogReader)
$dialog.Owner = $window
$txtSuggestedName = $dialog.FindName("txtSuggestedName")
$txtSuggestedStatus = $dialog.FindName("txtSuggestedStatus")
$btnCreateSuggested = $dialog.FindName("btnCreateSuggested")
$txtCustomName = $dialog.FindName("txtCustomName")
$txtFullCustomName = $dialog.FindName("txtFullCustomName")
$txtCustomStatus = $dialog.FindName("txtCustomStatus")
$btnCreateCustom = $dialog.FindName("btnCreateCustom")
$chkAssignToApp = $dialog.FindName("chkAssignToApp")
$btnCancel = $dialog.FindName("btnCancel")
$cleanedAppName = Clear-GroupName -Name $AppItem.AppName
$suggestedName = "intune-win-app-$cleanedAppName-required".ToLower()
$txtSuggestedName.Text = $suggestedName
try {
$existingGroups = Get-GraphGroup -Filter "displayName eq '$suggestedName'" -All
if ($existingGroups -and @($existingGroups).Count -gt 0) {
$txtSuggestedStatus.Text = "⚠ Gruppe existiert bereits!"
$txtSuggestedStatus.Foreground = "OrangeRed"
$btnCreateSuggested.IsEnabled = $false
}
else {
$txtSuggestedStatus.Text = "✓ Name verfügbar"
$txtSuggestedStatus.Foreground = "Green"
$btnCreateSuggested.IsEnabled = $true
}
}
catch {
$txtSuggestedStatus.Text = "Prüfung fehlgeschlagen"
$txtSuggestedStatus.Foreground = "Gray"
$btnCreateSuggested.IsEnabled = $false
}
$txtCustomName.Add_TextChanged({
if ([string]::IsNullOrWhiteSpace($txtCustomName.Text)) {
$txtFullCustomName.Text = ""
$txtCustomStatus.Text = ""
$btnCreateCustom.IsEnabled = $false
}
else {
$cleanedCustom = Clear-GroupName -Name $txtCustomName.Text
$fullName = "intune-win-app-$cleanedCustom-required".ToLower()
$txtFullCustomName.Text = "Vollständiger Name: $fullName"
try {
$existingGroups = Get-GraphGroup -Filter "displayName eq '$fullName'" -All
if ($existingGroups -and @($existingGroups).Count -gt 0) {
$txtCustomStatus.Text = "⚠ Gruppe existiert bereits!"
$txtCustomStatus.Foreground = "OrangeRed"
$btnCreateCustom.IsEnabled = $false
}
else {
$txtCustomStatus.Text = "✓ Name verfügbar"
$txtCustomStatus.Foreground = "Green"
$btnCreateCustom.IsEnabled = $true
}
}
catch {
$txtCustomStatus.Text = "Prüfung fehlgeschlagen"
$txtCustomStatus.Foreground = "Gray"
$btnCreateCustom.IsEnabled = $false
}
}
})
$btnCreateSuggested.Add_Click({
$dialog.Tag = @{
Action = "Create"
GroupName = $suggestedName
AssignToApp = $chkAssignToApp.IsChecked
}
$dialog.Close()
})
$btnCreateCustom.Add_Click({
$cleanedCustom = Clear-GroupName -Name $txtCustomName.Text
$fullName = "intune-win-app-$cleanedCustom-required".ToLower()
$dialog.Tag = @{
Action = "Create"
GroupName = $fullName
AssignToApp = $chkAssignToApp.IsChecked
}
$dialog.Close()
})
$btnCancel.Add_Click({ $dialog.Tag = @{ Action = "Cancel" }; $dialog.Close() })
$dialog.ShowDialog() | Out-Null
if ($dialog.Tag.Action -eq "Create") {
New-RequiredGroup -GroupName $dialog.Tag.GroupName -AppItem $AppItem -AssignToApp $dialog.Tag.AssignToApp
}
}
function Show-GroupMembersDialog {
param(
[string]$GroupId,
[string]$GroupName,
[bool]$ResolveNested
)
$dialogXaml = @"
"@
$dialogReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$dialogXaml)
$dialog = [Windows.Markup.XamlReader]::Load($dialogReader)
$dialog.Owner = $window
$chkResolveNestedDialog = $dialog.FindName("chkResolveNestedDialog")
$btnReloadMembers = $dialog.FindName("btnReloadMembers")
$txtMemberCount = $dialog.FindName("txtMemberCount")
$dgMembers = $dialog.FindName("dgMembers")
$btnExportMembers = $dialog.FindName("btnExportMembers")
$btnCloseMembers = $dialog.FindName("btnCloseMembers")
$chkResolveNestedDialog.IsChecked = $ResolveNested
$script:dialogMembers = @()
# Initiales Laden der Mitglieder
try {
$dialog.Cursor = [System.Windows.Input.Cursors]::Wait
$visited = [System.Collections.Generic.HashSet[string]]::new()
$resolve = $chkResolveNestedDialog.IsChecked -eq $true
$members = Get-ResolvedGroupMembers -GroupId $GroupId -SourceGroupName $GroupName -ResolveNested:$resolve -Visited $visited
$script:dialogMembers = @($members | Sort-Object ObjectType, DisplayName)
$dgMembers.ItemsSource = $script:dialogMembers
$txtMemberCount.Text = "$($script:dialogMembers.Count) Mitglied(er)"
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der Mitglieder: $_", "Fehler", "OK", "Error")
}
finally {
$dialog.Cursor = [System.Windows.Input.Cursors]::Arrow
}
$btnReloadMembers.Add_Click({
try {
$dialog.Cursor = [System.Windows.Input.Cursors]::Wait
$visited = [System.Collections.Generic.HashSet[string]]::new()
$resolve = $chkResolveNestedDialog.IsChecked -eq $true
$members = Get-ResolvedGroupMembers -GroupId $GroupId -SourceGroupName $GroupName -ResolveNested:$resolve -Visited $visited
$script:dialogMembers = @($members | Sort-Object ObjectType, DisplayName)
$dgMembers.ItemsSource = $null
$dgMembers.ItemsSource = $script:dialogMembers
$txtMemberCount.Text = "$($script:dialogMembers.Count) Mitglied(er)"
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der Mitglieder: $_", "Fehler", "OK", "Error")
}
finally {
$dialog.Cursor = [System.Windows.Input.Cursors]::Arrow
}
})
$btnExportMembers.Add_Click({
if (-not $script:dialogMembers -or $script:dialogMembers.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Keine Mitglieder zum Exportieren vorhanden.", "Hinweis", "OK", "Information")
return
}
$saveDialog = New-Object System.Windows.Forms.SaveFileDialog
$saveDialog.Filter = "CSV-Dateien (*.csv)|*.csv"
$safeName = ($GroupName -replace '[^\w\-]', '_')
$saveDialog.FileName = "GroupMembers_${safeName}_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
if ($saveDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$script:dialogMembers | Select-Object DisplayName, UserPrincipalName, ObjectType, SourceGroup |
Export-Csv -Path $saveDialog.FileName -NoTypeInformation -Encoding UTF8
[System.Windows.Forms.MessageBox]::Show("Export abgeschlossen:`n$($saveDialog.FileName)", "Export erfolgreich", "OK", "Information")
}
})
$btnCloseMembers.Add_Click({ $dialog.Close() })
$dialog.ShowDialog() | Out-Null
}
function Show-DepartmentSelectionDialog {
param($Departments, $SearchTerm)
$selectionXaml = @"
"@
$selectionReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$selectionXaml)
$selectionDialog = [Windows.Markup.XamlReader]::Load($selectionReader)
$selectionDialog.Owner = $window
$txtSearchInfo = $selectionDialog.FindName("txtSearchInfo")
$lstDepartments = $selectionDialog.FindName("lstDepartments")
$btnCancel = $selectionDialog.FindName("btnCancel")
$btnSelect = $selectionDialog.FindName("btnSelect")
$txtSearchInfo.Text = "Es wurden $($Departments.Count) Abteilungen gefunden, die '$SearchTerm' enthalten. Bitte wählen Sie eine aus:"
foreach ($dept in $Departments) {
$lstDepartments.Items.Add($dept)
}
$lstDepartments.Add_SelectionChanged({
$btnSelect.IsEnabled = ($null -ne $lstDepartments.SelectedItem)
})
$btnCancel.Add_Click({
$selectionDialog.Tag = $null
$selectionDialog.Close()
})
$btnSelect.Add_Click({
$selectionDialog.Tag = $lstDepartments.SelectedItem
$selectionDialog.Close()
})
$selectionDialog.ShowDialog() | Out-Null
return $selectionDialog.Tag
}
function Show-AssignmentReviewDialog {
param($TargetsWithAssignments, $TargetType, $TotalAssignments)
$reviewXaml = @"
"@
$reviewReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]$reviewXaml)
$reviewDialog = [Windows.Markup.XamlReader]::Load($reviewReader)
$reviewDialog.Owner = $window
$txtTargetsSummary = $reviewDialog.FindName("txtTargetsSummary")
$txtAssignmentsSummary = $reviewDialog.FindName("txtAssignmentsSummary")
$txtTotalOperations = $reviewDialog.FindName("txtTotalOperations")
$pnlDetails = $reviewDialog.FindName("pnlDetails")
$btnCancel = $reviewDialog.FindName("btnCancel")
$btnConfirm = $reviewDialog.FindName("btnConfirm")
# Berechne Anzahl der einzigartigen Zuweisungen (Apps)
$uniqueAssignments = @{}
foreach ($target in $TargetsWithAssignments) {
foreach ($assign in $target.Assignments) {
$key = "$($assign.AppName)_$($assign.GroupId)_$($assign.Type)"
if (-not $uniqueAssignments.ContainsKey($key)) {
$uniqueAssignments[$key] = $assign
}
}
}
# Summary-Zahlen setzen
$txtTargetsSummary.Text = "$($TargetsWithAssignments.Count) $TargetType"
$txtAssignmentsSummary.Text = "$($uniqueAssignments.Count) Zuweisungen"
$txtTotalOperations.Text = "$TotalAssignments Operationen"
# Detaillierte Übersicht erstellen - jedes Target mit seinen EIGENEN Zuweisungen
foreach ($target in $TargetsWithAssignments) {
$targetBorder = New-Object System.Windows.Controls.Border
$targetBorder.Background = if ($target.Type -eq "Group") { "#E8F5E9" } else { "#E3F2FD" }
$targetBorder.Padding = "10"
$targetBorder.Margin = "0,0,0,10"
$targetBorder.CornerRadius = 3
$targetStack = New-Object System.Windows.Controls.StackPanel
# Header mit Icon basierend auf Target-Typ
$targetHeader = New-Object System.Windows.Controls.TextBlock
$icon = if ($target.Type -eq "Group") { "👥 " } else { "👤 " }
$targetHeader.Text = "$icon $($target.DisplayName)"
$targetHeader.FontWeight = "Bold"
$targetHeader.FontSize = 13
$targetHeader.Margin = "0,0,0,8"
$targetStack.Children.Add($targetHeader) | Out-Null
# NUR die Zuweisungen für DIESES Target anzeigen
foreach ($assignment in $target.Assignments) {
$assignmentGrid = New-Object System.Windows.Controls.Grid
$assignmentGrid.Margin = "15,0,0,3"
$col1 = New-Object System.Windows.Controls.ColumnDefinition
$col1.Width = "Auto"
$col2 = New-Object System.Windows.Controls.ColumnDefinition
$col2.Width = "*"
$col3 = New-Object System.Windows.Controls.ColumnDefinition
$col3.Width = "Auto"
$assignmentGrid.ColumnDefinitions.Add($col1) | Out-Null
$assignmentGrid.ColumnDefinitions.Add($col2) | Out-Null
$assignmentGrid.ColumnDefinitions.Add($col3) | Out-Null
$bulletIcon = New-Object System.Windows.Controls.TextBlock
if ($assignment.Type -eq "Available") {
$bulletIcon.Text = "● "
$bulletIcon.Foreground = "#4CAF50"
} else {
$bulletIcon.Text = "● "
$bulletIcon.Foreground = "#F44336"
}
$bulletIcon.FontSize = 11
$bulletIcon.VerticalAlignment = "Center"
[System.Windows.Controls.Grid]::SetColumn($bulletIcon, 0)
$assignmentGrid.Children.Add($bulletIcon) | Out-Null
$appText = New-Object System.Windows.Controls.TextBlock
$appText.Text = "$($assignment.AppName)"
$appText.FontSize = 11
$appText.Margin = "5,0,0,0"
$appText.VerticalAlignment = "Center"
[System.Windows.Controls.Grid]::SetColumn($appText, 1)
$assignmentGrid.Children.Add($appText) | Out-Null
$typeLabel = New-Object System.Windows.Controls.TextBlock
$typeLabel.Text = "$($assignment.Type)"
$typeLabel.FontSize = 10
$typeLabel.FontWeight = "Bold"
$typeLabel.Padding = "5,2"
$typeLabel.Margin = "5,0,0,0"
if ($assignment.Type -eq "Available") {
$typeLabel.Foreground = "#2E7D32"
} else {
$typeLabel.Foreground = "#C62828"
}
$typeLabel.VerticalAlignment = "Center"
[System.Windows.Controls.Grid]::SetColumn($typeLabel, 2)
$assignmentGrid.Children.Add($typeLabel) | Out-Null
$targetStack.Children.Add($assignmentGrid) | Out-Null
}
$separator = New-Object System.Windows.Controls.Border
$separator.Height = 1
$separator.Background = "#E0E0E0"
$separator.Margin = "0,8,0,0"
$targetStack.Children.Add($separator) | Out-Null
$countText = New-Object System.Windows.Controls.TextBlock
$countText.Text = "Σ $($target.Assignments.Count) Zuweisung(en) für dieses Ziel"
$countText.FontSize = 10
$countText.Foreground = "Gray"
$countText.Margin = "0,5,0,0"
$countText.FontStyle = "Italic"
$targetStack.Children.Add($countText) | Out-Null
$targetBorder.Child = $targetStack
$pnlDetails.Children.Add($targetBorder) | Out-Null
}
# DialogResult verwenden
$btnCancel.Add_Click({
$reviewDialog.DialogResult = $false
})
$btnConfirm.Add_Click({
$reviewDialog.DialogResult = $true
})
# Dialog anzeigen und Ergebnis speichern
$result = $reviewDialog.ShowDialog()
# Explizit boolean zurückgeben
if ($result -eq $true) {
return $true
}
else {
return $false
}
}
function New-RequiredGroup {
param($GroupName, $AppItem, $AssignToApp)
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$GroupName = $GroupName.ToLower()
$existingGroups = Get-GraphGroup -Filter "displayName eq '$GroupName'" -All
if ($existingGroups -and @($existingGroups).Count -gt 0) {
[System.Windows.Forms.MessageBox]::Show("Fehler: Eine Gruppe mit dem Namen '$GroupName' existiert bereits in Entra ID!", "Gruppe existiert", "OK", "Error")
return
}
$newGroup = New-GraphGroup -DisplayName $GroupName -MailEnabled $false -SecurityEnabled $true -MailNickname $GroupName
$successMessage = "Gruppe erfolgreich erstellt:`n`n$GroupName`n`nID: $($newGroup.id)"
if ($AssignToApp) {
try {
$assignmentBody = @{
"@odata.type" = "#microsoft.graph.mobileAppAssignment"
"intent" = "required"
"target" = @{
"@odata.type" = "#microsoft.graph.groupAssignmentTarget"
"groupId" = $newGroup.id
}
}
$assignmentUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$($AppItem.AppId)/assignments"
$jsonBody = $assignmentBody | ConvertTo-Json -Depth 10
Invoke-MgGraphRequest -Uri $assignmentUri -Method POST -Body $jsonBody -ContentType "application/json"
$newAssignment = New-Object IntuneGroupAssignment
$newAssignment.GroupId = $newGroup.id
$newAssignment.DisplayName = $GroupName
if ($AppItem.RequiredGroupsCount -eq 0) {
$AppItem.RequiredGroups.Clear()
$AppItem.RequiredGroups.Add($newAssignment)
$AppItem.SelectedRequiredGroup = $newAssignment
$AppItem.RequiredGroupsCount = 1
$AppItem.RequiredGroupDisplayText = $GroupName
}
else {
$AppItem.RequiredGroups.Add($newAssignment)
$AppItem.SelectedRequiredGroup = $newAssignment
$AppItem.RequiredGroupsCount = $AppItem.RequiredGroups.Count
}
$dgApps.Items.Refresh()
$successMessage += "`n`n✓ Gruppe wurde der App in Intune zugewiesen"
}
catch {
$successMessage += "`n`n⚠ Warnung: Gruppe wurde erstellt, aber Intune-Zuweisung fehlgeschlagen:`n$($_.Exception.Message)"
}
}
[System.Windows.Forms.MessageBox]::Show($successMessage, "Erfolg", "OK", "Information")
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Erstellen der Gruppe:`n`n$_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
}
function Invoke-Assignments {
param($Targets, $Assignments, $IsUserMode)
$pbProgress.Visibility = "Visible"
$btnApply.IsEnabled = $false
$totalOperations = $Targets.Count * $Assignments.Count
$currentOperation = 0
$successCount = 0
$errorCount = 0
$skippedCount = 0
foreach ($target in $Targets) {
foreach ($assignment in $Assignments) {
$currentOperation++
$pbProgress.Value = ($currentOperation / $totalOperations) * 100
$txtProgress.Text = "$($target.DisplayName) -> $($assignment.AppName) ($($assignment.GroupName) - $($assignment.Type))"
[System.Windows.Forms.Application]::DoEvents()
if ($assignment.GroupId -eq "ALL_USERS" -or $assignment.GroupId -eq "ALL_DEVICES") {
$skippedCount++
continue
}
try {
Add-GraphGroupMember -GroupId $assignment.GroupId -DirectoryObjectId $target.Id
$successCount++
}
catch {
if ($_.Exception.Message -like "*already exist*" -or $_.Exception.Message -like "*bereits vorhanden*" -or $_.Exception.Message -like "*One or more added object references already exist*") {
$successCount++
}
else {
$errorCount++
}
}
}
}
$pbProgress.Visibility = "Collapsed"
$btnApply.IsEnabled = $true
$txtProgress.Text = ""
# HTML-Report erstellen
$reportPath = New-AssignmentReport -Targets $Targets -Assignments $Assignments -IsUserMode $IsUserMode `
-SuccessCount $successCount -ErrorCount $errorCount -SkippedCount $skippedCount
$resultMessage = "Zuweisungen abgeschlossen:`n`nErfolgreich: $successCount`n"
if ($skippedCount -gt 0) { $resultMessage += "Übersprungen (Native): $skippedCount`n" }
$resultMessage += "Fehler: $errorCount`nGesamt: $totalOperations"
$resultMessage += "`n`nReport erstellt: $reportPath`n`nReport jetzt öffnen?"
$openReport = [System.Windows.Forms.MessageBox]::Show($resultMessage, "Abgeschlossen", "YesNo", "Information")
if ($openReport -eq "Yes") {
Start-Process $reportPath
}
if ($IsUserMode) {
foreach ($user in $script:users) { $user.IsSelected = $false }
$dgUsers.Items.Refresh()
}
else {
foreach ($group in $script:groups) { $group.IsSelected = $false }
$dgGroups.Items.Refresh()
}
foreach ($app in $script:apps) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
$dgApps.Items.Refresh()
Update-Summary
}
# ===== EVENT HANDLER =====
$btnConnect.Add_Click({
try {
$txtStatus.Text = "Lade Microsoft.Graph Module..."
$txtStatus.Foreground = "Yellow"
$window.Dispatcher.Invoke([action]{}, [System.Windows.Threading.DispatcherPriority]::Render)
# Module erst beim Connect laden
if (-not (Initialize-GraphModules)) {
$txtStatus.Text = "Module konnten nicht geladen werden"
$txtStatus.Foreground = "Red"
return
}
$txtStatus.Text = "Verbinde mit Microsoft Graph..."
$window.Dispatcher.Invoke([action]{}, [System.Windows.Threading.DispatcherPriority]::Render)
Connect-MgGraph -TenantId "739614a3-9800-4868-ba08-7e2c16128a9f" -ClientId "56b11df1-4228-44a1-b94f-9f3fc27678fc" -Scopes "Group.ReadWrite.All", "GroupMember.ReadWrite.All", "User.Read.All", "DeviceManagementApps.Read.All" -NoWelcome
$context = Get-MgContext
if ($context) {
$script:connected = $true
$txtStatus.Text = "Verbunden als: $($context.Account)"
$txtStatus.Foreground = "LightGreen"
$btnConnect.IsEnabled = $false
[System.Windows.Forms.MessageBox]::Show("Erfolgreich verbunden!", "Verbunden", "OK", "Information")
}
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Verbinden: $_", "Fehler", "OK", "Error")
$txtStatus.Text = "Verbindungsfehler"
$txtStatus.Foreground = "Red"
}
})
$btnLoadGroups.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
# VOR dem Laden: Aktuelle Auswahlen speichern
Save-CurrentSelectionsToSession
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$allGroups = Get-GraphGroup -Filter "startswith(displayName,'abt-hm')" -All -Property Id,DisplayName
$script:allGroups = @()
foreach ($group in $allGroups) {
$groupItem = New-Object IntuneGroupItem
$groupItem.IsSelected = $false
$groupItem.Id = $group.id
$groupItem.DisplayName = $group.displayName
$script:allGroups += $groupItem
}
$script:groups = $script:allGroups
$dgGroups.ItemsSource = $null
$dgGroups.ItemsSource = $script:groups
$cmbDepartmentFilter.Items.Clear()
$cmbDepartmentFilter.Items.Add("-- Abteilung wählen --")
foreach ($group in $script:allGroups) { $cmbDepartmentFilter.Items.Add($group.DisplayName) }
$cmbDepartmentFilter.SelectedIndex = 0
$txtGroupCount.Text = "$($script:allGroups.Count) Gruppen geladen"
# WICHTIG: App-Checkboxen zurücksetzen bei neuem Laden
$script:groupModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# Target-Auswahlen aus Session wiederherstellen
Restore-SelectionsFromSession
Update-Summary
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der Gruppen: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
})
# RPA-Gruppen laden (fest definierte Gruppen)
$btnLoadRpaGroups.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
# VOR dem Laden: Aktuelle Auswahlen speichern
Save-CurrentSelectionsToSession
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
# Die drei spezifischen RPA-Gruppen
$rpaGroupNames = @(
"intune-userrole-windowsclientuser-rpa",
"intune-userrole-windowsclientuser-rpa-fbt",
"intune-userrole-windowsclientuser-rpa-pro"
)
$script:rpaGroups = @()
foreach ($groupName in $rpaGroupNames) {
try {
$filter = "displayName eq '$groupName'"
$group = Get-GraphGroup -Filter $filter -Property Id,DisplayName
if ($group) {
$groupItem = New-Object IntuneGroupItem
$groupItem.IsSelected = $false
$groupItem.Id = $group.id
$groupItem.DisplayName = $group.displayName
$script:rpaGroups += $groupItem
}
}
catch {
Write-Host "Warnung: RPA-Gruppe '$groupName' nicht gefunden: $_"
}
}
$dgRpaGroups.ItemsSource = $null
$dgRpaGroups.ItemsSource = $script:rpaGroups
$txtRpaGroupCount.Text = "$($script:rpaGroups.Count) RPA-Gruppen geladen"
# WICHTIG: App-Checkboxen zurücksetzen bei neuem Laden
$script:rpaModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# Wiederherstellen von Session-Auswahlen
Restore-SelectionsFromSession
Update-Summary
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der RPA-Gruppen: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
})
$btnLoadApps.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$btnLoadApps.IsEnabled = $false
$pbProgress.Visibility = "Visible"
# Apps MIT Zuweisungen in einem Aufruf laden (viel schneller als separate Abfragen)
$uri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps?`$expand=assignments"
$txtProgress.Text = "Lade Apps aus Intune..."
Update-UIForced
$response = Invoke-MgGraphRequest -Uri $uri -Method GET
$allApps = $response.value
while ($response.'@odata.nextLink') {
$response = Invoke-MgGraphRequest -Uri $response.'@odata.nextLink' -Method GET
$allApps += $response.value
}
$excludedTypes = @(
#iOS/iPadOS
'#microsoft.graph.iosLobApp',
'#microsoft.graph.iosStoreApp',
'#microsoft.graph.iosVppApp',
'#microsoft.graph.managedIOSLobApp',
'#microsoft.graph.managedIOSStoreApp',
# Android (inkl. Android Enterprise)
'#microsoft.graph.androidLobApp',
'#microsoft.graph.androidStoreApp',
'#microsoft.graph.managedAndroidLobApp',
'#microsoft.graph.managedAndroidStoreApp',
'#microsoft.graph.androidManagedStoreApp',
'#microsoft.graph.androidForWorkApp',
# macOS
'#microsoft.graph.macOSLobApp',
'#microsoft.graph.macOSDmgApp',
'#microsoft.graph.macOSPkgApp',
'#microsoft.graph.macOSOfficeSuiteApp',
'#microsoft.graph.macOSMicrosoftEdgeApp',
'#microsoft.graph.macOsVppApp'
)
$intuneApps = $allApps | Where-Object {
$appType = $_.'@odata.type'
$excludedTypes -notcontains $appType
}
$txtProgress.Text = "Sammle Gruppen-IDs..."
$pbProgress.Value = 30
Update-UIForced
# Alle eindeutigen Gruppen-IDs aus den Zuweisungen sammeln
$allGroupIds = @{}
foreach ($app in $intuneApps) {
if ($app.assignments) {
foreach ($assignment in $app.assignments) {
if ($assignment.target.'@odata.type' -eq '#microsoft.graph.groupAssignmentTarget') {
$allGroupIds[$assignment.target.groupId] = $true
}
}
}
}
# Alle Gruppen in einem Batch laden (viel schneller als einzelne Abfragen)
$txtProgress.Text = "Lade $($allGroupIds.Count) Gruppen..."
$pbProgress.Value = 40
Update-UIForced
$groupCache = @{}
if ($allGroupIds.Count -gt 0) {
$groupIdList = @($allGroupIds.Keys)
# Graph API Filter mit OR für mehrere IDs (max 15 pro Abfrage wegen URL-Länge)
for ($i = 0; $i -lt $groupIdList.Count; $i += 15) {
$batch = $groupIdList[$i..[Math]::Min($i + 14, $groupIdList.Count - 1)]
$filterParts = $batch | ForEach-Object { "id eq '$_'" }
$filter = $filterParts -join " or "
try {
$groups = Get-GraphGroup -Filter $filter -All -Property Id,DisplayName
foreach ($g in $groups) {
if ($g.id) { $groupCache[$g.id] = $g }
}
} catch { }
}
}
$txtProgress.Text = "Verarbeite Apps..."
$pbProgress.Value = 60
Update-UIForced
$script:allApps = @()
$totalApps = $intuneApps.Count
$processedApps = 0
foreach ($intuneApp in $intuneApps) {
$processedApps++
$appItem = New-Object IntuneAppItem
$appItem.AppName = $intuneApp.displayName
$appItem.AppId = $intuneApp.id
$appItem.UseAvailable = $false
$appItem.UseRequired = $false
$availableAssignments = @()
$requiredAssignments = @()
try {
# Zuweisungen sind bereits durch $expand=assignments geladen
$assignments = $intuneApp.assignments
foreach ($assignment in $assignments) {
$targetType = $assignment.target.'@odata.type'
$intent = $assignment.intent
$groupAssignment = New-Object IntuneGroupAssignment
if ($targetType -eq '#microsoft.graph.allLicensedUsersAssignmentTarget') {
$groupAssignment.GroupId = "ALL_USERS"
$groupAssignment.DisplayName = "All Users"
if ($intent -eq 'available') { $availableAssignments += $groupAssignment }
elseif ($intent -eq 'required') { $requiredAssignments += $groupAssignment }
}
elseif ($targetType -eq '#microsoft.graph.allDevicesAssignmentTarget') {
$groupAssignment.GroupId = "ALL_DEVICES"
$groupAssignment.DisplayName = "All Devices"
if ($intent -eq 'available') { $availableAssignments += $groupAssignment }
elseif ($intent -eq 'required') { $requiredAssignments += $groupAssignment }
}
elseif ($targetType -eq '#microsoft.graph.groupAssignmentTarget') {
$groupId = $assignment.target.groupId
# Gruppe aus vorab geladenem Cache holen
$cachedGroup = $groupCache[$groupId]
if ($null -ne $cachedGroup) {
# Filtere macOS-Gruppen aus (Gruppen mit "macos" im Namen)
if ($cachedGroup.displayName -like "*macos*") {
continue
}
# ALLE anderen zugewiesenen Gruppen anzeigen
$groupAssignment.GroupId = $groupId
$groupAssignment.DisplayName = $cachedGroup.displayName
if ($intent -eq 'available') { $availableAssignments += $groupAssignment }
elseif ($intent -eq 'required') { $requiredAssignments += $groupAssignment }
}
}
}
} catch { }
if ($availableAssignments.Count -eq 0) {
$appItem.AvailableGroupsCount = 0
$appItem.AvailableGroupDisplayText = "Keine Zuweisung vorhanden"
}
elseif ($availableAssignments.Count -eq 1) {
$appItem.AvailableGroups.Add($availableAssignments[0])
$appItem.SelectedAvailableGroup = $availableAssignments[0]
$appItem.AvailableGroupsCount = 1
$appItem.AvailableGroupDisplayText = $availableAssignments[0].DisplayName
}
else {
$appItem.AvailableGroups.Add((New-Object IntuneGroupAssignment -Property @{ GroupId = ""; DisplayName = "-- Bitte wählen --" }))
foreach ($avail in $availableAssignments) { $appItem.AvailableGroups.Add($avail) }
$appItem.SelectedAvailableGroup = $appItem.AvailableGroups[1]
$appItem.AvailableGroupsCount = $availableAssignments.Count + 1
}
if ($requiredAssignments.Count -eq 0) {
$appItem.RequiredGroupsCount = 0
$appItem.RequiredGroupDisplayText = "Keine Zuweisung vorhanden"
}
elseif ($requiredAssignments.Count -eq 1) {
$appItem.RequiredGroups.Add($requiredAssignments[0])
$appItem.SelectedRequiredGroup = $requiredAssignments[0]
$appItem.RequiredGroupsCount = 1
$appItem.RequiredGroupDisplayText = $requiredAssignments[0].DisplayName
}
else {
$appItem.RequiredGroups.Add((New-Object IntuneGroupAssignment -Property @{ GroupId = ""; DisplayName = "-- Bitte wählen --" }))
foreach ($req in $requiredAssignments) { $appItem.RequiredGroups.Add($req) }
$appItem.SelectedRequiredGroup = $appItem.RequiredGroups[1]
$appItem.RequiredGroupsCount = $requiredAssignments.Count + 1
}
$statusParts = @()
if ($availableAssignments.Count -gt 0) { $statusParts += "$($availableAssignments.Count)x AG" }
if ($requiredAssignments.Count -gt 0) { $statusParts += "$($requiredAssignments.Count)x RG" }
if ($statusParts.Count -gt 0) {
$appItem.StatusInfo = $statusParts -join " | "
$detailsParts = @()
if ($availableAssignments.Count -gt 0) { $detailsParts += "Available: " + (($availableAssignments | ForEach-Object { $_.DisplayName }) -join ", ") }
if ($requiredAssignments.Count -gt 0) { $detailsParts += "Required: " + (($requiredAssignments | ForEach-Object { $_.DisplayName }) -join ", ") }
$appItem.StatusDetails = $detailsParts -join "`n"
}
else {
$appItem.StatusInfo = "Keine"
$appItem.StatusDetails = "Diese App hat keine Zuweisungen"
}
$script:allApps += $appItem
if ($processedApps % 10 -eq 0) {
$progress = 30 + (($processedApps / $totalApps) * 60)
$pbProgress.Value = $progress
$txtProgress.Text = "Verarbeite: $processedApps / $totalApps Apps"
Update-UIForced
}
}
$script:apps = $script:allApps
$dgApps.ItemsSource = $null
$dgApps.ItemsSource = $script:apps
$txtAppCount.Text = "$($script:apps.Count) Apps geladen"
$pbProgress.Value = 100
$txtProgress.Text = "Abgeschlossen!"
Update-Summary
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Laden der Apps: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
$btnLoadApps.IsEnabled = $true
Start-Sleep -Milliseconds 500
$pbProgress.Visibility = "Collapsed"
$txtProgress.Text = ""
}
})
$btnCheckMembership.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
# Prüfen ob Targets ausgewählt sind
$hasSelection = $false
switch ($tabMain.SelectedIndex) {
0 {
# Abteilungsgruppen-Tab
if ($dgGroups.ItemsSource) {
foreach ($item in $dgGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$hasSelection = $true
break
}
}
}
}
1 {
# Einzelbenutzer-Tab
if ($dgUsers.ItemsSource) {
foreach ($item in $dgUsers.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$hasSelection = $true
break
}
}
}
}
2 {
# RPA-Tab
if ($dgRpaGroups.ItemsSource) {
foreach ($item in $dgRpaGroups.ItemsSource) {
if ($null -ne $item -and $item.IsSelected -eq $true) {
$hasSelection = $true
break
}
}
}
}
}
if (-not $hasSelection) {
[System.Windows.Forms.MessageBox]::Show("Bitte wählen Sie mindestens eine Gruppe oder einen Benutzer aus!", "Keine Auswahl", "OK", "Warning")
return
}
if (-not $dgApps.ItemsSource -or $script:allApps.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Bitte laden Sie zuerst die Apps!", "Keine Apps", "OK", "Warning")
return
}
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$btnCheckMembership.IsEnabled = $false
$txtProgress.Text = "Prüfe Gruppenmitgliedschaften..."
Update-UIForced
Update-MembershipIndicators
# Bestehende Zuweisungen im Panel anzeigen
Update-ExistingAssignmentsPanel
$txtProgress.Text = "Prüfung abgeschlossen"
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Prüfen der Mitgliedschaften: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
$btnCheckMembership.IsEnabled = $true
$txtProgress.Text = ""
}
})
$btnSearchUsers.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$searchTerm = $txtUserSearch.Text
if ([string]::IsNullOrWhiteSpace($searchTerm) -or $searchTerm -eq $txtUserSearch.Tag) {
[System.Windows.Forms.MessageBox]::Show("Bitte geben Sie einen Suchbegriff ein!", "Keine Eingabe", "OK", "Warning")
return
}
# VOR der Suche: Aktuelle Auswahlen in Session speichern
Save-CurrentSelectionsToSession
$filter = "startswith(displayName,'$searchTerm') or startswith(userPrincipalName,'$searchTerm') or startswith(mail,'$searchTerm')"
$users = Get-GraphUser -Filter $filter -All -Property Id,DisplayName,UserPrincipalName,Mail
$script:users = @()
foreach ($user in $users) {
$userItem = New-Object IntuneUserItem
$userItem.IsSelected = $false
$userItem.Id = $user.id
$userItem.DisplayName = $user.displayName
$userItem.UserPrincipalName = $user.userPrincipalName
$script:users += $userItem
}
$dgUsers.ItemsSource = $null
$dgUsers.ItemsSource = $script:users
$txtUserCount.Text = "$($script:users.Count) Benutzer gefunden"
# ComboBox zurücksetzen wenn Suche verwendet wird
$cmbDepartmentFilter.SelectedIndex = 0
# WICHTIG: App-Checkboxen zurücksetzen bei neuer Suche
# Die bisherigen Zuweisungen sind in der Session gespeichert, aber neue Benutzer
# sollen mit leeren App-Auswahlen starten
$script:userModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# NACH der Suche: Target-Auswahlen aus Session wiederherstellen (aber NICHT App-Checkboxen)
Restore-SelectionsFromSession
Update-Summary
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Suchen der Benutzer: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
})
$btnClearUserSearch.Add_Click({
# VOR dem Löschen: Aktuelle Auswahlen speichern
Save-CurrentSelectionsToSession
# Suchfeld zurücksetzen
$txtUserSearch.Text = $txtUserSearch.Tag
# Benutzer-Liste leeren
$script:users = @()
$dgUsers.ItemsSource = $null
$txtUserCount.Text = "0 Benutzer geladen"
# ComboBox zurücksetzen
if ($cmbDepartmentFilter.Items.Count -gt 0) {
$cmbDepartmentFilter.SelectedIndex = 0
}
# App-Checkboxen zurücksetzen
$script:userModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
Update-Summary
})
$btnSearchDept.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$searchTerm = $txtDeptSearch.Text
if ([string]::IsNullOrWhiteSpace($searchTerm) -or $searchTerm -eq $txtDeptSearch.Tag) {
[System.Windows.Forms.MessageBox]::Show("Bitte geben Sie einen Suchbegriff ein!", "Keine Eingabe", "OK", "Warning")
return
}
# VOR der Suche: Aktuelle Auswahlen in Session speichern
Save-CurrentSelectionsToSession
# Suche nach Abteilungsgruppen die den Suchbegriff enthalten
$allDeptGroups = Get-GraphGroup -Filter "startswith(displayName,'abt-hm')" -All -Property Id,DisplayName
$matchingGroups = @($allDeptGroups | Where-Object {
$deptName = $_.displayName -replace '^abt-hm-', ''
$deptName -ilike "*$searchTerm*"
})
if ($matchingGroups.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Keine Abteilung gefunden mit: '$searchTerm'", "Keine Treffer", "OK", "Information")
return
}
# Bei mehreren Treffern: Auswahl anbieten
if ($matchingGroups.Count -gt 1) {
$selection = Show-DepartmentSelectionDialog -Departments $matchingGroups -SearchTerm $searchTerm
if ($selection -eq $null) { return }
$selectedGroup = $selection
}
else {
$selectedGroup = $matchingGroups[0]
}
# Lade alle Benutzer aus der ausgewählten Gruppe (rekursiv)
$pbProgress.Visibility = "Visible"
$txtProgress.Text = "Lade Mitglieder aus $($selectedGroup.DisplayName)..."
Update-UIForced
$allUsers = Get-AllGroupMembersRecursive -GroupId $selectedGroup.Id
$newUsers = New-Object System.Collections.ObjectModel.ObservableCollection[IntuneUserItem]
foreach ($user in $allUsers) {
$userItem = New-Object IntuneUserItem
$userItem.IsSelected = $false
$userItem.Id = $user.id
$userItem.DisplayName = $user.displayName
$userItem.UserPrincipalName = $user.userPrincipalName
$newUsers.Add($userItem)
}
$sortedUsers = $newUsers | Sort-Object DisplayName
$finalUsers = New-Object System.Collections.ObjectModel.ObservableCollection[IntuneUserItem]
foreach ($user in $sortedUsers) { $finalUsers.Add($user) }
$script:users = @($finalUsers)
$dgUsers.ItemsSource = $finalUsers
$txtUserCount.Text = "$($finalUsers.Count) Benutzer aus '$($selectedGroup.DisplayName)' (inkl. Untergruppen)"
# Andere Suchfelder zurücksetzen
$txtUserSearch.Text = $txtUserSearch.Tag
$cmbDepartmentFilter.SelectedIndex = 0
# WICHTIG: App-Checkboxen zurücksetzen bei neuer Abteilungssuche
$script:userModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# NACH der Suche: Target-Auswahlen aus Session wiederherstellen
Restore-SelectionsFromSession
Update-Summary
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler beim Suchen der Abteilung: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
$pbProgress.Visibility = "Collapsed"
$txtProgress.Text = ""
}
})
$btnClearDeptSearch.Add_Click({
# VOR dem Löschen: Aktuelle Auswahlen speichern
Save-CurrentSelectionsToSession
# Suchfeld zurücksetzen
$txtDeptSearch.Text = $txtDeptSearch.Tag
# Benutzer-Liste leeren
$script:users = @()
$dgUsers.ItemsSource = $null
$txtUserCount.Text = "0 Benutzer geladen"
# ComboBox zurücksetzen
if ($cmbDepartmentFilter.Items.Count -gt 0) {
$cmbDepartmentFilter.SelectedIndex = 0
}
# App-Checkboxen zurücksetzen
$script:userModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
Update-Summary
})
$cmbDepartmentFilter.Add_SelectionChanged({
if (-not $script:connected) { return }
$window.Dispatcher.BeginInvoke([action]{
try {
$selectedDept = $cmbDepartmentFilter.SelectedItem.ToString()
if ($selectedDept -eq "-- Abteilung wählen --") {
$dgUsers.ItemsSource = $null
$txtUserCount.Text = "0 Benutzer geladen"
return
}
# VOR dem Laden: Aktuelle Auswahlen in Session speichern
Save-CurrentSelectionsToSession
$group = $script:groups | Where-Object { $_.DisplayName -eq $selectedDept } | Select-Object -First 1
if (-not $group) { return }
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$allUsers = Get-AllGroupMembersRecursive -GroupId $group.Id
$newUsers = New-Object System.Collections.ObjectModel.ObservableCollection[IntuneUserItem]
foreach ($user in $allUsers) {
$userItem = New-Object IntuneUserItem
$userItem.IsSelected = $false
$userItem.Id = $user.id
$userItem.DisplayName = $user.displayName
$userItem.UserPrincipalName = $user.userPrincipalName
$newUsers.Add($userItem)
}
$sortedUsers = $newUsers | Sort-Object DisplayName
$finalUsers = New-Object System.Collections.ObjectModel.ObservableCollection[IntuneUserItem]
foreach ($user in $sortedUsers) { $finalUsers.Add($user) }
$script:users = @($finalUsers)
$dgUsers.ItemsSource = $finalUsers
$txtUserCount.Text = "$($finalUsers.Count) Benutzer (inkl. Untergruppen)"
# WICHTIG: App-Checkboxen zurücksetzen bei neuer Abteilungsauswahl
$script:userModeAppSelections = @{}
if ($dgApps.ItemsSource) {
foreach ($app in $dgApps.ItemsSource) {
if ($null -ne $app) {
$app.UseAvailable = $false
$app.UseRequired = $false
}
}
$dgApps.Items.Refresh()
}
# NACH dem Laden: Target-Auswahlen aus Session wiederherstellen
Restore-SelectionsFromSession
Update-Summary
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
}, [System.Windows.Threading.DispatcherPriority]::Background)
})
# Platzhalter-Text für Benutzersuche
$txtUserSearch.Add_GotFocus({
if ($txtUserSearch.Text -eq $txtUserSearch.Tag) {
$txtUserSearch.Text = ""
}
})
$txtUserSearch.Add_LostFocus({
if ([string]::IsNullOrWhiteSpace($txtUserSearch.Text)) {
$txtUserSearch.Text = $txtUserSearch.Tag
}
})
$txtUserSearch.Add_KeyDown({
if ($_.Key -eq 'Return') {
$btnSearchUsers.RaiseEvent([System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent))
}
})
# Platzhalter-Text für Abteilungssuche (im Einzelbenutzer-Tab)
$txtDeptSearch.Add_GotFocus({
if ($txtDeptSearch.Text -eq $txtDeptSearch.Tag) {
$txtDeptSearch.Text = ""
}
})
$txtDeptSearch.Add_LostFocus({
if ([string]::IsNullOrWhiteSpace($txtDeptSearch.Text)) {
$txtDeptSearch.Text = $txtDeptSearch.Tag
}
})
$txtDeptSearch.Add_KeyDown({
if ($_.Key -eq 'Return') {
$btnSearchDept.RaiseEvent([System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent))
}
})
# Platzhalter-Text für Abteilungssuche
$txtGroupSearch.Add_GotFocus({
if ($txtGroupSearch.Text -eq $txtGroupSearch.Tag) {
$txtGroupSearch.Text = ""
}
})
$txtGroupSearch.Add_LostFocus({
if ([string]::IsNullOrWhiteSpace($txtGroupSearch.Text)) {
$txtGroupSearch.Text = $txtGroupSearch.Tag
}
})
# Abteilungssuche Event Handler mit Debouncing
$script:groupSearchTimer = New-Object System.Windows.Threading.DispatcherTimer
$script:groupSearchTimer.Interval = [TimeSpan]::FromMilliseconds(300)
$script:groupSearchTimer.Add_Tick({
$script:groupSearchTimer.Stop()
if ($script:allGroups.Count -gt 0) {
Set-GroupFilter
}
})
$txtGroupSearch.Add_TextChanged({
# Bei jeder Änderung Timer neu starten (Debouncing)
$script:groupSearchTimer.Stop()
$script:groupSearchTimer.Start()
})
$btnClearGroupSearch.Add_Click({
$txtGroupSearch.Text = $txtGroupSearch.Tag
if ($script:allGroups.Count -gt 0) {
Set-GroupFilter
}
})
# Platzhalter-Text für App-Suche
$txtAppSearch.Add_GotFocus({
if ($txtAppSearch.Text -eq $txtAppSearch.Tag) {
$txtAppSearch.Text = ""
}
})
$txtAppSearch.Add_LostFocus({
if ([string]::IsNullOrWhiteSpace($txtAppSearch.Text)) {
$txtAppSearch.Text = $txtAppSearch.Tag
}
})
# App-Suche Event Handler mit Debouncing
$script:searchTimer = New-Object System.Windows.Threading.DispatcherTimer
$script:searchTimer.Interval = [TimeSpan]::FromMilliseconds(300)
$script:searchTimer.Add_Tick({
$script:searchTimer.Stop()
if ($script:allApps.Count -gt 0) {
Set-AppFilter
}
})
$txtAppSearch.Add_TextChanged({
# Bei jeder Änderung Timer neu starten (Debouncing)
$script:searchTimer.Stop()
$script:searchTimer.Start()
})
$btnClearAppSearch.Add_Click({
$txtAppSearch.Text = $txtAppSearch.Tag
if ($script:allApps.Count -gt 0) {
Set-AppFilter
}
})
$btnClearSession.Add_Click({
$result = [System.Windows.Forms.MessageBox]::Show(
"Möchten Sie wirklich alle Session-Zuweisungen löschen?`n`nDies setzt alle Checkboxen zurück.",
"Session löschen",
"YesNo",
"Question"
)
if ($result -eq "Yes") {
Clear-AllSessionAssignments
}
})
$cmbAppFilter.Add_SelectionChanged({
$selected = $cmbAppFilter.SelectedItem
if ($selected -eq $null) { return }
$selectedText = $selected.ToString()
if ($selectedText.StartsWith("---")) {
$cmbAppFilter.SelectedIndex = 0
return
}
$script:currentFilter = $selectedText
if ($script:allApps.Count -gt 0) {
Set-AppFilter
}
# Aktiven Filter anzeigen
if ($selectedText -ne "Alle Apps") {
$txtActiveFilter.Text = "Aktiv: $selectedText"
$txtActiveFilter.Visibility = "Visible"
$btnClearFilter.Visibility = "Visible"
} else {
$txtActiveFilter.Visibility = "Collapsed"
$btnClearFilter.Visibility = "Collapsed"
}
})
$btnClearFilter.Add_Click({
$cmbAppFilter.SelectedIndex = 0
$txtActiveFilter.Visibility = "Collapsed"
$btnClearFilter.Visibility = "Collapsed"
})
$dgApps.AddHandler([System.Windows.Controls.Button]::ClickEvent, [System.Windows.RoutedEventHandler]{
param($eventSender, $e)
if ($e.OriginalSource.Name -eq "btnCreateRequiredGroup") {
$appItem = $e.OriginalSource.Tag
if ($null -ne $appItem) {
Show-CreateGroupDialog -AppItem $appItem
}
}
})
$dgGroups.Add_CellEditEnding({
$window.Dispatcher.BeginInvoke({
Save-CurrentSelectionsToSession
Update-Summary
}, [System.Windows.Threading.DispatcherPriority]::Background)
})
$dgUsers.Add_CellEditEnding({
$window.Dispatcher.BeginInvoke({
Save-CurrentSelectionsToSession
Update-Summary
}, [System.Windows.Threading.DispatcherPriority]::Background)
})
$dgRpaGroups.Add_CellEditEnding({
$window.Dispatcher.BeginInvoke({
Save-CurrentSelectionsToSession
Update-Summary
}, [System.Windows.Threading.DispatcherPriority]::Background)
})
$dgApps.Add_CellEditEnding({
$window.Dispatcher.BeginInvoke({
Save-CurrentSelectionsToSession
Update-Summary
}, [System.Windows.Threading.DispatcherPriority]::Background)
})
$tabMain.Add_SelectionChanged({
# Prüfen ob sich der Tab wirklich geändert hat
$newTabIndex = $tabMain.SelectedIndex
if ($newTabIndex -eq $script:lastTabIndex) { return }
# Beim Tab-Wechsel: Aktuelle Auswahlen in Session speichern
Save-CurrentSelectionsToSession
# App-Auswahl für den ALTEN Tab speichern
Save-AppSelectionsForCurrentMode
# Tab-Index aktualisieren
$script:lastTabIndex = $newTabIndex
# App-Auswahl für den NEUEN Tab wiederherstellen
Restore-AppSelectionsForMode -TabIndex $newTabIndex
# Target-Auswahlen für den neuen Tab wiederherstellen
Restore-SelectionsFromSession
Update-Summary
})
$btnApply.Add_Click({
# Zuerst aktuelle UI-Auswahlen in Session speichern
Save-CurrentSelectionsToSession
# Prüfen ob Session-Zuweisungen vorhanden sind
if ($script:sessionAssignments.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show(
"Keine Zuweisungen vorhanden.`n`nBitte wählen Sie mindestens eine Gruppe/Benutzer und eine App-Zuweisung aus.",
"Keine Zuweisungen",
"OK",
"Warning"
)
return
}
# Session-Zuweisungen in das erwartete Format konvertieren
$targetsByType = @{
"Group" = @{}
"User" = @{}
}
foreach ($key in $script:sessionAssignments.Keys) {
$assignment = $script:sessionAssignments[$key]
$targetType = $assignment.TargetType
$targetId = $assignment.TargetId
if (-not $targetsByType[$targetType].ContainsKey($targetId)) {
$targetsByType[$targetType][$targetId] = @{
Id = $targetId
DisplayName = $assignment.TargetName
Assignments = @()
}
}
$targetsByType[$targetType][$targetId].Assignments += @{
AppName = $assignment.AppName
GroupId = $assignment.GroupId
GroupName = $assignment.GroupName
Type = $assignment.AssignmentType
}
}
# Alle Targets mit ihren spezifischen Zuweisungen sammeln
$allTargetsWithAssignments = @()
$targetTypeLabel = ""
$totalAssignments = 0
# Gruppen-Zuweisungen (jede Gruppe mit ihren eigenen Zuweisungen)
foreach ($targetId in $targetsByType["Group"].Keys) {
$target = $targetsByType["Group"][$targetId]
$allTargetsWithAssignments += @{
Id = $target.Id
DisplayName = $target.DisplayName
Type = "Group"
Assignments = $target.Assignments
}
$totalAssignments += $target.Assignments.Count
}
if ($targetsByType["Group"].Count -gt 0) { $targetTypeLabel = "Gruppen" }
# Benutzer-Zuweisungen (jeder Benutzer mit seinen eigenen Zuweisungen)
foreach ($targetId in $targetsByType["User"].Keys) {
$target = $targetsByType["User"][$targetId]
$allTargetsWithAssignments += @{
Id = $target.Id
DisplayName = $target.DisplayName
Type = "User"
Assignments = $target.Assignments
}
$totalAssignments += $target.Assignments.Count
}
if ($targetsByType["User"].Count -gt 0) {
if ($targetTypeLabel) { $targetTypeLabel += " & Benutzer" }
else { $targetTypeLabel = "Benutzer" }
}
# Zeige Review-Dialog mit target-spezifischen Zuweisungen
$confirmed = Show-AssignmentReviewDialog -TargetsWithAssignments $allTargetsWithAssignments -TargetType $targetTypeLabel -TotalAssignments $totalAssignments
# Nur ausführen wenn EXPLIZIT bestätigt wurde
if ($confirmed -eq $true) {
# Ausführung mit Session-Zuweisungen
Invoke-SessionAssignments
}
})
<#$btnApply.Add_Click({
Write-Host "`n========== DEBUG: btnApply Click Start ==========" -ForegroundColor Magenta
$selectedTargets = @()
$targetType = ""
if ($tabMain.SelectedIndex -eq 0) {
foreach ($item in $dgGroups.Items) {
if ($item.IsSelected -eq $true) {
$selectedTargets += $item
}
}
$targetType = "Abteilungsgruppen"
}
else {
foreach ($item in $dgUsers.Items) {
if ($item.IsSelected -eq $true) {
$selectedTargets += $item
}
}
$targetType = "Benutzer"
}
Write-Host "DEBUG: Targets = $($selectedTargets.Count), Type = $targetType" -ForegroundColor Yellow
$assignments = @()
foreach ($item in $dgApps.Items) {
if ($item.UseAvailable -eq $true -and $item.SelectedAvailableGroup -ne $null -and $item.SelectedAvailableGroup.GroupId -ne "") {
$assignments += @{
AppName = $item.AppName
GroupId = $item.SelectedAvailableGroup.GroupId
GroupName = $item.SelectedAvailableGroup.DisplayName
Type = "Available"
}
}
if ($item.UseRequired -eq $true -and $item.SelectedRequiredGroup -ne $null -and $item.SelectedRequiredGroup.GroupId -ne "") {
$assignments += @{
AppName = $item.AppName
GroupId = $item.SelectedRequiredGroup.GroupId
GroupName = $item.SelectedRequiredGroup.DisplayName
Type = "Required"
}
}
}
Write-Host "DEBUG: Assignments = $($assignments.Count)" -ForegroundColor Yellow
Write-Host "DEBUG: Rufe Show-AssignmentReviewDialog auf..." -ForegroundColor Yellow
# Zeige Review-Dialog
$confirmed = Show-AssignmentReviewDialog -Targets $selectedTargets -Assignments $assignments -TargetType $targetType
Write-Host "DEBUG: confirmed = $confirmed (Type: $($confirmed.GetType().Name))" -ForegroundColor Cyan
Write-Host "DEBUG: Teste: confirmed -eq `$true = $($confirmed -eq $true)" -ForegroundColor Cyan
# Nur ausführen wenn EXPLIZIT bestätigt wurde
if ($confirmed -eq $true) {
Write-Host "DEBUG: IF-Bedingung ist TRUE - führe Invoke-Assignments aus!" -ForegroundColor Green
Invoke-Assignments -Targets $selectedTargets -Assignments $assignments -IsUserMode ($tabMain.SelectedIndex -eq 1)
}
else {
Write-Host "DEBUG: IF-Bedingung ist FALSE - KEINE Ausführung!" -ForegroundColor Red
}
Write-Host "========== DEBUG: btnApply Click Ende ==========`n" -ForegroundColor Magenta
})#>
# ===== GRUPPENABFRAGE: Event Handler =====
$btnGroupQuerySearch.Add_Click({
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
$searchTerm = $txtGroupQuerySearch.Text.Trim()
if ([string]::IsNullOrWhiteSpace($searchTerm) -or $searchTerm -eq $txtGroupQuerySearch.Tag) {
[System.Windows.Forms.MessageBox]::Show("Bitte einen Suchbegriff eingeben.", "Hinweis", "OK", "Information")
return
}
try {
$window.Cursor = [System.Windows.Input.Cursors]::Wait
$found = Find-GraphGroupsByName -SearchTerm $searchTerm
$script:groupQueryResults = @()
foreach ($g in $found) {
$script:groupQueryResults += [PSCustomObject]@{
DisplayName = $g.displayName
Id = $g.id
}
}
$script:groupQueryResults = @($script:groupQueryResults | Sort-Object DisplayName)
$dgGroupQueryResults.ItemsSource = $null
$dgGroupQueryResults.ItemsSource = $script:groupQueryResults
$txtGroupQueryCount.Text = "$($script:groupQueryResults.Count) Gruppe(n) gefunden"
}
catch {
[System.Windows.Forms.MessageBox]::Show("Fehler bei der Gruppensuche: $_", "Fehler", "OK", "Error")
}
finally {
$window.Cursor = [System.Windows.Input.Cursors]::Arrow
}
})
$dgGroupQueryResults.Add_MouseDoubleClick({
$selected = $dgGroupQueryResults.SelectedItem
if (-not $selected) { return }
if (-not $script:connected) {
[System.Windows.Forms.MessageBox]::Show("Bitte zuerst mit Graph verbinden!", "Nicht verbunden", "OK", "Warning")
return
}
Show-GroupMembersDialog -GroupId $selected.Id -GroupName $selected.DisplayName -ResolveNested ($chkResolveNestedGroups.IsChecked -eq $true)
})
# Platzhalter-Text für Gruppenabfrage-Suche
$txtGroupQuerySearch.Add_GotFocus({
if ($txtGroupQuerySearch.Text -eq $txtGroupQuerySearch.Tag) {
$txtGroupQuerySearch.Text = ""
}
})
$txtGroupQuerySearch.Add_LostFocus({
if ([string]::IsNullOrWhiteSpace($txtGroupQuerySearch.Text)) {
$txtGroupQuerySearch.Text = $txtGroupQuerySearch.Tag
}
})
$txtGroupQuerySearch.Add_KeyDown({
if ($_.Key -eq 'Return') {
$btnGroupQuerySearch.RaiseEvent([System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent))
}
})
# Update-Timer für Summary und Session-Synchronisation
$script:updateTimer = New-Object System.Windows.Threading.DispatcherTimer
$script:updateTimer.Interval = [TimeSpan]::FromMilliseconds(300)
$script:updateTimer.Add_Tick({
Save-CurrentSelectionsToSession
Update-Summary
})
$window.Add_Loaded({ $script:updateTimer.Start() })
# ===== INITIALISIERUNG =====
# Setze Platzhalter-Text NACH der Registrierung der Event-Handler
$txtUserSearch.Text = $txtUserSearch.Tag
$txtDeptSearch.Text = $txtDeptSearch.Tag
$txtGroupSearch.Text = $txtGroupSearch.Tag
$txtAppSearch.Text = $txtAppSearch.Tag
$txtGroupQuerySearch.Text = $txtGroupQuerySearch.Tag
# Initialisiere Filter
Initialize-AppFilters
# Initialisiere Session-Panel
Update-SessionPanel
Write-Host "Starte Intune App-Zuweisungs-Manager v5.2.0..."
$window.ShowDialog() | Out-Null
# Cleanup
if ($script:updateTimer) { $script:updateTimer.Stop() }
if ($script:searchTimer) { $script:searchTimer.Stop() }
if ($script:groupSearchTimer) { $script:groupSearchTimer.Stop() }
if ($script:connected) { Disconnect-MgGraph }