Add Windows/AcitveDirectory/usersignatures.md

This commit is contained in:
2025-03-28 19:34:01 +00:00
parent 1acbdad2e1
commit 51932ede8e

View File

@@ -0,0 +1,631 @@
# Requirements
1. ".env" Datei im gleichen Verzeichnis, wie das, in dem das Powershell Script gesichert wird.
2. "template.txt", "template.html", "template.rtf" ebenfalls in dem jeweiligen Verzeichnis ablegen.
## Aufbau: .env Datei
```
AD_OUPath=OU=...,OU=...,OU=...,DC=contoso,DC=org
SIGNATURE_PATH=C:\Scripts\Sinatures
DEBUG=0
```
Die Werte sind Beispielwerte und entsprechend anzupassen. Debug gibt Rückmeldungen in der Powershell.
## Aufbau: template Dateien
In den Templates werden Platzhalter genutzt um die Variablen zu ersetzen mit den Werten aus dem AD.
[VORNAME]
[NACHNAME]
[JOB]
[DEPARTMENT]
[PHONE]
[PLAIN_PHONE]
[EMAIL]
Ich selbst habe den Teil innerhalb der Templates so gestaltet, dass es "[JOB] | [DEPARTMENT]" gibt. Wenn ein User keine JOB Bezeichnung im AD hat, würde das automatisch ersetzt werden durch "[DEPARTMENT]".
Da ich auch Links beim Klicken auf Icons nutze, muss die Telefonnummer etwas angepasst werden in der html Template Datei. Dafür habe ich im "tel:" Bereich ein "[PLAIN_PHONE]" gesetzt.
## Powershell Script:
```powershell
Canvas [Console]::OutputEncoding = [Text.Encoding]::UTF8
###############################################################################
# 0) FUNKTION ZUR DEBUG-AUSGABE
###############################################################################
function Write-HostIfDebug {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
# Der Typ hier ist entfernt, damit null oder eine valide Farbe möglich ist.
$ForegroundColor
)
if ($Env:DEBUG -eq "1") {
if ($ForegroundColor) {
Write-Host $Message -ForegroundColor $ForegroundColor
} else {
Write-Host $Message
}
}
}
###############################################################################
# 1) SIGNATUREN-LOGIK
###############################################################################
function ConvertPhoneForHtml($phone) {
if (![string]::IsNullOrEmpty($phone)) {
$phone = $phone -replace " ", " ​"
$phone = $phone -replace "-", " ​- ​"
}
return $phone
}
function ConvertEmailForHtml($mail) {
if (![string]::IsNullOrEmpty($mail)) {
$mail = $mail -replace "\\.", ".​"
$mail = $mail -replace "@", "@​"
$mail = $mail -replace "-", "-​"
}
return $mail
}
# .env einlesen
$envFile = Join-Path $PSScriptRoot '.env'
if (Test-Path $envFile) {
Get-Content -Path $envFile | ForEach-Object {
if ($_ -match '^(?<key>[^=]+)=(?<value>.*)$') {
$key = $Matches['key']
$value = $Matches['value']
Set-Item -Path "Env:$key" -Value $value
}
}
} else {
Write-HostIfDebug ".env-Datei wurde nicht gefunden. Bitte stelle sicher, dass sie im gleichen Verzeichnis liegt."
return
}
# Pfade aus .env
$ouPath = $Env:AD_OUPath
$signaturePath = $Env:SIGNATURE_PATH
if (!$ouPath) {
Write-HostIfDebug "Bitte überprüfe, ob in der .env-Datei die Variable 'AD_OUPath' eingetragen ist."
return
}
if (![string]::IsNullOrEmpty($signaturePath)) {
if (!(Test-Path $signaturePath)) {
Write-HostIfDebug "Der Pfad '$signaturePath' existiert nicht. Bitte überprüfen."
return
}
} else {
Write-HostIfDebug "Bitte überprüfe, ob in der .env-Datei die Variable 'SIGNATURE_PATH' eingetragen ist."
return
}
Write-HostIfDebug "Suche nach Benutzern in der OU: $ouPath..."
# Benutzer holen (und sortieren)
$users = Get-ADUser -Filter * -SearchBase $ouPath -Properties cn, givenName, sn, telephoneNumber, mail, title, department
Write-HostIfDebug "Gefundene Benutzer:`n"
$global:AllUsers = $users |
Sort-Object -Property sn, givenName |
ForEach-Object {
[PSCustomObject]@{
Selected = $false
CN = $_.cn
ADUser = $_
DisplayText = if ($_.sn -and $_.givenName) {
"$($_.sn), $($_.givenName) ($($_.cn))"
} else {
$_.cn
}
}
}
function Generate-SignaturesForUsers($selectedUsers) {
foreach ($item in $selectedUsers) {
$user = $item.ADUser
$cn = $user.cn
$givenName = $user.givenName
$sn = $user.sn
$originalPhone = $user.telephoneNumber
$originalEmail = $user.mail
$plainPhone = if (![string]::IsNullOrEmpty($originalPhone)) {
$originalPhone -replace '^0+', ''
} else { "" }
$convertedPhone = ConvertPhoneForHtml($originalPhone)
$convertedEmail = ConvertEmailForHtml($originalEmail)
$jobTitle = $user.title
$department = $user.department
Write-HostIfDebug "-----------------------------------"
Write-HostIfDebug "CN : $cn"
Write-HostIfDebug "Vorname : $($givenName)"
Write-HostIfDebug "Nachname : $($sn)"
Write-HostIfDebug "Telefon (orig): $($originalPhone)"
Write-HostIfDebug "Telefon (HTML): $($convertedPhone)"
Write-HostIfDebug "plain_phone : $($plainPhone)"
Write-HostIfDebug "E-Mail (orig) : $($originalEmail)"
Write-HostIfDebug "E-Mail (HTML) : $($convertedEmail)"
Write-HostIfDebug "Job (Title) : $($jobTitle)"
Write-HostIfDebug "Abteilung : $($department)"
# 1) HTML
$templateFilePath = Join-Path $PSScriptRoot 'template.html'
if (!(Test-Path $templateFilePath)) {
Write-HostIfDebug "Vorlage 'template.html' wurde nicht gefunden, bitte prüfe den Pfad." -ForegroundColor Red
return
}
$signatureHtml = Get-Content -Path $templateFilePath -Raw
if ([string]::IsNullOrEmpty($jobTitle)) {
$searchString = '<div style="font-size: 14px; color: #ffffff;">[JOB] | [DEPARTMENT]</div>'
$pattern = [Regex]::Escape($searchString)
$replacement = '<div style="font-size: 14px; color: #ffffff;">[DEPARTMENT]</div>'
$signatureHtml = $signatureHtml -replace $pattern, $replacement
}
# E-Mail
if ($originalEmail) {
$pattern = 'href="mailto:\[EMAIL\]"'
$replacement = 'href="mailto:' + $originalEmail + '"'
$signatureHtml = $signatureHtml -replace $pattern, $replacement
$pattern = '\[EMAIL\]'
$replacement = $convertedEmail
$signatureHtml = $signatureHtml -replace $pattern, $replacement
} else {
$pattern = 'href="mailto:\[EMAIL\]"'
$replacement = 'href="mailto:"'
$signatureHtml = $signatureHtml -replace $pattern, $replacement
$pattern = '\[EMAIL\]'
$replacement = ''
$signatureHtml = $signatureHtml -replace $pattern, $replacement
}
# Phone
if ($plainPhone) {
$pattern = 'href="tel:\+49\[PLAIN_PHONE\]"'
$replacement = 'href="tel:+49' + $plainPhone + '"'
$signatureHtml = $signatureHtml -replace $pattern, $replacement
$pattern = '\[PHONE\]'
$replacement = $convertedPhone
$signatureHtml = $signatureHtml -replace $pattern, $replacement
} else {
$pattern = 'href="tel:\+49\[PHONE\]"'
$replacement = 'href="tel:"'
$signatureHtml = $signatureHtml -replace $pattern, $replacement
$pattern = '\[PHONE\]'
$replacement = ''
$signatureHtml = $signatureHtml -replace $pattern, $replacement
}
# Weitere Platzhalter
$signatureHtml = $signatureHtml -replace '\[VORNAME\]', $givenName
$signatureHtml = $signatureHtml -replace '\[NACHNAME\]', $sn
$signatureHtml = $signatureHtml -replace '\[JOB\]', $jobTitle
$signatureHtml = $signatureHtml -replace '\[DEPARTMENT\]', $department
$signatureHtml = $signatureHtml -replace '\[PLAIN_PHONE\]', $plainPhone
$filename = "${cn}.htm"
$outputPath = Join-Path $signaturePath $filename
Set-Content -Path $outputPath -Value $signatureHtml -Encoding UTF8
Write-HostIfDebug "Signatur (HTML) für '$($cn)' unter: $outputPath erstellt." -ForegroundColor Green
# 2) TXT (wenn vorhanden)
$templateTxtFilePath = Join-Path $PSScriptRoot 'template.txt'
if (Test-Path $templateTxtFilePath) {
$signatureTxt = Get-Content -Path $templateTxtFilePath -Raw
if ([string]::IsNullOrEmpty($jobTitle)) {
$searchString = '[JOB] | [DEPARTMENT]'
$pattern = [Regex]::Escape($searchString)
$replacement = '[DEPARTMENT]'
$signatureTxt = $signatureTxt -replace $pattern, $replacement
}
if ($originalEmail) {
$signatureTxt = $signatureTxt -replace '\[EMAIL\]', $originalEmail
} else {
$signatureTxt = $signatureTxt -replace '\[EMAIL\]', ''
}
if ($originalPhone) {
$signatureTxt = $signatureTxt -replace '\[PHONE\]', $originalPhone
} else {
$signatureTxt = $signatureTxt -replace '\[PHONE\]', ''
}
if ($plainPhone) {
$signatureTxt = $signatureTxt -replace '\[PLAIN_PHONE\]', $plainPhone
} else {
$signatureTxt = $signatureTxt -replace '\[PLAIN_PHONE\]', ''
}
$signatureTxt = $signatureTxt -replace '\[VORNAME\]', $givenName
$signatureTxt = $signatureTxt -replace '\[NACHNAME\]', $sn
$signatureTxt = $signatureTxt -replace '\[JOB\]', $jobTitle
$signatureTxt = $signatureTxt -replace '\[DEPARTMENT\]', $department
$txtFilename = "${cn}.txt"
$txtOutputPath = Join-Path $signaturePath $txtFilename
Set-Content -Path $txtOutputPath -Value $signatureTxt -Encoding UTF8
Write-HostIfDebug "Signatur (TXT) für '$($cn)' unter: $txtOutputPath erstellt." -ForegroundColor Green
}
# 3) RTF (wenn vorhanden)
$templateRtfFilePath = Join-Path $PSScriptRoot 'template.rtf'
if (Test-Path $templateRtfFilePath) {
$signatureRtf = Get-Content -Path $templateRtfFilePath -Raw
if ([string]::IsNullOrEmpty($jobTitle)) {
$searchString = '[JOB] | [DEPARTMENT]'
$pattern = [Regex]::Escape($searchString)
$replacement = '[DEPARTMENT]'
$signatureRtf = $signatureRtf -replace $pattern, $replacement
}
if ($originalEmail) {
$signatureRtf = $signatureRtf -replace '\[EMAIL\]', $originalEmail
} else {
$signatureRtf = $signatureRtf -replace '\[EMAIL\]', ''
}
if ($originalPhone) {
$signatureRtf = $signatureRtf -replace '\[PHONE\]', $originalPhone
} else {
$signatureRtf = $signatureRtf -replace '\[PHONE\]', ''
}
if ($plainPhone) {
$signatureRtf = $signatureRtf -replace '\[PLAIN_PHONE\]', $plainPhone
} else {
$signatureRtf = $signatureRtf -replace '\[PLAIN_PHONE\]', ''
}
$signatureRtf = $signatureRtf -replace '\[VORNAME\]', $givenName
$signatureRtf = $signatureRtf -replace '\[NACHNAME\]', $sn
$signatureRtf = $signatureRtf -replace '\[JOB\]', $jobTitle
$signatureRtf = $signatureRtf -replace '\[DEPARTMENT\]', $department
$rtfFilename = "${cn}.rtf"
$rtfOutputPath = Join-Path $signaturePath $rtfFilename
Set-Content -Path $rtfOutputPath -Value $signatureRtf -Encoding Default
Write-HostIfDebug "Signatur (RTF) für '$($cn)' unter: $rtfOutputPath erstellt." -ForegroundColor Green
}
Write-HostIfDebug "-----------------------------------"
}
Write-HostIfDebug "Fertig! Signaturen wurden erstellt."
}
###############################################################################
# 2) WPF-UI + DRAG MOVE
###############################################################################
Add-Type -AssemblyName PresentationCore,PresentationFramework
$XAML = @"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="E-Mail Signaturen"
WindowStyle="None"
ResizeMode="NoResize"
Background="#262626">
<Window.Resources>
<!-- Keine Hover-Effekte, nur normal/pressed für den X-Button -->
<Style x:Key="CloseButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<!-- Wir definieren ein Custom Template, ohne Hover-Trigger -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<!-- Nur Pressed-Trigger (optional) -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#444444"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Ab hier definieren wir ein eigenes ControlTemplate für OK/Abbrechen-Buttons -->
<Style x:Key="FancyButtonStyle" TargetType="Button" BasedOn="{x:Null}">
<!-- Wichtig: eigene Vorlage, damit wir Windows-Standard nicht haben -->
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<!-- Normal-Attribute -->
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="Background" Value="#3A3A3A"/>
<Setter Property="BorderBrush" Value="#555555"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Margin" Value="5,0,5,0"/>
<Setter Property="Padding" Value="6,3,6,3"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<!-- Rahmen in Form einer Border -->
<Border x:Name="border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!-- Hier liegt der eigentliche Button-Inhalt -->
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<!-- Hover: Hintergrund #FDC100, Schrift #000000 -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#FDC100"/>
<Setter Property="Foreground" Value="#000000"/>
</Trigger>
<!-- Pressed: Hintergrund #C77E00, Schrift #FFFFFF -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#C77E00"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
</Trigger>
<!-- Deaktiviert -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="#707070"/>
<Setter Property="Foreground" Value="#AAAAAA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- RadioButton-Style -->
<Style TargetType="RadioButton">
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<StackPanel Orientation="Horizontal" Margin="0,5,0,5">
<Border Width="16" Height="16"
BorderBrush="#3F3F3E"
BorderThickness="2"
CornerRadius="8" Margin="0,0,5,0">
<Ellipse x:Name="EllipseFill" Fill="Transparent"/>
</Border>
<ContentPresenter/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="EllipseFill" Property="Fill" Value="#FDC100"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="EllipseFill" Property="Fill" Value="Gray"/>
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ListView-Style: dunkel, bei deaktiviert -> Schrift grau -->
<Style TargetType="ListView" x:Key="UserListStyle">
<Setter Property="Background" Value="#2F2F2E"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="Background" Value="#2F2F2E"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- ListViewItem-Style -->
<Style TargetType="ListViewItem" x:Key="ItemStyle">
<Setter Property="Background" Value="#2F2F2E"/>
<Setter Property="Foreground" Value="#FFFFFF"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#3A3A3A"/>
</Trigger>
<DataTrigger Binding="{Binding IsEnabled,
RelativeSource={RelativeSource AncestorType=ListView}}" Value="False">
<Setter Property="Foreground" Value="Gray"/>
<Setter Property="Background" Value="#2F2F2E"/>
</DataTrigger>
</Style.Triggers>
</Style>
<!-- ScrollViewer-Style: kein Weiß -->
<Style TargetType="ScrollViewer">
<Setter Property="Background" Value="#2F2F2E"/>
</Style>
</Window.Resources>
<Grid x:Name="RootGrid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Eigener Titelbalken -->
<RowDefinition Height="Auto"/> <!-- Überschrift + Radiobuttons -->
<RowDefinition Height="Auto"/> <!-- ListView -->
<RowDefinition Height="Auto"/> <!-- Buttons -->
</Grid.RowDefinitions>
<!-- Titelbalken (mit "X" rechts) -->
<Grid Grid.Row="0" x:Name="TitleBar" Background="#444444">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Titel hier -->
<TextBlock Text="E-Mail Signaturen"
Foreground="#FFFFFF"
VerticalAlignment="Center"
Margin="10,0,0,0"/>
<Button x:Name="btnClose" Grid.Column="1"
Style="{StaticResource CloseButtonStyle}"
Content=""
HorizontalAlignment="Right"
Margin="0,0,10,0"/>
</Grid>
<!-- Überschrift + Radiobuttons -->
<StackPanel Orientation="Vertical" Grid.Row="1" Margin="10">
<TextBlock Text="Signaturen erstellen für ..."
Foreground="#FFFFFF"
FontWeight="Bold"
FontSize="16"
Margin="0,0,0,10"/>
<RadioButton x:Name="rbAlle" Content="alle Mitarbeiter" />
<RadioButton x:Name="rbAuswahl" Content="den ausgewählten Mitarbeiter" />
</StackPanel>
<!-- Liste mit Nutzern (max 300px hoch, scrollt bei Bedarf) -->
<ListView x:Name="lvUsers" Style="{StaticResource UserListStyle}"
ItemContainerStyle="{StaticResource ItemStyle}"
Grid.Row="2" Margin="10" MaxHeight="300">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Selected}" Margin="5,0,5,0"/>
<TextBlock Text="{Binding DisplayText}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!-- Buttons OK / Abbrechen -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="3" Margin="10">
<Button x:Name="btnOK" Style="{StaticResource FancyButtonStyle}" Content="OK" Width="80" Margin="0,0,10,0"/>
<Button x:Name="btnCancel" Style="{StaticResource FancyButtonStyle}" Content="Abbrechen" Width="80"/>
</StackPanel>
</Grid>
</Window>
"@
[xml]$xml = $XAML
$reader = New-Object System.Xml.XmlNodeReader $xml
$Window = [Windows.Markup.XamlReader]::Load($reader)
# Fenster-Einstellungen
$Window.Width = 400
$Window.SizeToContent = 'Height'
$Window.WindowStartupLocation = 'CenterScreen'
$Window.MaxHeight = [System.Windows.SystemParameters]::PrimaryScreenHeight * 0.8
# Elemente holen
$TitleBar = $Window.FindName('TitleBar')
$btnClose = $Window.FindName('btnClose')
$rbAlle = $Window.FindName('rbAlle')
$rbAuswahl = $Window.FindName('rbAuswahl')
$lvUsers = $Window.FindName('lvUsers')
$btnOK = $Window.FindName('btnOK')
$btnCancel = $Window.FindName('btnCancel')
# Beim Start: "alle Mitarbeiter" -> Liste grau
$rbAlle.IsChecked = $true
$lvUsers.IsEnabled = $false
$rbAlle.Add_Checked({
$lvUsers.IsEnabled = $false
})
$rbAuswahl.Add_Checked({
$lvUsers.IsEnabled = $true
})
# Datenquelle
$lvUsers.ItemsSource = $global:AllUsers
# Funktion zur Aktualisierung der RadioButton-Beschriftung
function UpdateRbAuswahlContent {
$count = ($global:AllUsers | Where-Object { $_.Selected -eq $true }).Count
if ($count -ge 2) {
$rbAuswahl.Content = "die ausgewählten Mitarbeiter"
}
else {
$rbAuswahl.Content = "den ausgewählten Mitarbeiter"
}
}
# Event-Handler für Check/Uncheck in der Liste
$lvUsers.AddHandler(
[System.Windows.Controls.Primitives.ButtonBase]::ClickEvent,
[System.Windows.RoutedEventHandler]{
UpdateRbAuswahlContent
},
$true
)
# Titelbalken "Drag" (Fenster verschieben)
$TitleBar.Add_MouseLeftButtonDown({
if ($_.LeftButton -eq [System.Windows.Input.MouseButtonState]::Pressed) {
try {
$Window.DragMove()
} catch {}
}
})
# "X"-Button oben rechts
$btnClose.Add_Click({
$Script:SelectedUsers = $null
$Window.Close()
})
# OK-/Abbrechen-Logik
$Script:SelectedUsers = $null
$btnOK.Add_Click({
if ($rbAlle.IsChecked -eq $true) {
$Script:SelectedUsers = $global:AllUsers
} else {
$Script:SelectedUsers = $global:AllUsers | Where-Object { $_.Selected -eq $true }
}
$Window.Close()
})
$btnCancel.Add_Click({
$Script:SelectedUsers = $null
$Window.Close()
})
# UI anzeigen
$null = $Window.ShowDialog()
# Falls abgebrochen oder geschlossen
if (-not $Script:SelectedUsers) {
Write-HostIfDebug "Abgebrochen. Keine Signaturen erzeugt."
return
}
# Signaturen erstellen
Generate-SignaturesForUsers -selectedUsers $Script:SelectedUsers
```