4019 lines
174 KiB
PowerShell
4019 lines
174 KiB
PowerShell
# 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) {
|
||
$uri = "https://graph.microsoft.com/v1.0/groups/$GroupId`?`$select=" + ($Property -join ",").ToLower()
|
||
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 = @"
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Intune Zuweisungs-Report - $dateForFile</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f5f5; padding: 20px; }
|
||
.container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; }
|
||
.header { background: linear-gradient(135deg, #0078d4, #106ebe); color: white; padding: 30px; }
|
||
.header h1 { font-size: 24px; margin-bottom: 5px; }
|
||
.header .subtitle { opacity: 0.9; font-size: 14px; }
|
||
.meta-info { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; padding: 20px; background: #f8f9fa; border-bottom: 1px solid #e9ecef; }
|
||
.meta-item { display: flex; flex-direction: column; }
|
||
.meta-label { font-size: 12px; color: #6c757d; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
.meta-value { font-size: 16px; font-weight: 600; color: #212529; margin-top: 4px; }
|
||
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; padding: 20px; }
|
||
.stat-card { padding: 20px; border-radius: 8px; text-align: center; }
|
||
.stat-card.total { background: #e3f2fd; color: #1565c0; }
|
||
.stat-card.success { background: #e8f5e9; color: #2e7d32; }
|
||
.stat-card.skipped { background: #fff3e0; color: #ef6c00; }
|
||
.stat-card.error { background: #ffebee; color: #c62828; }
|
||
.stat-number { font-size: 32px; font-weight: 700; }
|
||
.stat-label { font-size: 12px; text-transform: uppercase; margin-top: 5px; opacity: 0.8; }
|
||
.section { padding: 20px; border-top: 1px solid #e9ecef; }
|
||
.section-title { font-size: 18px; font-weight: 600; color: #212529; margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
|
||
.section-title .badge { font-size: 12px; padding: 4px 10px; border-radius: 12px; font-weight: 500; }
|
||
.badge-available { background: #e3f2fd; color: #1565c0; }
|
||
.badge-required { background: #fce4ec; color: #c2185b; }
|
||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e9ecef; }
|
||
th { background: #f8f9fa; font-weight: 600; color: #495057; font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||
tr:hover { background: #f8f9fa; }
|
||
.app-name { font-weight: 500; color: #212529; }
|
||
.group-name { color: #6c757d; font-size: 14px; }
|
||
.type-badge { display: inline-block; padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; }
|
||
.type-available { background: #e3f2fd; color: #1565c0; }
|
||
.type-required { background: #fce4ec; color: #c2185b; }
|
||
.targets-list { display: flex; flex-wrap: wrap; gap: 8px; }
|
||
.target-chip { background: #e9ecef; padding: 6px 12px; border-radius: 16px; font-size: 13px; color: #495057; }
|
||
.footer { padding: 20px; background: #f8f9fa; text-align: center; color: #6c757d; font-size: 12px; border-top: 1px solid #e9ecef; }
|
||
.no-data { color: #6c757d; font-style: italic; padding: 20px; text-align: center; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>Intune App-Zuweisungs-Report</h1>
|
||
<div class="subtitle">HanseMerkur | Automatisierte App-Zuweisung</div>
|
||
</div>
|
||
|
||
<div class="meta-info">
|
||
<div class="meta-item">
|
||
<span class="meta-label">Datum & Uhrzeit</span>
|
||
<span class="meta-value">$timestamp</span>
|
||
</div>
|
||
<div class="meta-item">
|
||
<span class="meta-label">Ausgefuhrt von</span>
|
||
<span class="meta-value">$executedBy</span>
|
||
</div>
|
||
<div class="meta-item">
|
||
<span class="meta-label">Ziel-Typ</span>
|
||
<span class="meta-value">$targetType</span>
|
||
</div>
|
||
<div class="meta-item">
|
||
<span class="meta-label">Anzahl Ziele</span>
|
||
<span class="meta-value">$($Targets.Count)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat-card total">
|
||
<div class="stat-number">$totalOps</div>
|
||
<div class="stat-label">Gesamt</div>
|
||
</div>
|
||
<div class="stat-card success">
|
||
<div class="stat-number">$SuccessCount</div>
|
||
<div class="stat-label">Erfolgreich</div>
|
||
</div>
|
||
<div class="stat-card skipped">
|
||
<div class="stat-number">$SkippedCount</div>
|
||
<div class="stat-label">Ubersprungen</div>
|
||
</div>
|
||
<div class="stat-card error">
|
||
<div class="stat-number">$ErrorCount</div>
|
||
<div class="stat-label">Fehler</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">Zugewiesene $targetType</div>
|
||
<div class="targets-list">
|
||
"@
|
||
|
||
foreach ($target in $Targets) {
|
||
$html += " <span class=`"target-chip`">$($target.DisplayName)</span>`n"
|
||
}
|
||
|
||
$html += @"
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">
|
||
Available-Zuweisungen
|
||
<span class="badge badge-available">$($availableApps.Count) Apps</span>
|
||
</div>
|
||
"@
|
||
|
||
if ($availableApps.Count -gt 0) {
|
||
$html += @"
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>App-Name</th>
|
||
<th>Ziel-Gruppe</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
"@
|
||
foreach ($app in $availableApps) {
|
||
$html += " <tr><td class=`"app-name`">$($app.AppName)</td><td class=`"group-name`">$($app.GroupName)</td></tr>`n"
|
||
}
|
||
$html += " </tbody></table>`n"
|
||
} else {
|
||
$html += " <div class=`"no-data`">Keine Available-Zuweisungen</div>`n"
|
||
}
|
||
|
||
$html += @"
|
||
</div>
|
||
|
||
<div class="section">
|
||
<div class="section-title">
|
||
Required-Zuweisungen
|
||
<span class="badge badge-required">$($requiredApps.Count) Apps</span>
|
||
</div>
|
||
"@
|
||
|
||
if ($requiredApps.Count -gt 0) {
|
||
$html += @"
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>App-Name</th>
|
||
<th>Ziel-Gruppe</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
"@
|
||
foreach ($app in $requiredApps) {
|
||
$html += " <tr><td class=`"app-name`">$($app.AppName)</td><td class=`"group-name`">$($app.GroupName)</td></tr>`n"
|
||
}
|
||
$html += " </tbody></table>`n"
|
||
} else {
|
||
$html += " <div class=`"no-data`">Keine Required-Zuweisungen</div>`n"
|
||
}
|
||
|
||
$html += @"
|
||
</div>
|
||
|
||
<div class="footer">
|
||
Report erstellt von Intune App Manager v5.2.0 | WendeIT Marco Wende
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</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 = @"
|
||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||
Title="Intune App Manager (v5.2.0 by WendeIT - Marco Wende)" Height="750" Width="1650">
|
||
<Window.Resources>
|
||
<!-- Checkbox Style für Zentrierung -->
|
||
<Style x:Key="CenteredCheckBox" TargetType="CheckBox">
|
||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||
</Style>
|
||
</Window.Resources>
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="50"/>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="80"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<Border Grid.Row="0" Background="#0da075" Padding="10">
|
||
<Grid>
|
||
<TextBlock Text="HanseMerkur | Intune App Manager v5.2.0" Foreground="White" FontSize="18"
|
||
VerticalAlignment="Center" FontWeight="Bold"/>
|
||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||
<TextBlock Name="txtStatus" Text="Nicht verbunden" Foreground="White"
|
||
VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||
<Button Name="btnConnect" Content="Mit Graph verbinden" Width="150" Height="30"/>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Border>
|
||
|
||
<Grid Grid.Row="1">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="380"/>
|
||
<ColumnDefinition Width="5"/>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="5"/>
|
||
<ColumnDefinition Width="320"/>
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<TabControl Grid.Column="0" Margin="10" Name="tabMain">
|
||
<TabItem Header="Abteilungsgruppen">
|
||
<DockPanel Margin="5">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||
<TextBlock Text="Abteilungsgruppen (abt-hm-*)" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Name="txtGroupSearch" Height="25" Grid.Column="0"
|
||
Tag="Abteilung suchen..." Margin="0,0,5,0"/>
|
||
<Button Name="btnClearGroupSearch" Content="✕" Width="25" Height="25"
|
||
Grid.Column="1" ToolTip="Suche zurücksetzen" Margin="0,0,5,0"/>
|
||
</Grid>
|
||
<Button Name="btnLoadGroups" Content="Gruppen laden" Height="25" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtGroupCount" Text="0 Gruppen geladen" FontSize="10" Foreground="Gray"/>
|
||
</StackPanel>
|
||
<DataGrid Name="dgGroups" AutoGenerateColumns="False" CanUserAddRows="False"
|
||
VirtualizingStackPanel.IsVirtualizing="True"
|
||
VirtualizingStackPanel.VirtualizationMode="Recycling"
|
||
EnableRowVirtualization="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTemplateColumn Header="✓" Width="30">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||
Style="{StaticResource CenteredCheckBox}"/>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTextColumn Header="Abteilung" Binding="{Binding DisplayName}" Width="*"/>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
</DockPanel>
|
||
</TabItem>
|
||
|
||
<TabItem Header="Einzelbenutzer">
|
||
<DockPanel Margin="5">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||
<TextBlock Text="Benutzersuche" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Name="txtUserSearch" Height="25" Grid.Column="0"
|
||
Tag="Name oder E-Mail eingeben..." Margin="0,0,5,0"/>
|
||
<Button Name="btnClearUserSearch" Content="✕" Width="25" Height="25"
|
||
Grid.Column="1" ToolTip="Suche zurücksetzen" Margin="0,0,5,0"/>
|
||
<Button Name="btnSearchUsers" Content="Suchen" Width="80" Height="25" Grid.Column="2"/>
|
||
</Grid>
|
||
|
||
<TextBlock Text="Abteilungssuche" FontWeight="Bold" FontSize="12" Margin="0,10,0,5"/>
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Name="txtDeptSearch" Height="25" Grid.Column="0"
|
||
Tag="Abteilung suchen (z.B. IT, Vertrieb)..." Margin="0,0,5,0"/>
|
||
<Button Name="btnClearDeptSearch" Content="✕" Width="25" Height="25"
|
||
Grid.Column="1" ToolTip="Suche zurücksetzen" Margin="0,0,5,0"/>
|
||
<Button Name="btnSearchDept" Content="Suchen" Width="80" Height="25" Grid.Column="2"/>
|
||
</Grid>
|
||
|
||
<TextBlock Text="Oder aus Liste wählen:" FontSize="11" Margin="0,5,0,3"/>
|
||
<ComboBox Name="cmbDepartmentFilter" Height="25" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtUserCount" Text="0 Benutzer geladen" FontSize="10" Foreground="Gray"/>
|
||
</StackPanel>
|
||
<DataGrid Name="dgUsers" AutoGenerateColumns="False" CanUserAddRows="False"
|
||
VirtualizingStackPanel.IsVirtualizing="True"
|
||
VirtualizingStackPanel.VirtualizationMode="Recycling"
|
||
EnableRowVirtualization="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTemplateColumn Header="✓" Width="30">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||
Style="{StaticResource CenteredCheckBox}"/>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTextColumn Header="Name" Binding="{Binding DisplayName}" Width="*"/>
|
||
<DataGridTextColumn Header="E-Mail" Binding="{Binding UserPrincipalName}" Width="200"/>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
</DockPanel>
|
||
</TabItem>
|
||
|
||
<TabItem Header="RPA">
|
||
<DockPanel Margin="5">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||
<TextBlock Text="RPA-Gruppen" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
|
||
<TextBlock Text="Wählen Sie RPA-Benutzergruppen für App-Zuweisungen" FontSize="11" Foreground="Gray" Margin="0,0,0,5"/>
|
||
<Button Name="btnLoadRpaGroups" Content="RPA-Gruppen laden" Height="25" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtRpaGroupCount" Text="0 RPA-Gruppen geladen" FontSize="10" Foreground="Gray"/>
|
||
</StackPanel>
|
||
<DataGrid Name="dgRpaGroups" AutoGenerateColumns="False" CanUserAddRows="False"
|
||
VirtualizingStackPanel.IsVirtualizing="True"
|
||
VirtualizingStackPanel.VirtualizationMode="Recycling"
|
||
EnableRowVirtualization="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTemplateColumn Header="✓" Width="30">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||
Style="{StaticResource CenteredCheckBox}"/>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTextColumn Header="Gruppenname" Binding="{Binding DisplayName}" Width="*"/>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
</DockPanel>
|
||
</TabItem>
|
||
|
||
<TabItem Header="Gruppenabfrage">
|
||
<DockPanel Margin="5">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||
<TextBlock Text="Beliebige Gruppe suchen" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
|
||
<TextBlock Text="Sucht über alle Gruppen im Tenant (nicht nur abt-hm-* / RPA)" FontSize="10" Foreground="Gray" Margin="0,0,0,5"/>
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Name="txtGroupQuerySearch" Height="25" Grid.Column="0"
|
||
Tag="Gruppenname (Teilstring)..." Margin="0,0,5,0"/>
|
||
<Button Name="btnGroupQuerySearch" Content="Suchen" Width="70" Height="25" Grid.Column="1"/>
|
||
</Grid>
|
||
<CheckBox Name="chkResolveNestedGroups" Content="Verschachtelte Gruppen auflösen" Margin="0,3,0,0"/>
|
||
<TextBlock Text="💡 Doppelklick auf eine Gruppe zeigt deren Mitglieder" FontSize="10" Foreground="Gray" Margin="0,8,0,0"/>
|
||
<TextBlock Name="txtGroupQueryCount" Text="0 Gruppen gefunden" FontSize="10" Foreground="Gray" Margin="0,3,0,0"/>
|
||
</StackPanel>
|
||
<DataGrid Name="dgGroupQueryResults" AutoGenerateColumns="False" CanUserAddRows="False"
|
||
SelectionMode="Single" SelectionUnit="FullRow"
|
||
VirtualizingStackPanel.IsVirtualizing="True"
|
||
VirtualizingStackPanel.VirtualizationMode="Recycling"
|
||
EnableRowVirtualization="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTextColumn Header="Gruppenname" Binding="{Binding DisplayName}" Width="*"/>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
</DockPanel>
|
||
</TabItem>
|
||
</TabControl>
|
||
|
||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" Background="LightGray"/>
|
||
|
||
<DockPanel Grid.Column="2" Margin="10">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,10">
|
||
<TextBlock Text="Verfügbare Apps" FontWeight="Bold" FontSize="14" Margin="0,0,0,5"/>
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Name="txtAppSearch" Height="25" Grid.Column="0"
|
||
Tag="App-Name suchen..." Margin="0,0,5,0"/>
|
||
<Button Name="btnClearAppSearch" Content="✕" Width="25" Height="25"
|
||
Grid.Column="1" ToolTip="Suche zurücksetzen" Margin="0,0,5,0"/>
|
||
</Grid>
|
||
<!-- Buttons nebeneinander -->
|
||
<Grid Margin="0,0,0,5">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="*"/>
|
||
</Grid.ColumnDefinitions>
|
||
<Button Name="btnLoadApps" Content="Apps laden" Height="25" Grid.Column="0" Margin="0,0,3,0"/>
|
||
<Button Name="btnCheckMembership" Content="🔍 Prüfen" Height="25" Grid.Column="1" Margin="3,0,0,0"
|
||
ToolTip="Prüft ob ausgewählte Gruppen/Benutzer bereits Mitglied der App-Zuweisungsgruppen sind"/>
|
||
</Grid>
|
||
<!-- Moderne Filter-Ansicht -->
|
||
<Border Name="filterPanel" BorderBrush="#E0E0E0" BorderThickness="1" CornerRadius="3" Padding="5" Margin="0,0,0,5" Background="#FAFAFA">
|
||
<StackPanel>
|
||
<TextBlock Text="Filter:" FontSize="9" FontWeight="SemiBold" Margin="0,0,0,3"/>
|
||
<WrapPanel Name="wrapFilterChips">
|
||
<ComboBox Name="cmbAppFilter" Height="22" Width="180" FontSize="10" Margin="0,0,5,3"/>
|
||
<Button Name="btnClearFilter" Content="✕ Filter zurücksetzen" Height="22" FontSize="9"
|
||
Padding="5,0" Margin="0,0,0,3" Visibility="Collapsed"
|
||
Background="#FFEBEE" Foreground="#C62828" BorderBrush="#FFCDD2"/>
|
||
</WrapPanel>
|
||
<TextBlock Name="txtActiveFilter" Text="" FontSize="9" Foreground="#1565C0"
|
||
FontStyle="Italic" Visibility="Collapsed" Margin="0,2,0,0"/>
|
||
</StackPanel>
|
||
</Border>
|
||
<TextBlock Name="txtAppCount" Text="0 Apps geladen" FontSize="10" Foreground="Gray"/>
|
||
</StackPanel>
|
||
<DataGrid Name="dgApps" AutoGenerateColumns="False" CanUserAddRows="False"
|
||
SelectionMode="Single" CanUserSortColumns="True"
|
||
VirtualizingStackPanel.IsVirtualizing="True"
|
||
VirtualizingStackPanel.VirtualizationMode="Recycling"
|
||
EnableRowVirtualization="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTextColumn Header="App Name" Binding="{Binding AppName}" Width="200" IsReadOnly="True"/>
|
||
<DataGridTemplateColumn Header="Avail." Width="50">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<CheckBox IsChecked="{Binding UseAvailable, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||
Style="{StaticResource CenteredCheckBox}"/>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTemplateColumn Header="Available Gruppe" Width="180">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<Border ToolTip="{Binding AvailableMembershipTooltip}" Padding="2">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="Transparent"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding AvailableMembershipStatus}" Value="Full">
|
||
<Setter Property="Background" Value="#C8E6C9"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding AvailableMembershipStatus}" Value="Partial">
|
||
<Setter Property="Background" Value="#FFF9C4"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding AvailableMembershipStatus}" Value="Native">
|
||
<Setter Property="Background" Value="#E3F2FD"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<Grid>
|
||
<ComboBox ItemsSource="{Binding AvailableGroups}"
|
||
SelectedItem="{Binding SelectedAvailableGroup, UpdateSourceTrigger=PropertyChanged}"
|
||
IsEnabled="{Binding UseAvailable}">
|
||
<ComboBox.Style>
|
||
<Style TargetType="ComboBox">
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding AvailableGroupsCount}" Value="0">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding AvailableGroupsCount}" Value="1">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</ComboBox.Style>
|
||
</ComboBox>
|
||
<TextBlock Text="{Binding AvailableGroupDisplayText}" VerticalAlignment="Center" Padding="3">
|
||
<TextBlock.Style>
|
||
<Style TargetType="TextBlock">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding AvailableGroupsCount}" Value="0">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
<Setter Property="Foreground" Value="Gray"/>
|
||
<Setter Property="FontStyle" Value="Italic"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding AvailableGroupsCount}" Value="1">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
<Setter Property="Foreground" Value="Black"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBlock.Style>
|
||
</TextBlock>
|
||
</Grid>
|
||
</Border>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTemplateColumn Header="Req." Width="50">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<CheckBox IsChecked="{Binding UseRequired, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||
Style="{StaticResource CenteredCheckBox}"/>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTemplateColumn Header="Required Gruppe" Width="180">
|
||
<DataGridTemplateColumn.CellTemplate>
|
||
<DataTemplate>
|
||
<Border ToolTip="{Binding RequiredMembershipTooltip}" Padding="2">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="Transparent"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding RequiredMembershipStatus}" Value="Full">
|
||
<Setter Property="Background" Value="#C8E6C9"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding RequiredMembershipStatus}" Value="Partial">
|
||
<Setter Property="Background" Value="#FFF9C4"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding RequiredMembershipStatus}" Value="Native">
|
||
<Setter Property="Background" Value="#E3F2FD"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<Grid Grid.Column="0">
|
||
<ComboBox ItemsSource="{Binding RequiredGroups}"
|
||
SelectedItem="{Binding SelectedRequiredGroup, UpdateSourceTrigger=PropertyChanged}"
|
||
IsEnabled="{Binding UseRequired}">
|
||
<ComboBox.Style>
|
||
<Style TargetType="ComboBox">
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding RequiredGroupsCount}" Value="0">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding RequiredGroupsCount}" Value="1">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</ComboBox.Style>
|
||
</ComboBox>
|
||
<TextBlock Text="{Binding RequiredGroupDisplayText}" VerticalAlignment="Center" Padding="3">
|
||
<TextBlock.Style>
|
||
<Style TargetType="TextBlock">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding RequiredGroupsCount}" Value="0">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
<Setter Property="Foreground" Value="Gray"/>
|
||
<Setter Property="FontStyle" Value="Italic"/>
|
||
</DataTrigger>
|
||
<DataTrigger Binding="{Binding RequiredGroupsCount}" Value="1">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
<Setter Property="Foreground" Value="Black"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBlock.Style>
|
||
</TextBlock>
|
||
</Grid>
|
||
<Button Name="btnCreateRequiredGroup" Content="+" Grid.Column="1"
|
||
Width="25" Height="20" Margin="2,0,0,0"
|
||
ToolTip="Required-Gruppe erstellen"
|
||
Tag="{Binding}">
|
||
<Button.Style>
|
||
<Style TargetType="Button">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding RequiredGroupsCount}" Value="0">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Button.Style>
|
||
</Button>
|
||
</Grid>
|
||
</Border>
|
||
</DataTemplate>
|
||
</DataGridTemplateColumn.CellTemplate>
|
||
</DataGridTemplateColumn>
|
||
<DataGridTextColumn Header="Status" Binding="{Binding StatusInfo}" Width="80" IsReadOnly="True">
|
||
<DataGridTextColumn.CellStyle>
|
||
<Style TargetType="DataGridCell">
|
||
<Setter Property="ToolTip" Value="{Binding StatusDetails}"/>
|
||
</Style>
|
||
</DataGridTextColumn.CellStyle>
|
||
</DataGridTextColumn>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
</DockPanel>
|
||
|
||
<GridSplitter Grid.Column="3" Width="5" HorizontalAlignment="Stretch" Background="LightGray"/>
|
||
|
||
<!-- Session-Übersicht Panel (horizontal geteilt) -->
|
||
<Grid Grid.Column="4" Margin="10">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="5"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- Obere Hälfte: Session-Zuweisungen -->
|
||
<DockPanel Grid.Row="0">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,5">
|
||
<TextBlock Text="Session-Zuweisungen" FontWeight="Bold" FontSize="13" Margin="0,0,0,3"/>
|
||
<TextBlock Name="txtSessionCount" Text="0 Zuweisungen in dieser Session" FontSize="9" Foreground="Gray" Margin="0,0,0,3"/>
|
||
<Button Name="btnClearSession" Content="Session löschen" Height="22" FontSize="10"
|
||
Background="#ffcccc" ToolTip="Entfernt alle Zuweisungen aus der aktuellen Session"/>
|
||
</StackPanel>
|
||
<Border BorderBrush="LightGray" BorderThickness="1" CornerRadius="3">
|
||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||
<StackPanel Name="pnlSessionAssignments" Margin="5"/>
|
||
</ScrollViewer>
|
||
</Border>
|
||
</DockPanel>
|
||
|
||
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" Background="LightGray"/>
|
||
|
||
<!-- Untere Hälfte: Bestehende App-Zuweisungen -->
|
||
<DockPanel Grid.Row="2">
|
||
<StackPanel DockPanel.Dock="Top" Margin="0,5,0,5">
|
||
<TextBlock Text="Bestehende Zuweisungen" FontWeight="Bold" FontSize="13" Margin="0,0,0,3"/>
|
||
<TextBlock Name="txtExistingAssignmentsInfo" Text="Wählen Sie Gruppen/Benutzer und klicken Sie 'Mitgliedschaft prüfen'"
|
||
FontSize="9" Foreground="Gray" TextWrapping="Wrap" Margin="0,0,0,3"/>
|
||
</StackPanel>
|
||
<Border BorderBrush="LightGray" BorderThickness="1" CornerRadius="3">
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
<!-- Sticky Header - Margin rechts für Scrollbar-Breite (17px) -->
|
||
<Grid Name="gridExistingHeader" Grid.Row="0" Background="#F5F5F5" Margin="5,5,22,0">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="50"/>
|
||
<ColumnDefinition Width="50"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBlock Text="App" FontWeight="SemiBold" FontSize="10" Grid.Column="0" VerticalAlignment="Center"/>
|
||
<TextBlock Text="Avail." FontWeight="SemiBold" FontSize="10" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||
<TextBlock Text="Req." FontWeight="SemiBold" FontSize="10" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||
</Grid>
|
||
<Border Grid.Row="0" BorderBrush="LightGray" BorderThickness="0,0,0,1" Margin="5,0" VerticalAlignment="Bottom"/>
|
||
<!-- Scrollable Content -->
|
||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
||
<StackPanel Name="pnlExistingAssignments" Margin="5"/>
|
||
</ScrollViewer>
|
||
</Grid>
|
||
</Border>
|
||
</DockPanel>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
<Border Grid.Row="2" Background="#F0F0F0" Padding="10">
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<StackPanel Orientation="Vertical" Grid.Column="0">
|
||
<TextBlock Name="txtSummary" Text="Keine Auswahl" FontWeight="Bold" Margin="0,0,0,5"/>
|
||
<Grid>
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="300"/>
|
||
<ColumnDefinition Width="*"/>
|
||
</Grid.ColumnDefinitions>
|
||
<ProgressBar Name="pbProgress" Height="20" Visibility="Collapsed" Margin="0,0,10,5" Grid.Column="0"/>
|
||
<TextBlock Name="txtProgress" Text="" VerticalAlignment="Center" Grid.Column="1"/>
|
||
</Grid>
|
||
</StackPanel>
|
||
|
||
<!-- Legende im Footer -->
|
||
<StackPanel Orientation="Horizontal" Grid.Column="1" VerticalAlignment="Center" Margin="0,0,20,0">
|
||
<TextBlock Text="Legende:" FontSize="11" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||
<Border Background="#C8E6C9" Width="16" Height="16" CornerRadius="2" Margin="0,0,4,0" BorderBrush="#4CAF50" BorderThickness="1"/>
|
||
<TextBlock Text="Mitglied" FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,12,0"/>
|
||
<Border Background="#FFF9C4" Width="16" Height="16" CornerRadius="2" Margin="0,0,4,0" BorderBrush="#FBC02D" BorderThickness="1"/>
|
||
<TextBlock Text="Teilweise" FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,12,0"/>
|
||
<Border Background="#E3F2FD" Width="16" Height="16" CornerRadius="2" Margin="0,0,4,0" BorderBrush="#2196F3" BorderThickness="1"/>
|
||
<TextBlock Text="Native" FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,0,0"/>
|
||
</StackPanel>
|
||
|
||
<Button Name="btnApply" Content="Zuweisungen anwenden" Width="180" Height="35"
|
||
Grid.Column="2" IsEnabled="False"
|
||
Background="#0da075" Foreground="White" FontWeight="Bold"/>
|
||
</Grid>
|
||
</Border>
|
||
</Grid>
|
||
</Window>
|
||
"@
|
||
|
||
$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<IntuneGroupAssignment> 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<IntuneGroupAssignment> 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<IntuneGroupAssignment>();
|
||
RequiredGroups = new ObservableCollection<IntuneGroupAssignment>();
|
||
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
|
||
$groups = Get-MgUserMemberOf -UserId $target.Id -All -Property Id 2>$null
|
||
foreach ($g in $groups) {
|
||
if ($g.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.group') {
|
||
$memberships += $g.Id
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
# Gruppe: Transitive Mitgliedschaften laden (Gruppe ist Mitglied von...)
|
||
$groups = Get-MgGroupMemberOf -GroupId $target.Id -All -Property Id 2>$null
|
||
foreach ($g in $groups) {
|
||
if ($g.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.group') {
|
||
$memberships += $g.Id
|
||
}
|
||
}
|
||
}
|
||
$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 = @"
|
||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
Title="Required-Gruppe erstellen" Height="575" Width="550"
|
||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||
<Grid Margin="15">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="Auto"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<TextBlock Grid.Row="0" Text="Required-Gruppe erstellen" FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
|
||
<TextBlock Grid.Row="1" Text="App: $($AppItem.AppName)" TextWrapping="Wrap" Margin="0,0,0,10"/>
|
||
|
||
<GroupBox Grid.Row="2" Header="Option 1: Automatischer Gruppenname" Margin="0,0,0,10" Padding="10">
|
||
<StackPanel>
|
||
<TextBlock Text="Vorgeschlagener Name:" Margin="0,0,0,5"/>
|
||
<TextBox Name="txtSuggestedName" IsReadOnly="True" Background="LightGray" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtSuggestedStatus" Text="" FontSize="10" Margin="0,0,0,10"/>
|
||
<Button Name="btnCreateSuggested" Content="Mit Vorschlag erstellen" Height="30"/>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
|
||
<GroupBox Grid.Row="3" Header="Option 2: Eigener Gruppenname" Padding="10">
|
||
<StackPanel>
|
||
<TextBlock Text="Eigener Name (ohne Präfix):" Margin="0,0,0,5"/>
|
||
<TextBox Name="txtCustomName" Height="25" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtFullCustomName" Text="" FontSize="10" Foreground="Gray" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtCustomStatus" Text="" FontSize="10" Margin="0,0,0,10"/>
|
||
<Button Name="btnCreateCustom" Content="Mit eigenem Namen erstellen" Height="30" IsEnabled="False"/>
|
||
</StackPanel>
|
||
</GroupBox>
|
||
|
||
<GroupBox Grid.Row="4" Header="Optionen" Padding="10" Margin="0,0,0,10">
|
||
<CheckBox Name="chkAssignToApp" Content="Neue Gruppe direkt der App zuweisen (Required)" IsChecked="True"/>
|
||
</GroupBox>
|
||
|
||
<StackPanel Grid.Row="6" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||
<Button Name="btnCancel" Content="Abbrechen" Width="100" Height="30"/>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Window>
|
||
"@
|
||
|
||
$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 = @"
|
||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
Title="Gruppenmitglieder: $GroupName" Height="550" Width="700"
|
||
WindowStartupLocation="CenterOwner">
|
||
<Grid Margin="15">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="Auto"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<TextBlock Grid.Row="0" Text="Mitglieder von: $GroupName" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
|
||
|
||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,0,0,10">
|
||
<CheckBox Name="chkResolveNestedDialog" Content="Verschachtelte Gruppen auflösen" VerticalAlignment="Center" Margin="0,0,15,0"/>
|
||
<Button Name="btnReloadMembers" Content="Neu laden" Width="90" Height="25"/>
|
||
<TextBlock Name="txtMemberCount" Text="" VerticalAlignment="Center" Margin="15,0,0,0" Foreground="Gray"/>
|
||
</StackPanel>
|
||
|
||
<DataGrid Name="dgMembers" Grid.Row="2" AutoGenerateColumns="False" CanUserAddRows="False" IsReadOnly="True">
|
||
<DataGrid.Columns>
|
||
<DataGridTextColumn Header="Name" Binding="{Binding DisplayName}" Width="*"/>
|
||
<DataGridTextColumn Header="UPN" Binding="{Binding UserPrincipalName}" Width="220"/>
|
||
<DataGridTextColumn Header="Typ" Binding="{Binding ObjectType}" Width="160"/>
|
||
<DataGridTextColumn Header="Quelle" Binding="{Binding SourceGroup}" Width="160"/>
|
||
</DataGrid.Columns>
|
||
</DataGrid>
|
||
|
||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||
<Button Name="btnExportMembers" Content="Als CSV exportieren" Width="150" Height="30" Margin="0,0,10,0"/>
|
||
<Button Name="btnCloseMembers" Content="Schließen" Width="100" Height="30"/>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Window>
|
||
"@
|
||
|
||
$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 = @"
|
||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
Title="Abteilung auswählen" Height="450" Width="500"
|
||
WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
|
||
<Grid Margin="15">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="Auto"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<StackPanel Grid.Row="0" Margin="0,0,0,15">
|
||
<TextBlock Text="Mehrere Abteilungen gefunden" FontSize="16" FontWeight="Bold" Margin="0,0,0,10"/>
|
||
<TextBlock Name="txtSearchInfo" TextWrapping="Wrap" FontSize="12"/>
|
||
</StackPanel>
|
||
|
||
<ListBox Grid.Row="1" Name="lstDepartments" FontSize="12" Margin="0,0,0,15">
|
||
<ListBox.ItemTemplate>
|
||
<DataTemplate>
|
||
<TextBlock Text="{Binding DisplayName}" Padding="5"/>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
</ListBox>
|
||
|
||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||
<Button Name="btnCancel" Content="Abbrechen" Width="100" Height="30" Margin="0,0,10,0"/>
|
||
<Button Name="btnSelect" Content="Auswählen" Width="100" Height="30" IsEnabled="False"/>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Window>
|
||
"@
|
||
|
||
$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 = @"
|
||
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||
Title="Zuweisungen überprüfen" Height="600" Width="900"
|
||
WindowStartupLocation="CenterOwner" ResizeMode="CanResize">
|
||
<Grid Margin="15">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<TextBlock Grid.Row="0" Text="Bitte überprüfen Sie die geplanten Zuweisungen"
|
||
FontSize="16" FontWeight="Bold" Margin="0,0,0,15"/>
|
||
|
||
<Grid Grid.Row="1" Margin="0,0,0,15">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="*"/>
|
||
</Grid.ColumnDefinitions>
|
||
|
||
<Border Grid.Column="0" Background="#E8F5E9" Padding="10" Margin="0,0,5,0" CornerRadius="5">
|
||
<StackPanel>
|
||
<TextBlock Text="Ziele" FontWeight="Bold" FontSize="12" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtTargetsSummary" FontSize="14" FontWeight="Bold"/>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<Border Grid.Column="1" Background="#E3F2FD" Padding="10" Margin="0,0,5,0" CornerRadius="5">
|
||
<StackPanel>
|
||
<TextBlock Text="Zuweisungen" FontWeight="Bold" FontSize="12" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtAssignmentsSummary" FontSize="14" FontWeight="Bold"/>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<Border Grid.Column="2" Background="#FFF3E0" Padding="10" CornerRadius="5">
|
||
<StackPanel>
|
||
<TextBlock Text="Gesamt" FontWeight="Bold" FontSize="12" Margin="0,0,0,5"/>
|
||
<TextBlock Name="txtTotalOperations" FontSize="14" FontWeight="Bold"/>
|
||
</StackPanel>
|
||
</Border>
|
||
</Grid>
|
||
|
||
<GroupBox Grid.Row="2" Header="Detaillierte Übersicht" Margin="0,0,0,15">
|
||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||
<StackPanel Name="pnlDetails" Margin="5"/>
|
||
</ScrollViewer>
|
||
</GroupBox>
|
||
|
||
<TextBlock Grid.Row="3" Text="Möchten Sie diese Zuweisungen jetzt durchführen?"
|
||
FontSize="12" FontWeight="Bold" Margin="0,0,0,15" HorizontalAlignment="Center"/>
|
||
|
||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right">
|
||
<Button Name="btnCancel" Content="Abbrechen" Width="120" Height="35" Margin="0,0,10,0"/>
|
||
<Button Name="btnConfirm" Content="Ja, durchführen" Width="150" Height="35"
|
||
Background="#0da075" Foreground="White" FontWeight="Bold"/>
|
||
</StackPanel>
|
||
</Grid>
|
||
</Window>
|
||
"@
|
||
|
||
$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 } |