Created
May 13, 2025 10:37
-
-
Save Vile-Gangster/04fae3449a3881d882871d72321a209d to your computer and use it in GitHub Desktop.
Template_loc_group_manage_by_AD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Verwaltet lokale Gruppenmitgliedschaften auf Servern nach dem AGDLP-Modell. | |
.DESCRIPTION | |
Dieses Skript automatisiert die Verwaltung von serverlokalen Gruppen durch: | |
- Erstellung von AD-Gruppen pro Server (Globale Gruppe + Domänenlokale Gruppe) gemäß AGDLP | |
- Migration direkt zugewiesener Domänenbenutzer in die entsprechende Globale Gruppe (GG) | |
- Verschachtelung der Domänenlokalen Gruppe (DLG) in die lokale Zielgruppe auf jedem Server | |
- Entfernung von Benutzern aus lokalen Gruppen, sobald sie über GG/DLG verwaltet werden | |
- Erstellung eines detaillierten Berichts über alle Änderungen (hinzugefügt, entfernt, erstellt) | |
- Optionaler Versand eines HTML-Berichts per E-Mail mit Fehlerhervorhebung und CSV-Anhang | |
- Unterstützung für Tiered-Admin-Umgebungen (Tier-0/Tier-1) | |
Hinweis: Für Tier-1-Systeme muss das Skript mit Tier-1-Berechtigungen oder als NT AUTHORITY\SYSTEM ausgeführt werden. | |
.PARAMETER GroupSuffix | |
Logischer Bezeichner für Gruppennamen (z. B. "DCOM" wird zu G_<Server>_DCOM und L_<Server>_DCOM). | |
.PARAMETER LocalGroupSIDEnd | |
SID-Suffix zur Identifizierung der lokalen Zielgruppe (562 = Distributed COM-Benutzer). | |
.PARAMETER EnableMailNotification | |
Aktiviert ($true) oder deaktiviert ($false) den Versand eines HTML-Zusammenfassungs-E-Mails. | |
.PARAMETER WhatIf | |
Simuliert Änderungen ohne sie anzuwenden (Trockenlauf-Modus). | |
.NOTES | |
Author : IT-Administration | |
Version : 2.2 | |
Date : 2025-05-09 | |
Changes : E-Mail-Versand aktiviert; Remoting für Tier-1-Systeme entfernt; ADSI für lokale Operationen verwendet; explizite Lokale Gruppe "Distributed COM-Benutzer"; verbesserte Fehlerbehandlung für SMTP; Tiering-Hinweise hinzugefügt; konsistente ${}-Verwendung; Benutzeranzeige im Format "Nachname, Vorname" für alle Aktionen; Fehler bei Benutzerentfernung aus lokaler Gruppe behoben; Syntaxfehler in Userlist-Log-Ausgabe korrigiert; Join-String durch -join ersetzt für PowerShell 5.1-Kompatibilität; E-Mail-Versand bei keine Änderungen deaktiviert; E-Mail-Optik angepasst: "hinzugefügt" grün, "entfernt" rot, "verschachtelt" orange; E-Mail-Farben korrigiert: angepasste -like-Bedingungen für korrekte Klassen-Zuweisung; "DLG lokal hinzugefügt" nun orange statt grün. | |
.EXAMPLE | |
.\Template_loc_group_manage_by_AD.ps1 | |
Führt das Skript mit Standardeinstellungen aus (E-Mail aktiviert). | |
.EXAMPLE | |
.\Template_loc_group_manage_by_AD.ps1 -WhatIf | |
Simuliert die Ausführung ohne Änderungen vorzunehmen. | |
#> | |
# ================================================================ | |
# ============================ CONFIG ============================ | |
[CmdletBinding()] | |
param ( | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string]$GroupSuffix = "DCOM", | |
[Parameter()] | |
[ValidatePattern("^\d+$")] | |
[string]$LocalGroupSIDEnd = "562", | |
[Parameter()] | |
[bool]$EnableMailNotification = $true, # Für Tests aktiviert | |
[Parameter()] | |
[switch]$WhatIf | |
) | |
# Ausschluss-Präfixe für AD-Konten | |
$ExcludedPrefixes = @('svc_', 'gMSA_') | |
# AD-Gruppen Präfixe | |
$GGPräfix = "G_" | |
$DLGPräfix = "L_" | |
# AD-Gruppen Beschreibung | |
$GGDescription = "Verwaltung der Server lokalen ${GroupSuffix} Gruppe" | |
$DLGDescription = "Verwaltung der Server lokalen ${GroupSuffix} Gruppe" | |
# OU mit den zu überprüfenden Servern | |
$ServerOU = "OU=Test,OU=T0,OU=Server,OU=_Administration,DC=TestLab,DC=internal" | |
# OUs für Gruppen | |
$DLGroupOU = "OU=Monitoring,OU=Lokale Gruppen,OU=Gruppen,OU=_Administration,DC=TestLab,DC=internal" | |
$GGGroupOU = "OU=Monitoring,OU=Globale Gruppen,OU=Gruppen,OU=_Administration,DC=TestLab,DC=internal" | |
# Gruppenkonfiguration | |
$GCategory = "Security" | |
$GGScope = "Global" | |
$DLGScope = "DomainLocal" | |
# NetBIOS-Name der Domäne | |
$SMBDomainName = "TestLab" | |
# Name der lokalen Gruppe | |
$LocalGroupNameOverride = "Distributed COM-Benutzer" | |
# Mail-Konfiguration | |
$MailSender = "ADScripts" | |
$MailRecipient = "IT-Administration" | |
$SMTPSrvName = "mail" | |
$SMTPDomain = "TestLab.de" | |
$SMTPServer = "${SMTPSrvName}.${SMTPDomain}" | |
$SMTPPort = 25 | |
$SMTPFrom = "${MailSender}-${GroupSuffix}@${SMTPDomain}" | |
$SMTPTo = "${MailRecipient}@${SMTPDomain}" | |
# SMTP-Authentifizierung für Port 587 | |
if ($SMTPPort -eq 587) { | |
$SMTPUser = "${SMBDomainName}\gMSA_SRV-DC-01$" | |
$SMTPPassword = ConvertTo-SecureString "" -AsPlainText -Force | |
$Credential = New-Object System.Management.Automation.PSCredential ($SMTPUser, $SMTPPassword) | |
} | |
# ============================ MELDUNGEN & TAGS ============================ | |
$TAG_New = 'Neue' | |
$TAG_Added = 'hinzugefügt' | |
$TAG_Removed = 'entfernt' | |
$TAG_Error = 'FEHLER' | |
$MSG_Create_GG = "${TAG_New} GG erstellt" | |
$MSG_Create_GG_Fail = "${TAG_Error} beim Erstellen GG" | |
$MSG_Create_DLG = "${TAG_New} DLG erstellt" | |
$MSG_Create_DLG_Fail = "${TAG_Error} beim Erstellen DLG" | |
$MSG_Nest_GG_Into_DLG = "GG in DLG verschachtelt" | |
$MSG_Nest_GG_Into_DLG_Fail = "${TAG_Error} beim Verschachteln GG in DLG" | |
$MSG_LocalGroup_NotFound = "${TAG_Error} Lokale Gruppe nicht gefunden" | |
$MSG_Read_LocalGroup_Fail = "${TAG_Error} bei Auslesen lokaler Gruppe" | |
$MSG_AddUser_To_GG = "Benutzer zur GG ${TAG_Added}" | |
$MSG_AddUser_Fail = "${TAG_Error} bei Benutzerübernahme" | |
$MSG_Add_DLG_Local = "DLG lokal ${TAG_Added}" | |
$MSG_Add_DLG_Fail = "${TAG_Error} beim lokalen Hinzufügen DLG" | |
$MSG_Remove_LocalUser = "Benutzer lokal ${TAG_Removed}" | |
$MSG_Remove_User_Fail = "${TAG_Error} beim Entfernen aus lokaler Gruppe" | |
$SUBJECT_PREFIX = "Gruppe" | |
$SUBJECT_TAG_New = "[Neue Gruppen]" | |
$SUBJECT_TAG_Changes = "[Mitgliederänderung]" | |
$SUBJECT_TAG_Errors = "[Fehler]" | |
# === Protokollierungsfunktion === | |
function Write-Log { | |
param ( | |
[string]$Message, | |
[string]$Level = "Info" | |
) | |
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" | |
$color = switch ($Level) { | |
"Error" { "Red" } | |
"Warning" { "Yellow" } | |
"Success" { "Green" } | |
default { "Cyan" } | |
} | |
Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color | |
Add-Content -Path ".\GroupManagement.log" -Value "[$timestamp] [$Level] $Message" | |
} | |
# Container für Berichtszeilen | |
$ReportData = @() | |
$ChangesDetected = $false | |
# ================================================================ | |
# ============================ START ============================= | |
try { | |
$ServerList = Get-ADComputer -Filter * -SearchBase $ServerOU -ErrorAction Stop | |
Write-Log -Message "Serverliste aus OU ${ServerOU} erfolgreich abgerufen." -Level Success | |
} catch { | |
Write-Log -Message "Fehler beim Abrufen der Serverliste: $($_.Exception.Message)" -Level Error | |
exit 1 | |
} | |
foreach ($Server in $ServerList) { | |
$ComputerName = $Server.Name | |
$DLGroupName = "${DLGPräfix}${ComputerName}_${GroupSuffix}" | |
$GGGroupName = "${GGPräfix}${ComputerName}_${GroupSuffix}" | |
$Userlist = @() | |
Write-Log -Message "Verarbeite Server: ${ComputerName}" -Level Info | |
# === Globale Gruppe prüfen/erstellen === | |
try { | |
$group = Get-ADGroup -Identity ${GGGroupName} -ErrorAction Stop | |
Write-Log -Message "Globale Gruppe ${GGGroupName} existiert bereits." -Level Info | |
} catch { | |
if (-not $WhatIf) { | |
New-ADGroup ` | |
-Name ${GGGroupName} ` | |
-SamAccountName ${GGGroupName} ` | |
-GroupCategory $GCategory ` | |
-GroupScope $GGScope ` | |
-DisplayName ${GGGroupName} ` | |
-Path $GGGroupOU ` | |
-Description $GGDescription ` | |
-ErrorAction Stop | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_Create_GG | |
Objekt = ${GGGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Globale Gruppe ${GGGroupName} erstellt." -Level Success | |
} else { | |
Write-Log -Message "Würde globale Gruppe ${GGGroupName} erstellen." -Level Info | |
} | |
} | |
# === Domänenlokale Gruppe prüfen/erstellen === | |
try { | |
$group = Get-ADGroup -Identity ${DLGroupName} -ErrorAction Stop | |
Write-Log -Message "Domänenlokale Gruppe ${DLGroupName} existiert bereits." -Level Info | |
} catch { | |
if (-not $WhatIf) { | |
New-ADGroup ` | |
-Name ${DLGroupName} ` | |
-SamAccountName ${DLGroupName} ` | |
-GroupCategory $GCategory ` | |
-GroupScope $DLGScope ` | |
-DisplayName ${DLGroupName} ` | |
-Path $DLGroupOU ` | |
-Description $DLGDescription ` | |
-ErrorAction Stop | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_Create_DLG | |
Objekt = ${DLGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Domänenlokale Gruppe ${DLGroupName} erstellt." -Level Success | |
} else { | |
Write-Log -Message "Würde domänenlokale Gruppe ${DLGroupName} erstellen." -Level Info | |
} | |
} | |
# === GG in DLG verschachteln === | |
try { | |
$isMember = Get-ADGroupMember -Identity ${DLGroupName} -ErrorAction Stop | | |
Where-Object { $_.SamAccountName -eq ${GGGroupName} } | |
if (-not $isMember) { | |
if (-not $WhatIf) { | |
Add-ADGroupMember ` | |
-Identity ${DLGroupName} ` | |
-Members ${GGGroupName} ` | |
-ErrorAction Stop | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_Nest_GG_Into_DLG | |
Objekt = ${GGGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Globale Gruppe ${GGGroupName} in ${DLGroupName} verschachtelt." -Level Success | |
} else { | |
Write-Log -Message "Würde ${GGGroupName} in ${DLGroupName} verschachteln." -Level Info | |
} | |
} | |
} catch { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_Nest_GG_Into_DLG_Fail}: $($_.Exception.Message)" | |
Objekt = ${GGGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim Verschachteln von ${GGGroupName} in ${DLGroupName}: $($_.Exception.Message)" -Level Error | |
} | |
# === Lokale Gruppe festlegen === | |
try { | |
$LocalGroupName = $LocalGroupNameOverride | |
Write-Log -Message "Lokale Gruppe festgelegt: ${LocalGroupName} (SID-Ende: ${LocalGroupSIDEnd})" -Level Info | |
} catch { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_LocalGroup_NotFound}: $($_.Exception.Message)" | |
Objekt = "SID-Ende -${LocalGroupSIDEnd}" | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim Festlegen der lokalen Gruppe: $($_.Exception.Message)" -Level Error | |
continue | |
} | |
# === Mitglieder der lokalen Gruppe auslesen (ADSI) === | |
try { | |
$LocalGroupObj = [ADSI]"WinNT://${ComputerName}/${LocalGroupName},group" | |
$rawMembers = $LocalGroupObj.psbase.Invoke("Members") | ForEach-Object { | |
$name = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) | |
$adsPath = $_.GetType().InvokeMember("AdsPath", 'GetProperty', $null, $_, $null) | |
$class = $_.GetType().InvokeMember("Class", 'GetProperty', $null, $_, $null) | |
[PSCustomObject]@{ | |
Name = $name | |
AdsPath = $adsPath | |
Class = $class | |
} | |
} | |
$memberList = $rawMembers | ForEach-Object { "$($_.Name) ($($_.AdsPath), Class: $($_.Class))" } | |
$memberString = $memberList -join ', ' | |
Write-Log -Message "Rohe Mitglieder für ${LocalGroupName}: $memberString" -Level Info | |
$LocalAdmins = $rawMembers | Where-Object { | |
# Nur Benutzer (Class = User) und passender Domänenname | |
$_.Class -eq "User" -and $_.Name -match "^(${SMBDomainName}\\)?(.+)$" | |
} | ForEach-Object { | |
$samAccountName = if ($_.Name -match "^${SMBDomainName}\\(.+)$") { | |
$Matches[1].Trim() | |
} else { | |
$_.Name.Trim() | |
} | |
try { | |
# Hole Nachname und Vorname aus AD | |
$user = Get-ADUser -Identity $samAccountName -Properties sn, givenName -ErrorAction Stop | |
$displayName = if ($user.sn -and $user.givenName) { | |
"$($user.sn), $($user.givenName)" | |
} else { | |
$samAccountName # Fallback auf SamAccountName | |
} | |
[PSCustomObject]@{ | |
SamAccountName = $samAccountName | |
DisplayName = $displayName | |
} | |
} catch { | |
Write-Log -Message "Fehler beim Abrufen von AD-Daten für ${samAccountName}: $($_.Exception.Message). Verwende SamAccountName." -Level Warning | |
[PSCustomObject]@{ | |
SamAccountName = $samAccountName | |
DisplayName = $samAccountName | |
} | |
} | |
} | Where-Object { $_.SamAccountName } | |
$displayNames = $LocalAdmins | ForEach-Object { $_.DisplayName } | |
Write-Log -Message "Gefilterte Domänenbenutzer für ${LocalGroupName}: $($displayNames -join ', ')" -Level Info | |
} catch { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_Read_LocalGroup_Fail}: $($_.Exception.Message). Möglicherweise Netzwerk- oder Berechtigungsproblem. Führe das Skript lokal auf ${ComputerName} als NT AUTHORITY\SYSTEM aus." | |
Objekt = ${LocalGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim Auslesen der lokalen Gruppe ${LocalGroupName}: $($_.Exception.Message)." -Level Error | |
continue | |
} | |
# === Domänenbenutzer aus lokaler Gruppe → GG verschieben === | |
$Userlist = @() # Initialisiere Userlist als leeres Array | |
foreach ($user in $LocalAdmins) { | |
$samAccountName = $user.SamAccountName | |
$displayName = $user.DisplayName | |
$exclude = $false | |
foreach ($prefix in $ExcludedPrefixes) { | |
if ($samAccountName -like "${prefix}*") { | |
Write-Log -Message "Benutzer ${displayName} wird übersprungen (Ausschlusspräfix)." -Level Warning | |
$exclude = $true | |
break | |
} | |
} | |
if ($exclude) { continue } | |
try { | |
$ADUserObject = Get-ADUser -Identity ${samAccountName} -ErrorAction Stop | |
$alreadyMember = Get-ADGroupMember -Identity ${GGGroupName} -ErrorAction Stop | Where-Object { | |
$_.SamAccountName -eq ${samAccountName} | |
} | |
if (-not $alreadyMember) { | |
if (-not $WhatIf) { | |
Add-ADGroupMember ` | |
-Identity ${GGGroupName} ` | |
-Members $ADUserObject.DistinguishedName ` | |
-ErrorAction Stop | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_AddUser_To_GG | |
Objekt = ${displayName} # Verwende Nachname, Vorname | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Benutzer ${displayName} zu ${GGGroupName} hinzugefügt." -Level Success | |
} else { | |
Write-Log -Message "Würde Benutzer ${displayName} zu ${GGGroupName} hinzufügen." -Level Info | |
} | |
# Füge Benutzer mit SamAccountName und DisplayName zu Userlist hinzu | |
$Userlist += [PSCustomObject]@{ | |
SamAccountName = $samAccountName | |
DisplayName = $displayName | |
} | |
} | |
} catch { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_AddUser_Fail}: $($_.Exception.Message)" | |
Objekt = ${displayName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim Hinzufügen von ${displayName} zu ${GGGroupName}: $($_.Exception.Message)" -Level Error | |
continue | |
} | |
} | |
# === DLG in lokale Gruppe eintragen === | |
try { | |
$GroupObj = [ADSI]"WinNT://${ComputerName}/${LocalGroupName},group" | |
# SID-Pfad (einzige Methode) | |
$ADGroup = Get-ADGroup -Identity ${DLGroupName} -ErrorAction Stop | |
$ADGroupSID = $ADGroup.SID.Value | |
$ADGroupWinNT_SID = "WinNT://${ADGroupSID}" | |
# Prüfe, ob die Gruppe bereits Mitglied ist | |
$alreadyMember = $GroupObj.psbase.Invoke("Members") | ForEach-Object { | |
$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) | |
} | Where-Object { $_ -ieq ${DLGroupName} } | |
if (-not $alreadyMember) { | |
if (-not $WhatIf) { | |
# Warte, um AD-Replikation zu gewährleisten | |
Start-Sleep -Seconds 10 | |
# Prüfe Replikation auf dem Ziel-DC | |
try { | |
# Wähle den ersten HostName als String | |
$dc = (Get-ADDomainController -Discover -DomainName $SMBDomainName).HostName | Select-Object -First 1 | |
Get-ADGroup -Identity ${DLGroupName} -Server $dc -ErrorAction Stop | |
Write-Log -Message "Gruppe ${DLGroupName} auf DC ${dc} repliziert." -Level Info | |
} catch { | |
Write-Log -Message "Warnung: Gruppe ${DLGroupName} möglicherweise nicht repliziert: $($_.Exception.Message). Fahre fort, da SID-Pfad verwendet wird." -Level Warning | |
} | |
# Füge Gruppe hinzu | |
$GroupObj.Add($ADGroupWinNT_SID) | |
Write-Log -Message "DLG ${DLGroupName} lokal zu ${LocalGroupName} hinzugefügt (SID-Pfad, SID: ${ADGroupSID})." -Level Success | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_Add_DLG_Local | |
Objekt = ${DLGroupName} | |
} | |
$ChangesDetected = $true | |
} else { | |
Write-Log -Message "Würde DLG ${DLGroupName} lokal zu ${LocalGroupName} hinzufügen." -Level Info | |
} | |
} else { | |
Write-Log -Message "DLG ${DLGroupName} ist bereits Mitglied von ${LocalGroupName}." -Level Info | |
} | |
} catch { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_Add_DLG_Fail}: $($_.Exception.Message). Möglicherweise AD-Replikationsproblem oder ADSI-Beschränkung. Führe das Skript lokal auf ${ComputerName} als NT AUTHORITY\SYSTEM aus oder überprüfe die AD-Replikation." | |
Objekt = ${DLGroupName} | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim lokalen Hinzufügen von ${DLGroupName}: $($_.Exception.Message)." -Level Error | |
} | |
# === Lokale Benutzer entfernen === | |
$userListString = ($Userlist | ForEach-Object { "$($_.DisplayName) ($($_.SamAccountName))" }) -join ', ' | |
Write-Log -Message "Userlist für Entfernung: $userListString" -Level Info | |
foreach ($user in $Userlist) { | |
$samAccountName = $user.SamAccountName | |
$displayName = $user.DisplayName | |
try { | |
$LocalGroupObj = [ADSI]"WinNT://${ComputerName}/${LocalGroupName},group" | |
# Normalisiere Mitgliedernamen (entferne Domänenpräfix) | |
$members = $LocalGroupObj.psbase.Invoke("Members") | ForEach-Object { | |
$name = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) | |
if ($name -match "^${SMBDomainName}\\(.+)$") { | |
$Matches[1].Trim() | |
} else { | |
$name.Trim() | |
} | |
} | |
Write-Log -Message "Mitglieder der lokalen Gruppe ${LocalGroupName}: $($members -join ', ')" -Level Info | |
# Versuche direkt zu entfernen | |
if (-not $WhatIf) { | |
$LocalGroupObj.Remove("WinNT://${SMBDomainName}/${samAccountName}") | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = $MSG_Remove_LocalUser | |
Objekt = $displayName # Verwende Nachname, Vorname | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Benutzer ${displayName} lokal aus ${LocalGroupName} entfernt." -Level Success | |
} else { | |
Write-Log -Message "Würde Benutzer ${displayName} lokal aus ${LocalGroupName} entfernen." -Level Info | |
} | |
} catch { | |
# Ignoriere Fehler, wenn der Benutzer nicht Mitglied ist | |
if ($_.Exception.Message -match "Das angegebene Konto ist kein Mitglied der Gruppe") { | |
Write-Log -Message "Benutzer ${displayName} ist bereits nicht in ${LocalGroupName}." -Level Info | |
} else { | |
$ReportData += [PSCustomObject]@{ | |
Server = $ComputerName | |
Aktion = "${MSG_Remove_User_Fail}: $($_.Exception.Message). Möglicherweise Tier-1-Zugriffsbeschränkung. Führe das Skript mit Tier-1-Berechtigungen oder als NT AUTHORITY\SYSTEM aus." | |
Objekt = $displayName | |
} | |
$ChangesDetected = $true | |
Write-Log -Message "Fehler beim Entfernen von ${displayName} aus ${LocalGroupName}: $($_.Exception.Message). Möglicherweise Tier-1-Zugriffsbeschränkung." -Level Error | |
} | |
} | |
} | |
} | |
# ================================================================ | |
# ========================== E-MAIL SENDEN ========================= | |
if ($EnableMailNotification -and $ReportData.Count -gt 0) { | |
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss" | |
$tempFile = "${env:TEMP}\ADScripts_GroupManagement_${timestamp}.txt" | |
$subjectSuffix = "" | |
if ($ReportData | Where-Object { $_.Aktion -like "${TAG_New}*" }) { | |
$subjectSuffix += $SUBJECT_TAG_New | |
} | |
if ($ReportData | Where-Object { $_.Aktion -like "${TAG_Added}*" -or $_.Aktion -like "${TAG_Removed}*" }) { | |
$subjectSuffix += $SUBJECT_TAG_Changes | |
} | |
if ($ReportData | Where-Object { $_.Aktion -like "${TAG_Error}*" }) { | |
$subjectSuffix += $SUBJECT_TAG_Errors | |
} | |
$SMTPSubject = "[${SUBJECT_PREFIX} ${GroupSuffix}] ${subjectSuffix}" | |
$styledRows = $ReportData | ForEach-Object { | |
$rowClass = if ($_.Aktion -like "*${TAG_Error}*") { | |
' class="error"' | |
} elseif ($_.Aktion -like "*${TAG_New}*" -or ($_.Aktion -like "*${TAG_Added}*" -and $_.Aktion -ne $MSG_Add_DLG_Local)) { | |
' class="added"' | |
} elseif ($_.Aktion -like "*${TAG_Removed}*") { | |
' class="removed"' | |
} elseif ($_.Aktion -eq $MSG_Nest_GG_Into_DLG -or $_.Aktion -eq $MSG_Add_DLG_Local) { | |
' class="nested"' | |
} else { | |
'' | |
} | |
# Debug-Ausgabe, um die Zuweisung der Klassen zu überprüfen | |
Write-Log -Message "E-Mail-Zeile: Aktion '$($_.Aktion)' → CSS-Klasse '$rowClass'" -Level Info | |
"<tr${rowClass}><td>$($_.Server)</td><td>$($_.Aktion)</td><td>$($_.Objekt)</td></tr>" | |
} | |
$htmlBody = @" | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<style> | |
body { font-family: "Segoe UI", sans-serif; font-size: 13px; background-color: #f4f4f4; padding: 20px; } | |
h2 { color: #333333; font-size: 16px; } | |
table { width: 100%; border-collapse: collapse; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0,0,0,0.1); font-size: 13px; } | |
th, td { padding: 8px 12px; border-bottom: 1px solid #dddddd; text-align: left; } | |
th { background-color: #eeeeee; } | |
tr:last-child td { border-bottom: none; } | |
.error { color: #b00020; font-weight: bold; background-color: #ffe5e5; } | |
.added { color: #006600; font-weight: bold; background-color: #e6ffe6; } | |
.removed { color: #cc0000; font-weight: bold; background-color: #ffe5e5; } | |
.nested { color: #e68a00; font-weight: bold; background-color: #fff5e6; } | |
</style> | |
</head> | |
<body> | |
<h2>Gruppenverwaltung: ${GroupSuffix} ($($ReportData.Count) Änderungen)</h2> | |
<table> | |
<thead> | |
<tr> | |
<th>Server</th> | |
<th>Aktion</th> | |
<th>Objekt</th> | |
</tr> | |
</thead> | |
<tbody> | |
$($styledRows -join "`n") | |
</tbody> | |
</table> | |
</body> | |
</html> | |
"@ | |
$ReportData | Export-Csv -Path $tempFile -NoTypeInformation -Encoding UTF8 -Delimiter ";" | |
function Send-UTF8Mail { | |
param ( | |
[string]$From, | |
[string]$To, | |
[string]$Subject, | |
[string]$Body, | |
[string]$SmtpServer, | |
[int]$Port = 25, | |
[pscredential]$Credential = $null, | |
[string]$AttachmentPath | |
) | |
try { | |
$smtpClient = New-Object System.Net.Mail.SmtpClient($SmtpServer, $Port) | |
if ($Credential) { | |
$smtpClient.Credentials = $Credential | |
$smtpClient.EnableSsl = $true | |
} | |
$mailMessage = New-Object System.Net.Mail.MailMessage | |
$mailMessage.From = $From | |
$mailMessage.To.Add($To) | |
$mailMessage.Subject = $Subject | |
$mailMessage.Body = $Body | |
$mailMessage.IsBodyHtml = $true | |
$mailMessage.BodyEncoding = [System.Text.Encoding]::UTF8 | |
$mailMessage.SubjectEncoding = [System.Text.Encoding]::UTF8 | |
if ($AttachmentPath -and (Test-Path $AttachmentPath)) { | |
$attachment = New-Object System.Net.Mail.Attachment($AttachmentPath) | |
$mailMessage.Attachments.Add($attachment) | |
} | |
$smtpClient.Send($mailMessage) | |
Write-Log -Message "E-Mail erfolgreich gesendet an ${To}" -Level Success | |
} catch { | |
Write-Log -Message "Fehler beim Senden der E-Mail: $($_.Exception.Message)" -Level Error | |
} finally { | |
if ($mailMessage) { | |
if ($mailMessage.Attachments.Count -gt 0) { | |
$mailMessage.Attachments.Dispose() | |
} | |
$mailMessage.Dispose() | |
} | |
} | |
} | |
Write-Log -Message "Sende HTML-Bericht mit Anhang an ${SMTPTo} über ${SMTPServer}: Port ${SMTPPort}" -Level Info | |
try { | |
if ($SMTPPort -eq 587) { | |
Send-UTF8Mail ` | |
-From $SMTPFrom ` | |
-To $SMTPTo ` | |
-Subject $SMTPSubject ` | |
-Body $htmlBody ` | |
-SmtpServer $SMTPServer ` | |
-Port $SMTPPort ` | |
-Credential $Credential ` | |
-AttachmentPath $tempFile | |
} else { | |
Send-UTF8Mail ` | |
-From $SMTPFrom ` | |
-To $SMTPTo ` | |
-Subject $SMTPSubject ` | |
-Body $htmlBody ` | |
-SmtpServer $SMTPServer ` | |
-Port $SMTPPort ` | |
-AttachmentPath $tempFile | |
} | |
} finally { | |
if (Test-Path $tempFile) { | |
Remove-Item $tempFile -Force | |
} | |
} | |
} else { | |
Write-Log -Message "Keine Änderungen festgestellt oder E-Mail-Versand deaktiviert (EnableMailNotification=${EnableMailNotification}, ReportData.Count=$($ReportData.Count))." -Level Info | |
} | |
if ($ReportData.Count -eq 0) { | |
Write-Log -Message "Skript erfolgreich abgeschlossen, keine Änderungen erforderlich." -Level Info | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment