diff --git a/Active-Directory/export_user_from_Group_to_CSV.ps1 b/Active-Directory/export_user_from_Group_to_CSV.ps1 new file mode 100644 index 0000000..6fe36dc --- /dev/null +++ b/Active-Directory/export_user_from_Group_to_CSV.ps1 @@ -0,0 +1,386 @@ +<# +.SYNOPSIS + GUI tool to export all members of an AD group (including nested groups) to a CSV file. +.PARAMETER LogPath + Optional path for the log file. Defaults to script directory with a timestamp. +#> + +param ( + [string]$LogPath = (Join-Path $PSScriptRoot ("export_log_" + (Get-Date -Format 'yyyyMMdd_HHmmss') + ".txt")) +) + +# --------------------------------------------------------------------------- +# Ensure ActiveDirectory module is available +# --------------------------------------------------------------------------- +if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) { + $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + if (-not $isAdmin) { + Write-Error "ActiveDirectory module not found. Please run this script as Administrator to install it automatically, or install RSAT manually." + exit 1 + } + + Write-Host "ActiveDirectory module not found. Attempting to install..." -ForegroundColor Yellow + $os = (Get-CimInstance Win32_OperatingSystem).Caption + + if ($os -match "Server") { + try { + Import-Module ServerManager -ErrorAction Stop + Add-WindowsFeature RSAT-AD-PowerShell -ErrorAction Stop | Out-Null + Write-Host "ActiveDirectory module installed via Add-WindowsFeature." -ForegroundColor Green + } catch { + Write-Error "Failed to install ActiveDirectory module on Server: $_" + exit 1 + } + } else { + try { + $feature = Get-WindowsCapability -Name "Rsat.ActiveDirectory*" -Online -ErrorAction Stop | + Where-Object State -ne Installed | Select-Object -First 1 + if ($feature) { + Add-WindowsCapability -Name $feature.Name -Online -ErrorAction Stop | Out-Null + Write-Host "ActiveDirectory module installed via Add-WindowsCapability." -ForegroundColor Green + } + } catch { + Write-Error "Failed to install ActiveDirectory module on Windows client: $_" + exit 1 + } + } +} + +try { + Import-Module ActiveDirectory -ErrorAction Stop +} catch { + Write-Error "ActiveDirectory module could not be loaded: $_" + exit 1 +} + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing + +[System.Windows.Forms.Application]::EnableVisualStyles() + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- +function Write-Log { + param([string]$Message, [string]$Level = 'INFO') + $entry = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message + $entry | Out-File -FilePath $LogPath -Append -Encoding UTF8 + + $color = switch ($Level) { + 'ERROR' { [System.Drawing.Color]::Salmon } + 'WARN' { [System.Drawing.Color]::Goldenrod } + default { [System.Drawing.Color]::White } + } + Write-OutputLine $entry $color +} + +function Write-OutputLine { + param([string]$Text, [System.Drawing.Color]$Color = [System.Drawing.Color]::White) + $script:rtbOutput.SelectionStart = $script:rtbOutput.TextLength + $script:rtbOutput.SelectionLength = 0 + $script:rtbOutput.SelectionColor = $Color + $script:rtbOutput.AppendText("$Text`n") + $script:rtbOutput.ScrollToCaret() + [System.Windows.Forms.Application]::DoEvents() +} + +function Start-Export { + param([string]$GroupName, [string]$OutputPath, [string]$Server) + + $script:btnExport.Enabled = $false + $script:rtbOutput.Clear() + + $adParams = @{} + if ($Server) { $adParams['Server'] = $Server } + + # Validate group + try { + $null = Get-ADGroup -Identity $GroupName -ErrorAction Stop @adParams + Write-Log "Gruppe gefunden: $GroupName" + } catch { + Write-Log "Gruppe '$GroupName' nicht gefunden: $_" -Level ERROR + $script:btnExport.Enabled = $true + return + } + + $visitedGroups = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + $results = [System.Collections.Generic.List[PSCustomObject]]::new() + + function Get-NestedMembers { + param([string]$Group, [string]$SourceGroup) + + if (-not $visitedGroups.Add($Group)) { return } + + try { + $members = Get-ADGroupMember -Identity $Group -ErrorAction Stop @adParams + } catch { + Write-Log "Gruppe '$Group' konnte nicht abgerufen werden: $_" -Level WARN + return + } + + foreach ($member in $members) { + switch ($member.objectClass) { + 'user' { + try { + $user = Get-ADUser -Identity $member.distinguishedName -Properties ` + DisplayName, EmailAddress, Title, Department, Company, + Enabled, LastLogonDate, PasswordLastSet, PasswordNeverExpires, + Manager, telephoneNumber, DistinguishedName ` + -ErrorAction Stop @adParams + + $managerName = '' + if ($user.Manager) { + try { + $mgr = Get-ADUser -Identity $user.Manager -Properties DisplayName -ErrorAction Stop @adParams + $managerName = $mgr.DisplayName + } catch {} + } + + $results.Add([PSCustomObject]@{ + SourceGroup = $SourceGroup + MemberOfGroup = $Group + SamAccountName = $user.SamAccountName + DisplayName = $user.DisplayName + GivenName = $user.GivenName + Surname = $user.Surname + EmailAddress = $user.EmailAddress + Title = $user.Title + Department = $user.Department + Company = $user.Company + TelephoneNumber = $user.telephoneNumber + Enabled = $user.Enabled + LastLogonDate = $user.LastLogonDate + PasswordLastSet = $user.PasswordLastSet + PasswordNeverExpires = $user.PasswordNeverExpires + Manager = $managerName + DistinguishedName = $user.DistinguishedName + }) + Write-Log " Benutzer: $($user.SamAccountName) ($($user.DisplayName))" + } catch { + Write-Log "Benutzer '$($member.SamAccountName)' konnte nicht abgerufen werden: $_" -Level WARN + } + } + 'group' { + Write-Log "Verschachtelte Gruppe: $($member.Name)" -Level INFO + Get-NestedMembers -Group $member.distinguishedName -SourceGroup $SourceGroup + } + } + } + } + + Write-Log "Starte Export für Gruppe '$GroupName' (inkl. verschachtelter Gruppen)..." + Get-NestedMembers -Group $GroupName -SourceGroup $GroupName + + if ($results.Count -eq 0) { + Write-Log "Keine Benutzer in Gruppe '$GroupName' gefunden." -Level WARN + $script:btnExport.Enabled = $true + return + } + + $unique = $results | Sort-Object SamAccountName -Unique + + try { + $unique | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8 -Delimiter ';' -ErrorAction Stop + $summary = "Export abgeschlossen. Benutzer: {0} | Gruppen durchsucht: {1} | Datei: {2}" -f ` + $unique.Count, $visitedGroups.Count, $OutputPath + Write-Log $summary -Level INFO + Write-OutputLine $summary ([System.Drawing.Color]::LightGreen) + } catch { + Write-Log "Fehler beim Schreiben der CSV: $_" -Level ERROR + } + + $script:btnExport.Enabled = $true +} + +# --------------------------------------------------------------------------- +# Form +# --------------------------------------------------------------------------- +$form = New-Object System.Windows.Forms.Form +$form.Text = "AD Group Export" +$form.Size = New-Object System.Drawing.Size(680, 600) +$form.StartPosition = "CenterScreen" +$form.FormBorderStyle = "FixedDialog" +$form.MaximizeBox = $false +$form.BackColor = [System.Drawing.Color]::FromArgb(30, 30, 30) +$form.ForeColor = [System.Drawing.Color]::White +$form.Font = New-Object System.Drawing.Font('Segoe UI', 9) + +$pad = 16 + +# --- Group row --- +$lblGroup = New-Object System.Windows.Forms.Label +$lblGroup.Text = "AD Gruppe:" +$lblGroup.Location = New-Object System.Drawing.Point($pad, 20) +$lblGroup.Size = New-Object System.Drawing.Size(84, 22) +$form.Controls.Add($lblGroup) + +$cmbGroup = New-Object System.Windows.Forms.ComboBox +$cmbGroup.Location = New-Object System.Drawing.Point(104, 18) +$cmbGroup.Size = New-Object System.Drawing.Size(436, 22) +$cmbGroup.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45) +$cmbGroup.ForeColor = [System.Drawing.Color]::White +$cmbGroup.FlatStyle = "Flat" +$cmbGroup.AutoCompleteMode = "SuggestAppend" +$cmbGroup.AutoCompleteSource = "ListItems" +$form.Controls.Add($cmbGroup) + +$btnLoadGroups = New-Object System.Windows.Forms.Button +$btnLoadGroups.Text = "Gruppen laden" +$btnLoadGroups.Location = New-Object System.Drawing.Point(550, 16) +$btnLoadGroups.Size = New-Object System.Drawing.Size(96, 26) +$btnLoadGroups.BackColor = [System.Drawing.Color]::FromArgb(0, 122, 204) +$btnLoadGroups.ForeColor = [System.Drawing.Color]::White +$btnLoadGroups.FlatStyle = "Flat" +$btnLoadGroups.FlatAppearance.BorderSize = 0 +$form.Controls.Add($btnLoadGroups) + +$btnLoadGroups.Add_Click({ + $btnLoadGroups.Text = "Lädt..." + $btnLoadGroups.Enabled = $false + [System.Windows.Forms.Application]::DoEvents() + try { + $adP = @{} + if ($txtServer.Text.Trim()) { $adP['Server'] = $txtServer.Text.Trim() } + $groups = Get-ADGroup -Filter * @adP | Select-Object -ExpandProperty Name | Sort-Object + $cmbGroup.Items.Clear() + $cmbGroup.Items.AddRange($groups) + Write-OutputLine "$($groups.Count) Gruppen geladen." ([System.Drawing.Color]::LightGreen) + } catch { + Write-OutputLine "Fehler beim Laden der Gruppen: $_" ([System.Drawing.Color]::Salmon) + } + $btnLoadGroups.Text = "Gruppen laden" + $btnLoadGroups.Enabled = $true +}) + +# --- Output file row --- +$lblOut = New-Object System.Windows.Forms.Label +$lblOut.Text = "CSV Datei:" +$lblOut.Location = New-Object System.Drawing.Point($pad, 58) +$lblOut.Size = New-Object System.Drawing.Size(84, 22) +$form.Controls.Add($lblOut) + +$txtOut = New-Object System.Windows.Forms.TextBox +$txtOut.Location = New-Object System.Drawing.Point(104, 56) +$txtOut.Size = New-Object System.Drawing.Size(436, 22) +$txtOut.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45) +$txtOut.ForeColor = [System.Drawing.Color]::White +$txtOut.BorderStyle = "FixedSingle" +$txtOut.Text = (Join-Path $PSScriptRoot "export.csv") +$form.Controls.Add($txtOut) + +$btnBrowse = New-Object System.Windows.Forms.Button +$btnBrowse.Text = "Speichern..." +$btnBrowse.Location = New-Object System.Drawing.Point(550, 54) +$btnBrowse.Size = New-Object System.Drawing.Size(96, 26) +$btnBrowse.BackColor = [System.Drawing.Color]::FromArgb(0, 122, 204) +$btnBrowse.ForeColor = [System.Drawing.Color]::White +$btnBrowse.FlatStyle = "Flat" +$btnBrowse.FlatAppearance.BorderSize = 0 +$form.Controls.Add($btnBrowse) + +$btnBrowse.Add_Click({ + $sfd = New-Object System.Windows.Forms.SaveFileDialog + $sfd.Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*" + $sfd.Title = "CSV-Datei speichern" + $sfd.FileName = "export.csv" + if ($sfd.ShowDialog() -eq "OK") { + $txtOut.Text = $sfd.FileName + } +}) + +# --- Server row --- +$lblServer = New-Object System.Windows.Forms.Label +$lblServer.Text = "DC (optional):" +$lblServer.Location = New-Object System.Drawing.Point($pad, 96) +$lblServer.Size = New-Object System.Drawing.Size(84, 22) +$form.Controls.Add($lblServer) + +$txtServer = New-Object System.Windows.Forms.TextBox +$txtServer.Location = New-Object System.Drawing.Point(104, 94) +$txtServer.Size = New-Object System.Drawing.Size(436, 22) +$txtServer.BackColor = [System.Drawing.Color]::FromArgb(45, 45, 45) +$txtServer.ForeColor = [System.Drawing.Color]::White +$txtServer.BorderStyle = "FixedSingle" +$txtServer.ForeColor = [System.Drawing.Color]::Gray +$txtServer.Text = "Domänencontroller (leer = Standard)" +$form.Controls.Add($txtServer) + +$txtServer.Add_GotFocus({ + if ($txtServer.ForeColor -eq [System.Drawing.Color]::Gray) { + $txtServer.Text = '' + $txtServer.ForeColor = [System.Drawing.Color]::White + } +}) +$txtServer.Add_LostFocus({ + if ([string]::IsNullOrWhiteSpace($txtServer.Text)) { + $txtServer.Text = "Domänencontroller (leer = Standard)" + $txtServer.ForeColor = [System.Drawing.Color]::Gray + } +}) + +# --- Separator --- +$sep = New-Object System.Windows.Forms.Panel +$sep.Location = New-Object System.Drawing.Point($pad, 130) +$sep.Size = New-Object System.Drawing.Size(632, 1) +$sep.BackColor = [System.Drawing.Color]::FromArgb(70, 70, 70) +$form.Controls.Add($sep) + +# --- Export button --- +$script:btnExport = New-Object System.Windows.Forms.Button +$script:btnExport.Text = "Export starten" +$script:btnExport.Location = New-Object System.Drawing.Point($pad, 140) +$script:btnExport.Size = New-Object System.Drawing.Size(632, 34) +$script:btnExport.BackColor = [System.Drawing.Color]::FromArgb(16, 124, 16) +$script:btnExport.ForeColor = [System.Drawing.Color]::White +$script:btnExport.FlatStyle = "Flat" +$script:btnExport.FlatAppearance.BorderSize = 0 +$script:btnExport.Font = New-Object System.Drawing.Font('Segoe UI', 10, [System.Drawing.FontStyle]::Bold) +$form.Controls.Add($script:btnExport) + +$script:btnExport.Add_Click({ + $groupName = $cmbGroup.Text.Trim() + $outputPath = $txtOut.Text.Trim() + $server = if ($txtServer.ForeColor -ne [System.Drawing.Color]::Gray) { $txtServer.Text.Trim() } else { '' } + + if (-not $groupName) { + [System.Windows.Forms.MessageBox]::Show("Bitte eine AD-Gruppe eingeben oder auswählen.", "Validierung", "OK", "Warning") | Out-Null + return + } + if (-not $outputPath) { + [System.Windows.Forms.MessageBox]::Show("Bitte einen Speicherpfad für die CSV angeben.", "Validierung", "OK", "Warning") | Out-Null + return + } + + Start-Export -GroupName $groupName -OutputPath $outputPath -Server $server +}) + +# --- Output panel --- +$lblOutput = New-Object System.Windows.Forms.Label +$lblOutput.Text = "Output:" +$lblOutput.Location = New-Object System.Drawing.Point($pad, 186) +$lblOutput.Size = New-Object System.Drawing.Size(80, 18) +$form.Controls.Add($lblOutput) + +$script:rtbOutput = New-Object System.Windows.Forms.RichTextBox +$script:rtbOutput.Location = New-Object System.Drawing.Point($pad, 206) +$script:rtbOutput.Size = New-Object System.Drawing.Size(632, 320) +$script:rtbOutput.BackColor = [System.Drawing.Color]::FromArgb(12, 12, 12) +$script:rtbOutput.ForeColor = [System.Drawing.Color]::White +$script:rtbOutput.Font = New-Object System.Drawing.Font('Consolas', 9) +$script:rtbOutput.ReadOnly = $true +$script:rtbOutput.BorderStyle = "None" +$script:rtbOutput.ScrollBars = "Vertical" +$form.Controls.Add($script:rtbOutput) + +# --- Status bar --- +$lblStatus = New-Object System.Windows.Forms.Label +$lblStatus.Text = "Log: $LogPath" +$lblStatus.Location = New-Object System.Drawing.Point($pad, 536) +$lblStatus.Size = New-Object System.Drawing.Size(632, 18) +$lblStatus.ForeColor = [System.Drawing.Color]::Gray +$lblStatus.Font = New-Object System.Drawing.Font('Segoe UI', 8) +$form.Controls.Add($lblStatus) + +# --------------------------------------------------------------------------- +$form.ShowDialog() | Out-Null +$form.Dispose()