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
{
///
/// Manages Exchange operations using PowerShell remoting
///
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;
///
/// Constructor with explicit credentials, Exchange URI and OU path
///
/// URL of the Exchange PowerShell endpoint (e.g. "http://exchangeserver/PowerShell")
/// Explicit credentials
/// OU path where objects should be created
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;
}
///
/// Creates a runspace connected to the Exchange PowerShell endpoint
///
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);
var openTask = Task.Run(() => runspace.Open());
if (!openTask.Wait(RunspaceOpenTimeout))
{
try
{
runspace.Dispose();
}
catch (Exception disposeEx)
{
LogException(disposeEx);
}
throw new TimeoutException(
$"Timeout while opening Exchange runspace after {RunspaceOpenTimeout.TotalSeconds:0} seconds.");
}
openTask.GetAwaiter().GetResult();
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> Groups { get; private set; }
public CreationResult()
{
Groups = new List>();
}
}
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;
}
}
}
///
/// Creates a distribution group (mailing list). Optionally members can be added initially.
///
public void CreateDistributionGroup(string name, string alias, List 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());
}
}
///
/// 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.
///
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);
}
}
///
/// Gets all shared mailboxes matching an optional filter
///
public IEnumerable GetSharedMailboxes(string filter = null)
{
var result = GetSharedMailboxes(filter, out _, out _);
return result ?? Enumerable.Empty();
}
///
/// Gets all shared mailboxes matching an optional filter.
/// Returns null on failure and exposes an error code/message.
///
public IEnumerable 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());
}
}
///
/// Gets a shared mailbox by primary SMTP address
///
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());
}
}
///
/// Gets all distribution groups matching an optional filter
///
public IEnumerable GetDistributionGroups(string filter = null)
{
var result = GetDistributionGroups(filter, out _, out _);
return result ?? Enumerable.Empty();
}
///
/// Gets all distribution groups matching an optional filter.
/// Returns null on failure and exposes an error code/message.
///
public IEnumerable 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());
}
}
///
/// Gets a distribution group by primary SMTP address.
/// Die Methode bleibt unverändert.
///
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());
}
}
///
/// Gets members of a distribution group
///
public IEnumerable 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();
var result = new List();
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 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();
var result = new List();
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