- update multiple LIAM projects and solution/config files - add LiamWorkflowDiagnostics app sources and generated outputs - include current workspace state (dependencies and build outputs)
1130 lines
46 KiB
C#
1130 lines
46 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Management.Automation;
|
||
using System.Management.Automation.Runspaces;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using C4IT.Logging;
|
||
using static C4IT.Logging.cLogManager;
|
||
using System.Threading.Tasks;
|
||
using System.Collections;
|
||
using System.DirectoryServices.AccountManagement;
|
||
using System.DirectoryServices.ActiveDirectory;
|
||
using System.DirectoryServices;
|
||
using System.ComponentModel;
|
||
using System.Security.AccessControl;
|
||
using System.Threading;
|
||
|
||
namespace C4IT.LIAM
|
||
{
|
||
/// <summary>
|
||
/// Manages Exchange operations using PowerShell remoting
|
||
/// </summary>
|
||
public partial class ExchangeManager
|
||
{
|
||
private static readonly TimeSpan RunspaceOpenTimeout = TimeSpan.FromSeconds(30);
|
||
|
||
private readonly cLiamProviderBase _provider;
|
||
private readonly string _exchangeUri;
|
||
private readonly PSCredential _credential;
|
||
private readonly string _organizationalUnit;
|
||
private readonly string _domain;
|
||
|
||
/// <summary>
|
||
/// Constructor with explicit credentials, Exchange URI and OU path
|
||
/// </summary>
|
||
/// <param name="exchangeUri">URL of the Exchange PowerShell endpoint (e.g. "http://exchangeserver/PowerShell")</param>
|
||
/// <param name="credential">Explicit credentials</param>
|
||
/// <param name="organizationalUnit">OU path where objects should be created</param>
|
||
public ExchangeManager(cLiamProviderBase provider, string exchangeUri, PSCredential credential, string domain, string organizationalUnit)
|
||
{
|
||
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||
_exchangeUri = exchangeUri;
|
||
_credential = credential;
|
||
_domain = domain;
|
||
_organizationalUnit = organizationalUnit;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a runspace connected to the Exchange PowerShell endpoint
|
||
/// </summary>
|
||
private Runspace CreateRunspace()
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
var connectionInfo = new WSManConnectionInfo(
|
||
new Uri(_exchangeUri),
|
||
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
|
||
_credential);
|
||
|
||
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
|
||
var runspace = RunspaceFactory.CreateRunspace(connectionInfo);
|
||
LogEntry($"Opening Exchange runspace (timeout: {RunspaceOpenTimeout.TotalSeconds:0}s) for endpoint '{_exchangeUri}'", LogLevels.Debug);
|
||
|
||
IAsyncResult openResult = null;
|
||
try
|
||
{
|
||
openResult = runspace.BeginOpen(null, null);
|
||
if (!openResult.AsyncWaitHandle.WaitOne(RunspaceOpenTimeout))
|
||
{
|
||
try
|
||
{
|
||
runspace.Dispose();
|
||
}
|
||
catch (Exception disposeEx)
|
||
{
|
||
LogException(disposeEx);
|
||
}
|
||
|
||
throw new TimeoutException(
|
||
$"Timeout while opening Exchange runspace after {RunspaceOpenTimeout.TotalSeconds:0} seconds.");
|
||
}
|
||
|
||
runspace.EndOpen(openResult);
|
||
}
|
||
finally
|
||
{
|
||
openResult?.AsyncWaitHandle?.Close();
|
||
}
|
||
|
||
LogEntry("Exchange runspace opened successfully", LogLevels.Debug);
|
||
return runspace;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
// Internal helper for creation details
|
||
private class CreationResult
|
||
{
|
||
public Guid ObjectGuid { get; set; }
|
||
public List<Tuple<string, string, string, string>> Groups { get; private set; }
|
||
|
||
public CreationResult()
|
||
{
|
||
Groups = new List<Tuple<string, string, string, string>>();
|
||
}
|
||
}
|
||
|
||
private DirectoryEntry FindAdObject(string ldapFilter)
|
||
{
|
||
using (DirectoryEntry root = new DirectoryEntry("LDAP://" + _organizationalUnit,
|
||
_credential.UserName,
|
||
SecureStringToString(_credential.Password),
|
||
AuthenticationTypes.Secure))
|
||
{
|
||
using (DirectorySearcher ds = new DirectorySearcher(root))
|
||
{
|
||
ds.Filter = ldapFilter;
|
||
ds.PropertiesToLoad.Add("objectGUID");
|
||
ds.PropertiesToLoad.Add("objectSid");
|
||
ds.PropertiesToLoad.Add("cn");
|
||
ds.PropertiesToLoad.Add("sAMAccountName");
|
||
SearchResult searchResult = ds.FindOne();
|
||
if (searchResult != null)
|
||
return searchResult.GetDirectoryEntry();
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Creates a distribution group (mailing list). Optionally members can be added initially.
|
||
/// </summary>
|
||
public void CreateDistributionGroup(string name, string alias, List<string> initialMembers = null)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("New-DistributionGroup")
|
||
.AddParameter("Name", name)
|
||
.AddParameter("Alias", alias)
|
||
.AddParameter("OrganizationalUnit", _organizationalUnit);
|
||
ps.Invoke();
|
||
|
||
if (initialMembers != null)
|
||
{
|
||
foreach (var member in initialMembers)
|
||
{
|
||
ps.Commands.Clear();
|
||
ps.AddCommand("Add-DistributionGroupMember")
|
||
.AddParameter("Identity", name)
|
||
.AddParameter("Member", member);
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a security group in Active Directory that can be used for Exchange permissions
|
||
/// – direkt per LDAP und mit den im Konstruktor übergebenen Credentials & Domain.
|
||
/// </summary>
|
||
public bool CreateSecurityGroup(eLiamAccessRoles accessRole, string baseName, string baseDescription="")
|
||
{
|
||
var CM = MethodBase.GetCurrentMethod();
|
||
LogMethodBegin(CM);
|
||
try
|
||
{
|
||
// Find the naming convention for this role
|
||
var namingConvention = _provider.NamingConventions
|
||
.FirstOrDefault(nc => nc.AccessRole == accessRole);
|
||
if (namingConvention == null)
|
||
throw new InvalidOperationException($"No naming convention found for role '{accessRole}'");
|
||
|
||
// Generate group name and description from templates
|
||
string groupName = namingConvention.NamingTemplate
|
||
.Replace("{Name}", baseName);
|
||
string groupDescription = namingConvention.DescriptionTemplate
|
||
.Replace("{Name}", baseDescription);
|
||
|
||
// Determine scope bit for groupType
|
||
int scopeBit;
|
||
switch (namingConvention.Scope)
|
||
{
|
||
case eLiamAccessRoleScopes.Global:
|
||
scopeBit = 0x2;
|
||
break;
|
||
case eLiamAccessRoleScopes.DomainLocal:
|
||
scopeBit = 0x4;
|
||
break;
|
||
case eLiamAccessRoleScopes.Universal:
|
||
scopeBit = 0x8;
|
||
break;
|
||
default:
|
||
scopeBit = 0x8;
|
||
break;
|
||
}
|
||
int groupType = unchecked((int)(0x80000000 | scopeBit));
|
||
|
||
// Create the group in AD
|
||
string ldapPath = $"LDAP://{_organizationalUnit}";
|
||
string password = new System.Net.NetworkCredential(string.Empty, _credential.Password).Password;
|
||
using (var root = new DirectoryEntry(
|
||
ldapPath,
|
||
_credential.UserName,
|
||
password,
|
||
AuthenticationTypes.Secure))
|
||
{
|
||
var newGroup = root.Children.Add($"CN={groupName}", "group");
|
||
newGroup.Properties["sAMAccountName"].Value = groupName;
|
||
newGroup.Properties["displayName"].Value = groupName;
|
||
newGroup.Properties["groupType"].Value = groupType;
|
||
newGroup.Properties["description"].Value = groupDescription;
|
||
newGroup.CommitChanges();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(CM);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all shared mailboxes matching an optional filter
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetSharedMailboxes(string filter = null)
|
||
{
|
||
var result = GetSharedMailboxes(filter, out _, out _);
|
||
return result ?? Enumerable.Empty<PSObject>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all shared mailboxes matching an optional filter.
|
||
/// Returns null on failure and exposes an error code/message.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetSharedMailboxes(string filter, out string errorCode, out string errorMessage)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
errorCode = "OK";
|
||
errorMessage = string.Empty;
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-Mailbox")
|
||
.AddParameter("RecipientTypeDetails", "SharedMailbox");
|
||
|
||
if (!string.IsNullOrEmpty(filter))
|
||
{
|
||
ps.AddParameter("Filter", filter);
|
||
}
|
||
return InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-Mailbox SharedMailbox filter='{filter ?? string.Empty}'");
|
||
}
|
||
}
|
||
catch (TimeoutException ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_SHAREDMAILBOXES_TIMEOUT";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_SHAREDMAILBOXES_FAILED";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a shared mailbox by primary SMTP address
|
||
/// </summary>
|
||
public PSObject GetSharedMailboxByAddress(string primarySmtpAddress)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-Mailbox")
|
||
.AddParameter("RecipientTypeDetails", "SharedMailbox")
|
||
.AddParameter("PrimarySmtpAddress", primarySmtpAddress);
|
||
|
||
var results = InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-Mailbox SharedMailbox address='{primarySmtpAddress}'");
|
||
return results.Count > 0 ? results[0] : null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return null;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all distribution groups matching an optional filter
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetDistributionGroups(string filter = null)
|
||
{
|
||
var result = GetDistributionGroups(filter, out _, out _);
|
||
return result ?? Enumerable.Empty<PSObject>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets all distribution groups matching an optional filter.
|
||
/// Returns null on failure and exposes an error code/message.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetDistributionGroups(string filter, out string errorCode, out string errorMessage)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
errorCode = "OK";
|
||
errorMessage = string.Empty;
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-DistributionGroup");
|
||
|
||
if (!string.IsNullOrEmpty(filter))
|
||
{
|
||
ps.AddParameter("Filter", filter);
|
||
}
|
||
return InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-DistributionGroup filter='{filter ?? string.Empty}'");
|
||
}
|
||
}
|
||
catch (TimeoutException ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_DISTRIBUTIONGROUPS_TIMEOUT";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_DISTRIBUTIONGROUPS_FAILED";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets a distribution group by primary SMTP address.
|
||
/// Die Methode bleibt unverändert.
|
||
/// </summary>
|
||
public PSObject GetDistributionGroupByAddress(string primarySmtpAddress)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-DistributionGroup")
|
||
.AddParameter("PrimarySmtpAddress", primarySmtpAddress);
|
||
|
||
var results = InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-DistributionGroup address='{primarySmtpAddress}'");
|
||
return results.Count > 0 ? results[0] : null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return null;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Gets members of a distribution group
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetDistributionGroupMembers(string distributionListIdentity)
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
// alle Members des DL holen
|
||
ps.AddCommand("Get-DistributionGroupMember")
|
||
.AddParameter("Identity", distributionListIdentity)
|
||
.AddParameter("ResultSize", "Unlimited")
|
||
.AddParameter("ErrorAction", "Stop");
|
||
var members = InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-DistributionGroupMember '{distributionListIdentity}'");
|
||
if (ps.HadErrors || members.Count == 0)
|
||
return Enumerable.Empty<PSObject>();
|
||
|
||
var result = new List<PSObject>();
|
||
foreach (var member in members)
|
||
{
|
||
// nur Gruppen (Security- oder Distributions-Gruppen)
|
||
var rt = member.Properties["RecipientTypeDetails"]?.Value?.ToString() ?? "";
|
||
if (rt.IndexOf("Group", StringComparison.OrdinalIgnoreCase) <= 0)
|
||
continue;
|
||
|
||
// SamAccountName auslesen
|
||
var name = member.Properties["Name"]?.Value?.ToString();
|
||
if (string.IsNullOrEmpty(name))
|
||
continue;
|
||
|
||
// AD-Gruppe per .NET auflösen
|
||
using (var ctx = new PrincipalContext(ContextType.Domain))
|
||
using (var grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, name))
|
||
{
|
||
if (grp == null)
|
||
continue;
|
||
|
||
// PSObject bauen
|
||
var obj = new PSObject();
|
||
obj.Properties.Add(new PSNoteProperty("SamAccountName", grp.SamAccountName));
|
||
obj.Properties.Add(new PSNoteProperty("Name", grp.Name));
|
||
obj.Properties.Add(new PSNoteProperty("Sid", grp.Sid.Value));
|
||
obj.Properties.Add(new PSNoteProperty("DistinguishedName", grp.DistinguishedName));
|
||
obj.Properties.Add(new PSNoteProperty("GroupPrincipal", grp));
|
||
result.Add(obj);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
|
||
|
||
public IEnumerable<PSObject> GetSendAsPermissionMembers(string mailboxIdentity)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
|
||
// 1) Alle AD-Permissions holen
|
||
ps.AddCommand("Get-ADPermission")
|
||
.AddParameter("Identity", mailboxIdentity);
|
||
var perms = ps.Invoke();
|
||
if (ps.HadErrors) return Enumerable.Empty<PSObject>();
|
||
|
||
var result = new List<PSObject>();
|
||
using (var ps2 = PowerShell.Create())
|
||
{
|
||
ps2.Runspace = runspace;
|
||
|
||
foreach (var perm in perms)
|
||
{
|
||
// 2) Nur explizite (nicht geerbte) Rechte
|
||
var inherited = perm.Properties["IsInherited"]?.Value as bool? ?? true;
|
||
if (inherited) continue;
|
||
|
||
// 3) ExtendedRights robust auslesen
|
||
var rawVal = perm.Properties["ExtendedRights"]?.Value;
|
||
IEnumerable<object> rights = null;
|
||
|
||
if (rawVal is IEnumerable seq && !(rawVal is string))
|
||
rights = seq.Cast<object>();
|
||
else if (rawVal is PSObject pso && pso.BaseObject is IEnumerable baseSeq)
|
||
rights = baseSeq.Cast<object>();
|
||
else if (rawVal != null)
|
||
rights = new[] { rawVal };
|
||
|
||
// wenn gar keine Rechte da sind, weiter
|
||
if (rights == null)
|
||
continue;
|
||
|
||
// die Liste der Recht‑Strings extrahieren
|
||
var rightStrings = rights
|
||
.Select(r => (r as PSObject)?.Properties["RawIdentity"]?.Value?.ToString()
|
||
?? r.ToString())
|
||
.ToList();
|
||
|
||
// UND jetzt prüfen, ob *genau* "Send-As" (normaler Bindestrich) enthalten ist
|
||
if (!rightStrings
|
||
.Any(rs => string.Equals(rs.Trim(),
|
||
"Send-As",
|
||
StringComparison.OrdinalIgnoreCase)))
|
||
{
|
||
continue; // kein Send‑As, nächste Permission
|
||
}
|
||
|
||
// 4) System‑Accounts rausfiltern
|
||
var userName = perm.Properties["User"]?.Value?.ToString() ?? "";
|
||
if (userName.StartsWith("NT AUTHORITY", StringComparison.OrdinalIgnoreCase)
|
||
|| userName.IndexOf("SELF", StringComparison.OrdinalIgnoreCase) >= 0
|
||
|| userName.IndexOf("Administrator", StringComparison.OrdinalIgnoreCase) >= 0)
|
||
continue;
|
||
|
||
// 5) Get-User auflösen
|
||
ps2.Commands.Clear();
|
||
ps2.AddCommand("Get-User")
|
||
.AddParameter("Identity", userName);
|
||
var users = ps2.Invoke();
|
||
if (users.Count > 0)
|
||
result.Add(users[0]);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return Enumerable.Empty<PSObject>();
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Liefert pro Send‑As‑Gruppe ein PSObject mit Name, SID und dem GroupPrincipal selbst.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetSendAsPermissionGroups(string mailboxIdentity)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-ADPermission")
|
||
.AddParameter("Identity", mailboxIdentity)
|
||
.AddParameter("ErrorAction", "Stop");
|
||
var perms = InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-ADPermission (SendAsGroups) '{mailboxIdentity}'");
|
||
if (ps.HadErrors || perms.Count == 0)
|
||
return Enumerable.Empty<PSObject>();
|
||
|
||
var result = new List<PSObject>();
|
||
|
||
foreach (var perm in perms)
|
||
{
|
||
// 1) Nur explizite Rechte
|
||
var inherited = perm.Properties["IsInherited"]?.Value as bool? ?? true;
|
||
if (inherited) continue;
|
||
|
||
// 2) ExtendedRights auf "Send‑As" prüfen (nur via ToString)
|
||
var rawVal = perm.Properties["ExtendedRights"]?.Value;
|
||
IEnumerable<object> rights;
|
||
if (rawVal is System.Collections.IEnumerable seq && !(rawVal is string))
|
||
rights = seq.Cast<object>();
|
||
else if (rawVal is PSObject pso && pso.BaseObject is System.Collections.IEnumerable baseSeq)
|
||
rights = baseSeq.Cast<object>();
|
||
else if (rawVal != null)
|
||
rights = new[] { rawVal };
|
||
else
|
||
continue;
|
||
|
||
// ganz einfach: jede Rechte-Instanz zu String machen
|
||
var rightStrings = rights.Select(r => r.ToString().Trim());
|
||
|
||
if (!rightStrings.Any(s =>
|
||
string.Equals(s, "Send-As", StringComparison.OrdinalIgnoreCase)))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 3) Nur Gruppen‑Principals (DOMAIN\Name) und keine System‑Accounts
|
||
var userName = perm.Properties["User"]?.Value?.ToString() ?? "";
|
||
var low = userName.ToLowerInvariant();
|
||
if (!userName.Contains("\\") ||
|
||
low.StartsWith("nt authority") ||
|
||
low.Contains("self") ||
|
||
low.Contains("administrator"))
|
||
continue;
|
||
|
||
// 4) Gruppe im AD auflösen
|
||
var sam = userName.Split('\\').Last();
|
||
using (var ctx = new PrincipalContext(ContextType.Domain))
|
||
using (var grp = GroupPrincipal.FindByIdentity(ctx, sam))
|
||
{
|
||
if (grp == null)
|
||
continue;
|
||
|
||
// 5) PSObject mit allen gewünschten Properties anlegen
|
||
var obj = new PSObject();
|
||
obj.Properties.Add(new PSNoteProperty("SamAccountName", grp.SamAccountName));
|
||
obj.Properties.Add(new PSNoteProperty("Name", grp.Name));
|
||
obj.Properties.Add(new PSNoteProperty("Sid", grp.Sid.Value));
|
||
obj.Properties.Add(new PSNoteProperty("DistinguishedName", grp.DistinguishedName));
|
||
obj.Properties.Add(new PSNoteProperty("GroupPrincipal", grp));
|
||
|
||
result.Add(obj);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return Enumerable.Empty<PSObject>();
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Liefert pro Gruppe, die FullAccess auf das angegebene Postfach hat,
|
||
/// ein PSObject mit Name, SID, DistinguishedName und dem GroupPrincipal.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetFullAccessPermissionGroups(string mailboxIdentity)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
|
||
// 1) Alle FullAccess-Berechtigungen für das Postfach holen
|
||
ps.AddCommand("Get-MailboxPermission")
|
||
.AddParameter("Identity", mailboxIdentity)
|
||
.AddParameter("ErrorAction", "Stop");
|
||
var perms = InvokePowerShellWithTimeout(ps, PowerShellInvokeTimeout, $"Get-MailboxPermission (FullAccessGroups) '{mailboxIdentity}'");
|
||
if (ps.HadErrors || perms.Count == 0)
|
||
return Enumerable.Empty<PSObject>();
|
||
|
||
var result = new List<PSObject>();
|
||
|
||
foreach (var perm in perms)
|
||
{
|
||
// 2) Eingebettete Properties ziehen
|
||
// - IsInherited
|
||
// - AccessRights (MultiValuedProperty)
|
||
var inherited = perm.Properties["IsInherited"]?.Value as bool? ?? true;
|
||
if (inherited)
|
||
continue;
|
||
|
||
var rawVal = perm.Properties["AccessRights"]?.Value;
|
||
IEnumerable<object> rights = null;
|
||
|
||
if (rawVal is System.Collections.IEnumerable seq && !(rawVal is string))
|
||
rights = seq.Cast<object>();
|
||
else if (rawVal is PSObject pso && pso.BaseObject is System.Collections.IEnumerable baseSeq)
|
||
rights = baseSeq.Cast<object>();
|
||
else if (rawVal != null)
|
||
rights = new[] { rawVal };
|
||
|
||
// 3) Nur FullAccess durchlassen
|
||
if (rights == null ||
|
||
!rights.Any(r =>
|
||
{
|
||
var s = (r as PSObject)?.BaseObject?.ToString() ?? r.ToString();
|
||
return string.Equals(s, "FullAccess", StringComparison.OrdinalIgnoreCase);
|
||
}))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 4) Nur Security-Principals im DOMAIN\Name-Format und keine System‑Accounts
|
||
var userName = perm.Properties["User"]?.Value?.ToString() ?? "";
|
||
var low = userName.ToLowerInvariant();
|
||
if (!userName.Contains("\\")
|
||
|| low.StartsWith("nt authority")
|
||
|| low.Contains("self")
|
||
|| low.Contains("administrator"))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
// 5) Gruppe im AD auflösen und PSObject bauen
|
||
var sam = userName.Split('\\').Last();
|
||
using (var ctx = new PrincipalContext(ContextType.Domain))
|
||
using (var grp = GroupPrincipal.FindByIdentity(ctx, sam))
|
||
{
|
||
if (grp == null)
|
||
continue;
|
||
|
||
var obj = new PSObject();
|
||
obj.Properties.Add(new PSNoteProperty("SamAccountName", grp.SamAccountName));
|
||
obj.Properties.Add(new PSNoteProperty("Name", grp.Name));
|
||
obj.Properties.Add(new PSNoteProperty("Sid", grp.Sid.Value));
|
||
obj.Properties.Add(new PSNoteProperty("DistinguishedName", grp.DistinguishedName));
|
||
obj.Properties.Add(new PSNoteProperty("GroupPrincipal", grp));
|
||
|
||
result.Add(obj);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return Enumerable.Empty<PSObject>();
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// Gets users who have permissions on a mailbox
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetMailboxPermissionMembers(string id)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Get-MailboxPermission")
|
||
.AddParameter("Identity", id)
|
||
.AddParameter("AccessRights", "FullAccess");
|
||
|
||
var results = ps.Invoke().Where(p =>
|
||
!p.Properties["User"].Value.ToString().Contains("NT AUTHORITY") &&
|
||
!p.Properties["User"].Value.ToString().Contains("Administrator"));
|
||
|
||
var userList = new List<PSObject>();
|
||
using (var ps2 = PowerShell.Create())
|
||
{
|
||
ps2.Runspace = runspace;
|
||
foreach (var perm in results)
|
||
{
|
||
ps2.Commands.Clear();
|
||
ps2.AddCommand("Get-User")
|
||
.AddParameter("Identity", perm.Properties["User"].Value.ToString());
|
||
var users = ps2.Invoke();
|
||
if (users.Count > 0)
|
||
userList.Add(users[0]);
|
||
}
|
||
}
|
||
return userList;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return Enumerable.Empty<PSObject>();
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sucht im angegebenen AD‑Domain/OU nach Security‑Gruppen, deren Name zum Wildcard passt.
|
||
/// Nutzt hierfür die übergebenen Anmeldeinformationen.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetSecurityGroups(string nameWildcard)
|
||
{
|
||
var result = GetSecurityGroups(nameWildcard, out _, out _);
|
||
return result ?? new List<PSObject>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sucht im angegebenen AD‑Domain/OU nach Security‑Gruppen, deren Name zum Wildcard passt.
|
||
/// Returns null on failure and exposes an error code/message.
|
||
/// </summary>
|
||
public IEnumerable<PSObject> GetSecurityGroups(string nameWildcard, out string errorCode, out string errorMessage)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
errorCode = "OK";
|
||
errorMessage = string.Empty;
|
||
try
|
||
{
|
||
// Credentials in NetworkCredential konvertieren
|
||
var netCred = _credential.GetNetworkCredential();
|
||
|
||
// PrincipalContext auf Domain oder OU
|
||
using (var ctx = new PrincipalContext(
|
||
ContextType.Domain,
|
||
_domain, // ← real domain or DC hostname
|
||
_organizationalUnit, // ← the OU‑DN to scope your search
|
||
netCred.UserName,
|
||
netCred.Password))
|
||
{
|
||
{
|
||
// QBE‑Gruppe für Wildcard‑Suche
|
||
using (var qbe = new GroupPrincipal(ctx) { Name = nameWildcard })
|
||
using (var searcher = new PrincipalSearcher(qbe))
|
||
{
|
||
var result = new List<PSObject>();
|
||
var findTask = Task.Run(() => searcher.FindAll().OfType<GroupPrincipal>().ToList());
|
||
if (!findTask.Wait(PowerShellInvokeTimeout))
|
||
{
|
||
throw new TimeoutException(
|
||
$"Directory search operation 'GetSecurityGroups' timed out after {PowerShellInvokeTimeout.TotalSeconds:0} seconds.");
|
||
}
|
||
|
||
foreach (var principal in findTask.Result)
|
||
{
|
||
// DirectoryEntry benötigt man, um DistinguishedName auszulesen
|
||
var de = principal.GetUnderlyingObject() as DirectoryEntry;
|
||
|
||
var obj = new PSObject();
|
||
obj.Properties.Add(new PSNoteProperty("SamAccountName",
|
||
principal.SamAccountName));
|
||
obj.Properties.Add(new PSNoteProperty("DisplayName",
|
||
principal.Name));
|
||
obj.Properties.Add(new PSNoteProperty("Sid",
|
||
principal.Sid?.Value ?? string.Empty));
|
||
obj.Properties.Add(new PSNoteProperty("DistinguishedName",
|
||
de?.Properties["distinguishedName"]?.Value?.ToString()
|
||
?? string.Empty));
|
||
|
||
result.Add(obj);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (TimeoutException ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_SECURITYGROUPS_TIMEOUT";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
errorCode = "EXCH_GET_SECURITYGROUPS_FAILED";
|
||
errorMessage = ex.Message;
|
||
return null;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Adds a member to a distribution group
|
||
/// </summary>
|
||
public void AddMemberToDistributionGroup(string groupName, string member)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Add-DistributionGroupMember")
|
||
.AddParameter("Identity", groupName)
|
||
.AddParameter("Member", member);
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes a member from a distribution group
|
||
/// </summary>
|
||
public void RemoveMemberFromDistributionGroup(string groupName, string member)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Remove-DistributionGroupMember")
|
||
.AddParameter("Identity", groupName)
|
||
.AddParameter("Member", member)
|
||
.AddParameter("Confirm", false);
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds mailbox permission for a user
|
||
/// </summary>
|
||
public void AddMailboxPermission(string mailboxName, string user, string accessRight = "FullAccess")
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Add-MailboxPermission")
|
||
.AddParameter("Identity", mailboxName)
|
||
.AddParameter("User", user)
|
||
.AddParameter("AccessRights", accessRight)
|
||
.AddParameter("InheritanceType", "All");
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes mailbox permission for a user
|
||
/// </summary>
|
||
public void RemoveMailboxPermission(string mailboxName, string user, string accessRight = "FullAccess")
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Remove-MailboxPermission")
|
||
.AddParameter("Identity", mailboxName)
|
||
.AddParameter("User", user)
|
||
.AddParameter("AccessRights", accessRight)
|
||
.AddParameter("InheritanceType", "All")
|
||
.AddParameter("Confirm", false);
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Adds Send As permission for a mailbox
|
||
/// </summary>
|
||
public async Task<bool> AddSendAsPermission(string mailboxName, string user)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Add-ADPermission")
|
||
.AddParameter("Identity", mailboxName)
|
||
.AddParameter("User", user)
|
||
.AddParameter("ExtendedRights", "Send-As")
|
||
.AddParameter("AccessRights", "ExtendedRight");
|
||
|
||
var results = ps.Invoke();
|
||
return results.Count > 0;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Removes Send As permission for a mailbox
|
||
/// </summary>
|
||
public async Task<bool> RemoveSendAsPermission(string mailboxName, string user)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("Remove-ADPermission")
|
||
.AddParameter("Identity", mailboxName)
|
||
.AddParameter("User", user)
|
||
.AddParameter("ExtendedRights", "Send-As")
|
||
.AddParameter("AccessRights", "ExtendedRight")
|
||
.AddParameter("Confirm", false);
|
||
|
||
var results = ps.Invoke();
|
||
return results.Count > 0;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Creates a shared mailbox. Optionally initial permissions (e.g. FullAccess) can be set for members.
|
||
/// </summary>
|
||
public void CreateSharedMailbox(string name, string alias, List<string> initialMembers = null)
|
||
{
|
||
LogMethodBegin(MethodBase.GetCurrentMethod());
|
||
try
|
||
{
|
||
using (var runspace = CreateRunspace())
|
||
using (var ps = PowerShell.Create())
|
||
{
|
||
ps.Runspace = runspace;
|
||
ps.AddCommand("New-Mailbox")
|
||
.AddParameter("Name", name)
|
||
.AddParameter("Alias", alias)
|
||
.AddParameter("Shared", true)
|
||
.AddParameter("OrganizationalUnit", _organizationalUnit);
|
||
ps.Invoke();
|
||
|
||
if (initialMembers != null)
|
||
{
|
||
foreach (var member in initialMembers)
|
||
{
|
||
ps.Commands.Clear();
|
||
ps.AddCommand("Add-MailboxPermission")
|
||
.AddParameter("Identity", name)
|
||
.AddParameter("User", member)
|
||
.AddParameter("AccessRights", "FullAccess")
|
||
.AddParameter("InheritanceType", "All");
|
||
ps.Invoke();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogException(ex);
|
||
throw;
|
||
}
|
||
finally
|
||
{
|
||
LogMethodEnd(MethodBase.GetCurrentMethod());
|
||
}
|
||
}
|
||
}
|
||
}
|