Files
LIAM/LieamExchange/ExchangeManager.cs
Meik 3d4f60d83e chore: sync LIAM solution snapshot incl. diagnostics tooling
- update multiple LIAM projects and solution/config files

- add LiamWorkflowDiagnostics app sources and generated outputs

- include current workspace state (dependencies and build outputs)
2026-02-27 09:12:34 +01:00

543 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Security;
using System.Reflection;
using C4IT.Logging;
using static C4IT.Logging.cLogManager;
namespace C4IT.LIAM
{
/// <summary>
/// Manages connections and operations for a local Microsoft Exchange Server
/// using PowerShell Remoting with explicit credentials.
/// </summary>
public class ExchangeManager : IDisposable
{
private readonly string _exchangeServerFqdn;
private readonly PSCredential _credential;
private Runspace _runspace;
private bool _isConnected = false;
/// <summary>
/// Initializes the ExchangeManager for connection with explicit credentials.
/// </summary>
/// <param name="exchangeServerFqdn">Fully qualified domain name of the Exchange Server.</param>
/// <param name="userName">Username for the connection (e.g. user@domain.com or DOMAIN\user).</param>
/// <param name="password">The user's password as a SecureString.</param>
public ExchangeManager(string exchangeServerFqdn, string userName, SecureString password)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
_exchangeServerFqdn = exchangeServerFqdn ?? throw new ArgumentNullException(nameof(exchangeServerFqdn));
if (string.IsNullOrEmpty(userName)) throw new ArgumentNullException(nameof(userName));
_credential = new PSCredential(userName, password ?? throw new ArgumentNullException(nameof(password)));
InitializeRunspace();
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
// Initializes the PowerShell Runspace for remote connection
private void InitializeRunspace()
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
// Use HTTPS for Basic Authentication!
var connectionUri = new Uri($"https://{_exchangeServerFqdn}/powershell?serializationLevel=Full");
var connectionInfo = new WSManConnectionInfo(
connectionUri,
"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
_credential
)
{
// Set authentication to Basic (or Negotiate/Kerberos depending on server config)
// Basic Auth MUST be enabled on the Exchange Server for the PowerShell vDir!
AuthenticationMechanism = AuthenticationMechanism.Basic
};
_runspace = RunspaceFactory.CreateRunspace(connectionInfo);
LogEntry($"Attempting to connect to {connectionUri} with user '{_credential.UserName}' and method '{connectionInfo.AuthenticationMechanism}'...", LogLevels.Info);
_runspace.Open();
_isConnected = true;
LogEntry("Connection established successfully.", LogLevels.Info);
}
catch (Exception E)
{
LogException(E);
LogEntry($"Error opening runspace to Exchange server. Check server name, credentials, and PowerShell remoting settings.", LogLevels.Error);
throw new InvalidOperationException($"Error opening runspace to Exchange. Details: {E.Message}", E);
}
finally
{
LogMethodEnd(CM);
}
}
// Private helper method to execute PowerShell commands
private IEnumerable<PSObject> ExecuteExchangeCommand(string script, Dictionary<string, object> parameters = null)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
if (_runspace == null || _runspace.RunspaceStateInfo.State != RunspaceState.Opened)
{
throw new InvalidOperationException("Runspace is not open or initialized.");
}
LogEntry($"Executing script: {script}", LogLevels.Debug);
if (parameters != null && parameters.Any())
{
LogEntry("With parameters: " + string.Join(", ", parameters.Select(kvp => $"{kvp.Key}=***")), LogLevels.Debug);
}
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = _runspace;
ps.AddScript(script);
if (parameters != null)
{
ps.AddParameters(parameters);
}
var results = ps.Invoke();
// Check for errors (important!)
if (ps.Streams.Error.Count > 0)
{
var errorMessages = ps.Streams.Error.Select(e => e.ToString()).ToList();
LogEntry($"Error in PowerShell execution (User: '{_credential.UserName}'):", LogLevels.Error);
foreach (var errMsg in errorMessages)
{
LogEntry($"- {errMsg}", LogLevels.Error);
}
// Throw exception to surface the error
throw new Exception($"Error in Exchange PowerShell command execution. First error message: {errorMessages.FirstOrDefault()}");
}
LogEntry("Script executed successfully.", LogLevels.Debug);
return results;
}
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
// *** Methods for managing Shared Mailboxes ***
/// <summary>
/// Creates a new Shared Mailbox. The associated AD account is disabled by default.
/// </summary>
public void CreateSharedMailbox(string name, string alias, string userPrincipalName, string organizationalUnit = null)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
string script = @"New-Mailbox -Name $Name -Alias $Alias -UserPrincipalName $UserPrincipalName -Shared";
var parameters = new Dictionary<string, object>
{
{ "Name", name },
{ "Alias", alias },
{ "UserPrincipalName", userPrincipalName }
};
if (!string.IsNullOrEmpty(organizationalUnit))
{
script += " -OrganizationalUnit $OrganizationalUnit";
parameters.Add("OrganizationalUnit", organizationalUnit);
}
ExecuteExchangeCommand(script, parameters);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets information about all Shared Mailboxes.
/// </summary>
public IEnumerable<PSObject> GetSharedMailboxes()
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
return ExecuteExchangeCommand("Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited");
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Adds full access permissions for a user to a mailbox.
/// </summary>
public void AddFullAccessPermission(string mailboxIdentity, string userIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
ExecuteExchangeCommand(
"Add-MailboxPermission -Identity $MailboxIdentity -User $UserIdentity -AccessRights FullAccess -InheritanceType All",
new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Removes full access permissions for a user from a mailbox.
/// </summary>
public void RemoveFullAccessPermission(string mailboxIdentity, string userIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
ExecuteExchangeCommand(
"Remove-MailboxPermission -Identity $MailboxIdentity -User $UserIdentity -AccessRights FullAccess -InheritanceType All -Confirm:$false",
new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Adds "Send As" permissions for a user to a mailbox.
/// </summary>
public void AddSendAsPermission(string mailboxIdentity, string userIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
ExecuteExchangeCommand(
"Add-ADPermission -Identity $MailboxIdentity -User $UserIdentity -ExtendedRights 'Send As'",
new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
// *** Methods for managing Distribution Groups (Mailing Lists) ***
/// <summary>
/// Creates a new Distribution Group (mailing list).
/// </summary>
public void CreateDistributionGroup(string name, string alias, string organizationalUnit = null, string managedBy = null)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
string script = @"New-DistributionGroup -Name $Name -Alias $Alias -Type Distribution";
var parameters = new Dictionary<string, object> { { "Name", name }, { "Alias", alias } };
if (!string.IsNullOrEmpty(organizationalUnit))
{
script += " -OrganizationalUnit $OrganizationalUnit";
parameters.Add("OrganizationalUnit", organizationalUnit);
}
if (!string.IsNullOrEmpty(managedBy))
{
script += " -ManagedBy $ManagedBy";
parameters.Add("ManagedBy", managedBy);
}
ExecuteExchangeCommand(script, parameters);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets information about all Distribution Groups.
/// </summary>
public IEnumerable<PSObject> GetDistributionGroups()
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
return ExecuteExchangeCommand("Get-DistributionGroup -ResultSize Unlimited");
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Adds a member to a Distribution Group.
/// </summary>
public void AddMemberToDistributionGroup(string groupIdentity, string memberIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
ExecuteExchangeCommand(
"Add-DistributionGroupMember -Identity $GroupIdentity -Member $MemberIdentity",
new Dictionary<string, object> { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Removes a member from a Distribution Group.
/// </summary>
public void RemoveMemberFromDistributionGroup(string groupIdentity, string memberIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
ExecuteExchangeCommand(
"Remove-DistributionGroupMember -Identity $GroupIdentity -Member $MemberIdentity -Confirm:$false",
new Dictionary<string, object> { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets the members of a Distribution Group.
/// </summary>
public IEnumerable<PSObject> GetDistributionGroupMembers(string groupIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
return ExecuteExchangeCommand(
"Get-DistributionGroupMember -Identity $GroupIdentity",
new Dictionary<string, object> { { "GroupIdentity", groupIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets the permissions for a mailbox.
/// </summary>
public IEnumerable<PSObject> GetMailboxPermissions(string mailboxIdentity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
return ExecuteExchangeCommand(
"Get-MailboxPermission -Identity $MailboxIdentity | Where-Object {$_.User -ne 'NT AUTHORITY\\SELF' -and $_.IsInherited -eq $false}",
new Dictionary<string, object> { { "MailboxIdentity", mailboxIdentity } }
);
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets information about a specific mailbox.
/// </summary>
public PSObject GetMailbox(string identity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var results = ExecuteExchangeCommand(
"Get-Mailbox -Identity $Identity",
new Dictionary<string, object> { { "Identity", identity } }
);
return results.FirstOrDefault();
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
/// <summary>
/// Gets information about a specific distribution group.
/// </summary>
public PSObject GetDistributionGroup(string identity)
{
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
try
{
var results = ExecuteExchangeCommand(
"Get-DistributionGroup -Identity $Identity",
new Dictionary<string, object> { { "Identity", identity } }
);
return results.FirstOrDefault();
}
catch (Exception E)
{
LogException(E);
throw;
}
finally
{
LogMethodEnd(CM);
}
}
// Implementation of IDisposable to clean up the runspace
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && _runspace != null)
{
if (_runspace.RunspaceStateInfo.State == RunspaceState.Opened)
{
LogEntry("Closing Runspace...", LogLevels.Debug);
_runspace.Close();
}
_runspace.Dispose();
_runspace = null;
LogEntry("Runspace closed and resources released.", LogLevels.Debug);
}
}
~ExchangeManager()
{
Dispose(false);
}
}
}