543 lines
18 KiB
C#
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);
|
|
}
|
|
}
|
|
} |