using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Security; using System.Text; using System.Threading.Tasks; using System.Management.Automation; using System.Management.Automation.Runspaces; using C4IT.Logging; using static C4IT.Logging.cLogManager; using C4IT.Matrix42.ServerInfo; using System.Text.RegularExpressions; using LiamNtfs; using System.DirectoryServices; using System.Security.Principal; namespace C4IT.LIAM { public class cLiamProviderExchange : cLiamProviderBase { public static Guid exchangeModuleId = new Guid("A1E213C3-6517-EA11-4881-000C2980FD95"); public readonly ExchangeManager exchangeManager; internal readonly cActiveDirectoryBase activeDirectoryBase = new cActiveDirectoryBase(); private string exchangeUri; private PSCredential credential; private string organizationalUnit; private string lastErrorCode = string.Empty; private string lastErrorMessage; private bool isLoggedOn = false; public cLiamProviderExchange(cLiamConfiguration LiamConfiguration, cLiamProviderData ProviderData) : base(LiamConfiguration, ProviderData) { exchangeUri = ProviderData.RootPath; if (!string.IsNullOrEmpty(ProviderData.GroupPath)) organizationalUnit = ProviderData.GroupPath; else organizationalUnit = ProviderData.Domain; // Credential erstellen var securePassword = new SecureString(); foreach (char c in ProviderData.Credential.Secret) { securePassword.AppendChar(c); } credential = new PSCredential(ProviderData.Credential.Identification, securePassword); // ExchangeManager initialisieren exchangeManager = new ExchangeManager(this, exchangeUri, credential, ProviderData.Domain, organizationalUnit); // AD-Zugriff initialisieren var LI = new cNtfsLogonInfo() { Domain = Domain, User = Credential?.Identification, UserSecret = Credential?.Secret, TargetGroupPath = this.GroupPath }; // Asynchrone Initialisierung starten _ = activeDirectoryBase.LogonAsync(LI).ConfigureAwait(false); } /// /// Extrahiert den GUID-Wert aus den Properties. /// Zunächst wird versucht, "objectGUID" zu lesen – ist dieser leer, wird "GUID" verwendet. /// internal static string ExtractObjectGuid(dynamic properties) { // Erstversuch: objectGUID (wie in AD) var value = properties["objectGUID"]?.Value; if (value == null || string.IsNullOrEmpty(value.ToString())) { // Alternative: GUID (wie von den Exchange-Cmdlets zurückgegeben) value = properties["GUID"]?.Value; } if (value is byte[] guidBytes) return new Guid(guidBytes).ToString(); if (value is Guid guid) return guid.ToString(); return value?.ToString() ?? string.Empty; } public string GetLastErrorCode() { return lastErrorCode; } private void ClearLastError() { lastErrorCode = string.Empty; lastErrorMessage = string.Empty; } private void SetLastError(string code, string message) { lastErrorCode = string.IsNullOrWhiteSpace(code) ? "EXCH_UNKNOWN_ERROR" : code; lastErrorMessage = message ?? string.Empty; LogEntry($"[{lastErrorCode}] {lastErrorMessage}", LogLevels.Error); } public override async Task LogonAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { ClearLastError(); if (!cC4ITLicenseM42ESM.Instance.IsValid) { SetLastError("EXCH_LOGON_LICENSE_INVALID", "License not valid or Exchange module not licensed"); return false; } try { var testMailboxes = exchangeManager.GetSharedMailboxes( "Name -like '*'", out string errorCode, out string errorMessage); if (testMailboxes == null) { SetLastError(errorCode, $"Failed to connect to Exchange: {errorMessage}"); isLoggedOn = false; return false; } if (testMailboxes != null) { LogEntry("Successfully connected to Exchange", LogLevels.Info); isLoggedOn = true; ClearLastError(); return true; } } catch (Exception ex) { LogException(ex); SetLastError("EXCH_LOGON_EXCEPTION", $"Failed to connect to Exchange: {ex.Message}"); isLoggedOn = false; return false; } SetLastError("EXCH_LOGON_FAILED", "Unknown error connecting to Exchange"); return false; } catch (Exception E) { LogException(E); SetLastError("EXCH_LOGON_EXCEPTION", $"Exception during Exchange logon: {E.Message}"); return false; } finally { LogMethodEnd(CM); } } public override string GetLastErrorMessage() { return lastErrorMessage; } public override async Task> getDataAreasAsync(int MaxDepth = -1) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { ClearLastError(); if (!cC4ITLicenseM42ESM.Instance.IsValid) { SetLastError("EXCH_GET_DATAAREAS_LICENSE_INVALID", "License not valid or Exchange module not licensed"); return new List(); } if (!isLoggedOn && !await LogonAsync()) return null; var DataAreas = new List(); // Shared Mailboxes var sharedMailboxes = exchangeManager.GetSharedMailboxes( null, out string sharedErrorCode, out string sharedErrorMessage); if (sharedMailboxes == null) { SetLastError(sharedErrorCode, $"Failed to read shared mailboxes: {sharedErrorMessage}"); return null; } foreach (var mailbox in sharedMailboxes) { var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString(); var alias = mailbox.Properties["Alias"]?.Value?.ToString(); var primarySmtpAddress = mailbox.Properties["PrimarySmtpAddress"]?.Value?.ToString(); var objectGuid = ExtractObjectGuid(mailbox.Properties); // Filterung via Regex if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(displayName, this.DataAreaRegEx).Success) continue; var exchangeMailbox = new cLiamExchangeSharedMailbox(this, displayName, primarySmtpAddress, alias, objectGuid); await exchangeMailbox.ResolvePermissionGroupsAsync(); DataAreas.Add(exchangeMailbox); } // Distribution Groups var distributionGroups = exchangeManager.GetDistributionGroups( null, out string distErrorCode, out string distErrorMessage); if (distributionGroups == null) { SetLastError(distErrorCode, $"Failed to read distribution groups: {distErrorMessage}"); return null; } foreach (var group in distributionGroups) { var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var alias = group.Properties["Alias"]?.Value?.ToString(); var primarySmtpAddress = group.Properties["PrimarySmtpAddress"]?.Value?.ToString(); var objectGuid = ExtractObjectGuid(group.Properties); if (!string.IsNullOrEmpty(this.DataAreaRegEx) && !Regex.Match(displayName, this.DataAreaRegEx).Success) continue; var exchangeGroup = new cLiamExchangeDistributionGroup(this, displayName, primarySmtpAddress, alias, objectGuid); await exchangeGroup.ResolvePermissionGroupsAsync(); DataAreas.Add(exchangeGroup); } ClearLastError(); return DataAreas; } catch (Exception E) { LogException(E); SetLastError("EXCH_GET_DATAAREAS_EXCEPTION", E.Message); } finally { LogMethodEnd(CM); } return null; } public override async Task LoadDataArea(string UID) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var dataType = cLIAMHelper.getUidItem(ref UID); if (string.IsNullOrEmpty(dataType)) return null; if (!int.TryParse(dataType, out int dataTypeInt)) return null; var primarySmtpAddress = cLIAMHelper.getUidItem(ref UID); if (string.IsNullOrEmpty(primarySmtpAddress)) return null; if (!isLoggedOn && !await LogonAsync()) return null; switch ((eLiamDataAreaTypes)dataTypeInt) { case eLiamDataAreaTypes.ExchangeSharedMailbox: return await cLiamExchangeSharedMailbox.Load(this, primarySmtpAddress); case eLiamDataAreaTypes.ExchangeDistributionGroup: return await cLiamExchangeDistributionGroup.Load(this, primarySmtpAddress); default: return null; } } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> getSecurityGroupsAsync(string groupFilter) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { ClearLastError(); if (!cC4ITLicenseM42ESM.Instance.IsValid) { SetLastError("EXCH_GET_SECURITYGROUPS_LICENSE_INVALID", "License not valid or Exchange module not licensed"); return new List(); } if (!isLoggedOn && !await LogonAsync()) return null; var securityGroups = new List(); var groups = exchangeManager.GetSecurityGroups( groupFilter, out string errorCode, out string errorMessage); if (groups == null) { SetLastError(errorCode, $"Failed to read security groups: {errorMessage}"); return null; } foreach (var group in groups) { var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var sid = group.Properties["Sid"]?.Value?.ToString(); var dn = group.Properties["DistinguishedName"]?.Value?.ToString(); var objectGuid = ExtractObjectGuid(group.Properties); if (!string.IsNullOrEmpty(this.GroupRegEx) && !Regex.Match(displayName, this.GroupRegEx).Success) continue; var securityGroup = new cLiamExchangeSecurityGroup(this, displayName, sid, dn, objectGuid); securityGroups.Add(securityGroup); } ClearLastError(); return securityGroups; } catch (Exception E) { LogException(E); SetLastError("EXCH_GET_SECURITYGROUPS_EXCEPTION", E.Message); } finally { LogMethodEnd(CM); } return null; } // Hilfsmethoden zur Interaktion mit Exchange internal async Task AddMemberToGroup(string groupName, string member, bool isSharedMailbox) { try { if (isSharedMailbox) exchangeManager.AddMailboxPermission(groupName, member); else exchangeManager.AddMemberToDistributionGroup(groupName, member); return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error adding member to group: {ex.Message}"; return false; } } internal async Task RemoveMemberFromGroup(string groupName, string member, bool isSharedMailbox) { try { if (isSharedMailbox) exchangeManager.RemoveMailboxPermission(groupName, member); else exchangeManager.RemoveMemberFromDistributionGroup(groupName, member); return true; } catch (Exception ex) { LogException(ex); lastErrorMessage = $"Error removing member from group: {ex.Message}"; return false; } } public async Task GetManagedByGroup(string groupDn) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (activeDirectoryBase.adContext == null) { LogEntry("Active Directory context not initialized", LogLevels.Error); return null; } string ldapPath = $"LDAP://{groupDn}"; DirectoryEntry groupEntry = null; try { groupEntry = new DirectoryEntry( ldapPath, this.Credential?.Identification, this.Credential?.Secret, AuthenticationTypes.Secure | AuthenticationTypes.Sealing); if (groupEntry.Properties.Contains("managedBy") && groupEntry.Properties["managedBy"].Value != null) { string managedByDn = groupEntry.Properties["managedBy"].Value.ToString(); string managedByLdapPath = $"LDAP://{managedByDn}"; using (DirectoryEntry managedByEntry = new DirectoryEntry( managedByLdapPath, this.Credential?.Identification, this.Credential?.Secret, AuthenticationTypes.Secure | AuthenticationTypes.Sealing)) { if (managedByEntry.SchemaClassName == "group") { byte[] sidBytes = (byte[])managedByEntry.Properties["objectSid"].Value; SecurityIdentifier sid = new SecurityIdentifier(sidBytes, 0); string displayName = managedByEntry.Properties["displayName"]?.Value?.ToString() ?? managedByEntry.Properties["name"]?.Value?.ToString(); return new cLiamExchangeSecurityGroup(this, displayName, sid.Value, managedByDn, sid.Value); } } } } catch (Exception ex) { LogException(ex); } finally { groupEntry?.Dispose(); } return null; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } } public class cLiamExchangeSecurityGroup : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string sid = null; public readonly string dn = null; // objectGuid wird nun als TechnicalName genutzt public cLiamExchangeSecurityGroup(cLiamProviderExchange Provider, string displayName, string sid, string dn, string objectGuid) : base(Provider) { this.Provider = Provider; this.TechnicalName = displayName; this.DisplayName = displayName; this.UID = sid; this.sid = sid; this.dn = dn; } public override Task> getChildrenAsync(int Depth = -1) { return Task.FromResult(new List()); } } public class cLiamExchangeSharedMailbox : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string PrimarySmtpAddress = null; public readonly string Alias = null; public string OwnerGroupIdentifier = "S-1-0-0"; public string FullAccessGroupSid = "S-1-0-0"; public string SendAsGroupSid = "S-1-0-0"; // objectGuid wird für TechnicalName genutzt public cLiamExchangeSharedMailbox(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias, string objectGuid) : base(Provider) { this.Provider = Provider; this.TechnicalName = objectGuid; this.DisplayName = displayName; this.PrimarySmtpAddress = primarySmtpAddress; this.Alias = alias; this.UID = getUID(primarySmtpAddress); this.Level = 0; this.DataType = eLiamDataAreaTypes.ExchangeSharedMailbox; this.SupportsOwners = true; this.SupportsPermissions = true; } internal static string getUID(string primarySmtpAddress) { return $"{(int)eLiamDataAreaTypes.ExchangeSharedMailbox}|{primarySmtpAddress}"; } public static async Task Load(cLiamProviderExchange Provider, string primarySmtpAddress) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var mailbox = Provider.exchangeManager.GetSharedMailboxByAddress(primarySmtpAddress); if (mailbox == null) return null; var displayName = mailbox.Properties["DisplayName"]?.Value?.ToString(); var alias = mailbox.Properties["Alias"]?.Value?.ToString(); var objectGuid = cLiamProviderExchange.ExtractObjectGuid(mailbox.Properties); var mailboxDataArea = new cLiamExchangeSharedMailbox(Provider, displayName, primarySmtpAddress, alias, objectGuid); await mailboxDataArea.ResolvePermissionGroupsAsync(); return mailboxDataArea; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> GetOwnersAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var members = Provider.exchangeManager.GetMailboxPermissionMembers(this.TechnicalName); if (members == null) return new List(); var ownersList = new List(); foreach (var member in members) { var userInfo = new cLiamUserInfo { DisplayName = member.Properties["DisplayName"]?.Value?.ToString(), UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(), EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(), SID = member.Properties["Sid"]?.Value?.ToString() }; ownersList.Add(userInfo); } return ownersList; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { bool success = false; switch (Role) { case eLiamAccessRoles.Owner: success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, true); break; case eLiamAccessRoles.Write: success = await Provider.exchangeManager.AddSendAsPermission(this.DisplayName, User.UserPrincipalName); break; default: LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning); return new cLiamPermissionResult { Valid = false }; } return new cLiamPermissionResult { Valid = success, UserReference = User.UserPrincipalName }; } catch (Exception E) { LogException(E); return new cLiamPermissionResult { Valid = false }; } finally { LogMethodEnd(CM); } } public override async Task RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { bool success = false; switch (Role) { case eLiamAccessRoles.Owner: success = await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, true); break; case eLiamAccessRoles.Write: success = await Provider.exchangeManager.RemoveSendAsPermission(this.DisplayName, User.UserPrincipalName); break; default: LogEntry($"Unsupported permission role for Exchange mailbox: {Role}", LogLevels.Warning); return false; } return success; } catch (Exception E) { LogException(E); return false; } finally { LogMethodEnd(CM); } } public override async Task> getChildrenAsync(int Depth = -1) { return new List(); } public async Task ResolvePermissionGroupsAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var fullAccessNamingConvention = Provider.NamingConventions.FirstOrDefault(i => i.AccessRole == eLiamAccessRoles.ExchangeSMBFullAccess); var sendAsNamingConvention = Provider.NamingConventions.FirstOrDefault(i => i.AccessRole == eLiamAccessRoles.ExchangeSMBSendAs); if (fullAccessNamingConvention == null || sendAsNamingConvention == null) { LogEntry("Naming conventions for Exchange mailbox permissions not found", LogLevels.Warning); return; } try { var fullAccessPermissions = Provider.exchangeManager.GetFullAccessPermissionGroups(this.TechnicalName); if (fullAccessPermissions != null) { foreach (var permission in fullAccessPermissions) { string samAccountName = permission.Properties["SamAccountName"]?.Value?.ToString(); string sid = permission.Properties["Sid"]?.Value?.ToString(); if (!string.IsNullOrEmpty(samAccountName) && !string.IsNullOrEmpty(sid)) { //if (Regex.IsMatch(samAccountName, fullAccessNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { this.FullAccessGroupSid = sid; LogEntry($"Found FullAccess group {samAccountName} (SID: {sid}) for mailbox {this.DisplayName}", LogLevels.Debug); string dn = permission.Properties["DistinguishedName"]?.Value?.ToString(); if (!string.IsNullOrEmpty(dn)) { var managedByGroup = await Provider.GetManagedByGroup(dn); if (managedByGroup != null) { this.OwnerGroupIdentifier = managedByGroup.sid; LogEntry($"Found owner group {managedByGroup.TechnicalName} (SID: {managedByGroup.sid}) for mailbox {this.DisplayName}", LogLevels.Debug); } } } } } } var sendAsPermissions = Provider.exchangeManager.GetSendAsPermissionGroups(this.TechnicalName); if (sendAsPermissions != null) { foreach (var permission in sendAsPermissions) { string recipientType = permission.Properties["RecipientType"]?.Value?.ToString(); string samAccountName = permission.Properties["SamAccountName"]?.Value?.ToString(); string sid = permission.Properties["Sid"]?.Value?.ToString(); if (!string.IsNullOrEmpty(samAccountName) && !string.IsNullOrEmpty(sid)) { //if (Regex.IsMatch(samAccountName, sendAsNamingConvention.Wildcard, RegexOptions.IgnoreCase)) { this.SendAsGroupSid = sid; LogEntry($"Found SendAs group {samAccountName} (SID: {sid}) for mailbox {this.DisplayName}", LogLevels.Debug); } } } } } catch (Exception ex) { LogException(ex); } } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } } } public class cLiamExchangeDistributionGroup : cLiamDataAreaBase { public new readonly cLiamProviderExchange Provider = null; public readonly string PrimarySmtpAddress = null; public readonly string Alias = null; public string OwnerGroupIdentifier = "S-1-0-0"; public string MemberGroupSid = "S-1-0-0"; // objectGuid wird als TechnicalName gesetzt public cLiamExchangeDistributionGroup(cLiamProviderExchange Provider, string displayName, string primarySmtpAddress, string alias, string objectGuid) : base(Provider) { this.Provider = Provider; this.TechnicalName = objectGuid; this.DisplayName = displayName; this.PrimarySmtpAddress = primarySmtpAddress; this.Alias = alias; this.UID = getUID(primarySmtpAddress); this.Level = 0; this.DataType = eLiamDataAreaTypes.ExchangeDistributionGroup; this.SupportsOwners = true; this.SupportsPermissions = true; } internal static string getUID(string primarySmtpAddress) { return $"{(int)eLiamDataAreaTypes.ExchangeDistributionGroup}|{primarySmtpAddress}"; } public static async Task Load(cLiamProviderExchange Provider, string primarySmtpAddress) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var group = Provider.exchangeManager.GetDistributionGroupByAddress(primarySmtpAddress); if (group == null) return null; var displayName = group.Properties["DisplayName"]?.Value?.ToString(); var alias = group.Properties["Alias"]?.Value?.ToString(); var objectGuid = cLiamProviderExchange.ExtractObjectGuid(group.Properties); var groupDataArea = new cLiamExchangeDistributionGroup(Provider, displayName, primarySmtpAddress, alias, objectGuid); await groupDataArea.ResolvePermissionGroupsAsync(); return groupDataArea; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task> GetOwnersAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var members = Provider.exchangeManager.GetDistributionGroupMembers(this.TechnicalName); if (members == null) return new List(); var membersList = new List(); foreach (var member in members) { var userInfo = new cLiamUserInfo { DisplayName = member.Properties["DisplayName"]?.Value?.ToString(), UserPrincipalName = member.Properties["UserPrincipalName"]?.Value?.ToString(), EMail = member.Properties["PrimarySmtpAddress"]?.Value?.ToString(), SID = member.Properties["Sid"]?.Value?.ToString() }; membersList.Add(userInfo); } return membersList; } catch (Exception E) { LogException(E); return null; } finally { LogMethodEnd(CM); } } public override async Task GrantPermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (Role != eLiamAccessRoles.Owner) { LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning); return new cLiamPermissionResult { Valid = false }; } bool success = await Provider.AddMemberToGroup(this.DisplayName, User.UserPrincipalName, false); return new cLiamPermissionResult { Valid = success, UserReference = User.UserPrincipalName }; } catch (Exception E) { LogException(E); return new cLiamPermissionResult { Valid = false }; } finally { LogMethodEnd(CM); } } public override async Task RevokePermissionAsync(cLiamUserInfo User, eLiamAccessRoles Role) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { if (Role != eLiamAccessRoles.Owner) { LogEntry($"Only Owner role is supported for distribution groups, requested: {Role}", LogLevels.Warning); return false; } return await Provider.RemoveMemberFromGroup(this.DisplayName, User.UserPrincipalName, false); } catch (Exception E) { LogException(E); return false; } finally { LogMethodEnd(CM); } } public override async Task> getChildrenAsync(int Depth = -1) { return new List(); } public async Task ResolvePermissionGroupsAsync() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var namingConvention = Provider.NamingConventions .FirstOrDefault(nc => nc.AccessRole == eLiamAccessRoles.ExchangeMLMember); if (namingConvention == null) { LogEntry("Naming convention for DL-Member not found", LogLevels.Warning); return; } try { // ruft alle Gruppen auf dem DL ab var memberGroups = Provider.exchangeManager .GetDistributionGroupMembers(this.TechnicalName); foreach (var group in memberGroups) { var sam = group.Properties["SamAccountName"]?.Value?.ToString(); var sid = group.Properties["Sid"]?.Value?.ToString(); if (string.IsNullOrEmpty(sam) || string.IsNullOrEmpty(sid)) continue; // falls gewünscht: Filter nach Namenskonvention // if (!Regex.IsMatch(sam, namingConvention.Wildcard, RegexOptions.IgnoreCase)) // continue; // hier beispielsweise in eine List MemberGroupSids aufnehmen this.MemberGroupSid = sid; LogEntry($"Found DL-member group {sam} (SID: {sid}) for distribution list {this.DisplayName}", LogLevels.Debug); // optional: falls die Gruppe ein ManagedBy hat var dn = group.Properties["DistinguishedName"]?.Value?.ToString(); if (!string.IsNullOrEmpty(dn)) { var mgr = await Provider.GetManagedByGroup(dn); if (mgr != null) { this.OwnerGroupIdentifier = mgr.sid; LogEntry($"Found owner group {mgr.TechnicalName} (SID: {mgr.sid}) for distribution list {this.DisplayName}", LogLevels.Debug); } } } } catch (Exception ex) { LogException(ex); } } catch (Exception E) { LogException(E); } finally { LogMethodEnd(CM); } } } }