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 { /// /// Manages connections and operations for a local Microsoft Exchange Server /// using PowerShell Remoting with explicit credentials. /// public class ExchangeManager : IDisposable { private readonly string _exchangeServerFqdn; private readonly PSCredential _credential; private Runspace _runspace; private bool _isConnected = false; /// /// Initializes the ExchangeManager for connection with explicit credentials. /// /// Fully qualified domain name of the Exchange Server. /// Username for the connection (e.g. user@domain.com or DOMAIN\user). /// The user's password as a SecureString. 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 ExecuteExchangeCommand(string script, Dictionary 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 *** /// /// Creates a new Shared Mailbox. The associated AD account is disabled by default. /// 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 { { "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); } } /// /// Gets information about all Shared Mailboxes. /// public IEnumerable 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); } } /// /// Adds full access permissions for a user to a mailbox. /// 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 { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Removes full access permissions for a user from a mailbox. /// 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 { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Adds "Send As" permissions for a user to a mailbox. /// 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 { { "MailboxIdentity", mailboxIdentity }, { "UserIdentity", userIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } // *** Methods for managing Distribution Groups (Mailing Lists) *** /// /// Creates a new Distribution Group (mailing list). /// 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 { { "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); } } /// /// Gets information about all Distribution Groups. /// public IEnumerable GetDistributionGroups() { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { return ExecuteExchangeCommand("Get-DistributionGroup -ResultSize Unlimited"); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Adds a member to a Distribution Group. /// public void AddMemberToDistributionGroup(string groupIdentity, string memberIdentity) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { ExecuteExchangeCommand( "Add-DistributionGroupMember -Identity $GroupIdentity -Member $MemberIdentity", new Dictionary { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Removes a member from a Distribution Group. /// 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 { { "GroupIdentity", groupIdentity }, { "MemberIdentity", memberIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Gets the members of a Distribution Group. /// public IEnumerable GetDistributionGroupMembers(string groupIdentity) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { return ExecuteExchangeCommand( "Get-DistributionGroupMember -Identity $GroupIdentity", new Dictionary { { "GroupIdentity", groupIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Gets the permissions for a mailbox. /// public IEnumerable 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 { { "MailboxIdentity", mailboxIdentity } } ); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Gets information about a specific mailbox. /// public PSObject GetMailbox(string identity) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var results = ExecuteExchangeCommand( "Get-Mailbox -Identity $Identity", new Dictionary { { "Identity", identity } } ); return results.FirstOrDefault(); } catch (Exception E) { LogException(E); throw; } finally { LogMethodEnd(CM); } } /// /// Gets information about a specific distribution group. /// public PSObject GetDistributionGroup(string identity) { var CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); try { var results = ExecuteExchangeCommand( "Get-DistributionGroup -Identity $Identity", new Dictionary { { "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); } } }