initial
This commit is contained in:
543
LieamExchange/ExchangeManager.cs
Normal file
543
LieamExchange/ExchangeManager.cs
Normal file
@@ -0,0 +1,543 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user