Files
Boilerplates/Windows/AcitveDirectory/usersignatures.md

25 KiB

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:

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