Archiwa blogu

Zarządzanie serwerem Exchange 2010/2013 poprzez Exchange Management Shell

2014-07-20

W jednym z poprzednich tematów opisałem sposób zarządzania serwerem Exchange 2007 poprzez polecenia PowerShell (tzw. cmdlets) wywoływane z poziomu .NET. Jeżeli tego samego rozwiązania chcielibyśmy użyć do obsługi serwerów Exchange 2010/2013 musimy nieco zmodyfikować nasz kod. W tych wersjach Exchange korzystanie ze środowiska Exchange Management Shell odbywa się poprzez zdalne sesje PowerShell (PowerShell remoting), a nie tak jak wcześniej przez załadowanie odpowiedniego PSSnapIn. Istnieje co prawda możliwość wykorzystania PSSnapIn „Microsoft.Exchange.Management.PowerShell.E2010” ale jest to metoda niezalecana. W przypadku Exchange 2010/2013 kod metody InvokeCommand (zamieszczonej we wspomnianym wcześniej wpisie) powinien wyglądać tak:

public void InvokeCommand(
	string exchangeEndpointUri,
	string command,
	Dictionary<string, object> parameters,
	out List<Dictionary<string, string>> results,
	out List<string> errors)
{
	results = new List<Dictionary<string, string>>();
	errors = new List<string>();

	var connectionInfo = new WSManConnectionInfo(
		new Uri(exchangeEndpointUri),
		"http://schemas.microsoft.com/powershell/Microsoft.Exchange",
		(PSCredential)null);
	connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
	connectionInfo.SkipCACheck = true;
	connectionInfo.SkipCNCheck = true;

	using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(connectionInfo))
	{
		myRunSpace.Open();
		using (PowerShell powershell = PowerShell.Create())
		{
			powershell.Runspace = myRunSpace;
			powershell.AddCommand(command);
			powershell.AddParameters(parameters);

			Collection<PSObject> commandResults = powershell.Invoke();

			if (powershell.Streams.Error != null && powershell.Streams.Error.Count > 0)
				foreach (ErrorRecord error in powershell.Streams.Error)
					errors.Add(error.ToString());

			if (commandResults != null)
				foreach (PSObject commandResult in commandResults)
				{
					var result = new Dictionary<string, string>();
					foreach (PSPropertyInfo property in commandResult.Properties)
					{
						string propertyName = property.Name;
						string propertyValue = property.Value == null ? "" : property.Value.ToString();

						result.Add(propertyName, propertyValue);
					}
					results.Add(result);
				}
		}
		myRunSpace.Close();
	}
}

Główna zmiana polega na wykorzystaniu klasy WSManConnectionInfo do połączenia z serwerem Exchange (wymagane jest podanie adresu serwera oraz w przypadku uwierzytelniania konkretnym użytkownikiem – obiektu PSCredential). Poprzednio do tego celu używany był odpowiedni plik PSSnapIn ładowany za pomocą metody RunspaceConfiguration.AddPSSnapIn. W powyższej metodzie do wywoływania poleceń cmdlet zastosowałem klasę PowerShell, ale można wykorzystać poprzednio użyte klasy Pipeline i Command. Największą zaletą nowego podejścia jest fakt, że nasz kod możemy teraz uruchomić na dowolnej maszynie mogącej połączyć się z serwerem Exchange 2010/2013.

Reklamy

Przełączanie skrzynek Exchange pomiędzy kontami Active Directory z wykorzystaniem Exchange Management Shell

2014-05-20

Od pewnego czasu mam okazję budować system, którego część funkcjonalności dotyczy automatyzacji czynności administracyjnych związanych z zarządzaniem użytkownikami Active Directory. Jedną z takich czynności jest przełączanie skrzynek pocztowych Exchange 2007 pomiędzy dwoma kontami użytkowników. W dzisiejszym wpisie pokażę, w jaki sposób zrealizować to zadanie wykorzystując Exchange Management Shell (EMS). EMS jest środowiskiem skryptowym pozwalającym na zarządzanie serwerami Exchange poprzez polecenia PowerShell (tzw. cmdlets). Możliwość wywoływania poleceń cmdlets z kodu .NET zapewnia biblioteka System.Management.Automation.dll. Domyślnie znajduje się ona w katalogu C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0, a dostępna jest po zainstalowaniu Windows PowerShell SDK będącego składnikiem Windows Platform SDK.
Poniżej znajduje się kod stworzonej przeze mnie metody pozwalającej na wywoływanie poleceń cmdlets. W argumentach przyjmuje nazwę polecenia oraz jego parametry w postaci Dictionary<string, object> (nazwa parametru, wartość). Poprzez argument results zwracana jest lista obiektów Dictionary<string, string>, gdzie każdy słownik reprezentuje właściwości danego obiektu zwróconego przez cmdlet (nazwa właściwości, wartość). Z kolei w przypadku wystąpienia błędów, ich lista zwracana jest poprzez argument errors.

using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Collections.ObjectModel;
public void InvokeCommand(string command, Dictionary<string, object> parameters,
	out List<Dictionary<string, string>> results, out List<string> errors)
{
	results = new List<Dictionary<string, string>>();
	errors = new List<string>();

	RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
	PSSnapInException snapInException = null;
	PSSnapInInfo info = rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException);

	using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig))
	{
		myRunSpace.Open();
		using (Pipeline pipeLine = myRunSpace.CreatePipeline())
		{
			Command myCommand = new Command(command);

			foreach (var parameter in parameters)
				myCommand.Parameters.Add(new CommandParameter(parameter.Key, parameter.Value));

			pipeLine.Commands.Add(myCommand);
			Collection<PSObject> commandResults = pipeLine.Invoke();

			if (pipeLine.Error != null && pipeLine.Error.Count > 0)
				foreach (PSObject error in pipeLine.Error.ReadToEnd())
					errors.Add(error.ToString());

			if (commandResults != null)
				foreach (PSObject commandResult in commandResults)
				{
					var result = new Dictionary<string, string>();
					foreach (PSPropertyInfo property in commandResult.Properties)
					{
						string propertyName = property.Name;
						string propertyValue = property.Value == null ? "" : property.Value.ToString();

						result.Add(propertyName, propertyValue);
					}
					results.Add(result);
				}
		}
		myRunSpace.Close();
	}
}

Aby wywołać cmdlet musimy utworzyć obiekt Runspace przekazując instancję RunspaceConfiguration z dodanym PSSnapIn-em „Microsoft.Exchange.Management.PowerShell.Admin”. Następnie tworzymy obiekt Command (podając nazwę polecenia), ustawiamy jego parametry, po czym dodajemy go do utworzonego za pomocą metody CreatePipeline potoku komend obiektu Runspace. Do potoku możemy dodać więcej niż jedną komendę. Cmdlets uruchamiamy poprzez metodę Invoke potoku, w wyniku otrzymując kolekcję obiektów typu PSObject. Jeżeli podczas przetwarzania zgłoszone zostały błędy, mamy do nich dostęp poprzez właściwość Error obiektu Pipeline. Więcej informacji na temat wykorzystania Exchange Management Shell w kodzie .NET można znaleźć w artykule Using Exchange Management Shell Commands With Managed Code.

Skoro mamy już metodę pozwalającą na uruchamianie poleceń cmdlet, możemy przejść do realizacji wspomnianego we wstępie przełączania skrzynki pocztowej Exchange pomiędzy dwoma kontami Active Directory. Zadanie to składa się z następujących kroków:

1. Odczyt informacji o przełączanej skrzynce pocztowej
2. Odłączenie skrzynki od aktualnego konta Active Directory
3. Podłączenie skrzynki do nowego konta Active Directory
4. Utworzenie skrzynki dla nowego konta jeżeli odłączona skrzynka została usunięta

Odczyt informacji o skrzynce

Informacje jakich potrzebujemy to identyfikator skrzynki (ExchangeGuid) oraz baza, w której ta skrzynka się znajduje (Database). Do odczytu informacji o skrzynce służy polecenie Get-Mailbox, do którego przekazujemy parametr Identity identyfikujący skrzynkę. W tym przypadku jako jego wartość przekazywany jest MailNickname (Alias) pochodzący z aktualnego konta Active Directory. Możemy skorzystać z wartości innych atrybutów: GUID, DN, Display name, Domain\Account, UPN, SmtpAddress. Oto kod pobierający wymagane informacje o skrzynce:

string mailNickname = "jan.kowalski";
var parameters = new Dictionary<string, object>();
List<Dictionary<string, string>> results;
List<string> errors;

parameters.Add("Identity", mailNickname);
InvokeCommand("Get-Mailbox", parameters, out results, out errors);

if (errors.Count > 0)
{
	//Obsługa błędów
}
else
	if (results.Count > 0)
	{
		string exchangeGuid = results[0]["ExchangeGuid"];
		string database = results[0]["Database"];
	}

Odłączenie skrzynki

Kolejnym krokiem jest odłączenie skrzynki od aktualnego konta Active Directory. Realizowane jest to poprzez polecenie Disable-Mailbox. Podobnie jak wcześniej przekazujemy parametr Identity (tym razem używamy odczytanego ExchangeGuid) oraz parametr Confirm ustawiony na false (dzięki temu nie jest zadawane pytanie o potwierdzenie odłączenia). W efekcie tej operacji z konta Active Directory usunięte zostają wszystkie atrybuty związane ze skrzynką pocztową Exchange.

var parameters = new Dictionary<string, object>();
List<Dictionary<string, string>> results;
List<string> errors;

parameters.Add("Identity", exchangeGuid);
parameters.Add("Confirm", false);
InvokeCommand("Disable-Mailbox", parameters, out results, out errors);

if (errors.Count > 0)
{
	//Obsługa błędów
}

Podłączenie skrzynki

Teraz przyszła kolej na podłączenie skrzynki do nowego konta Actice Directory. Do tego celu służy polecenie Connect-Mailbox. Przekazywane do niego parametry to: Identity (ExchangeGuid), User (AccountName nowego konta), Alias (MailNickname nowego konta) oraz Database (baza skrzynki).

string accountName = "JKowalski";
string mailNickname = "jkowalski";
var parameters = new Dictionary<string, object>();
List<Dictionary<string, string>> results;
List<string> errors;

parameters.Add("Identity", exchangeGuid);
parameters.Add("User", accountName);
parameters.Add("Alias", mailNickname);
parameters.Add("Database", database);
InvokeCommand("Connect-Mailbox", parameters, out results, out errors);

if (errors.Count > 0)
{
	//Obsługa błędów
}

Utworzenie skrzynki

Jeżeli dana skrzynka nie została nigdy użyta, po odłączeniu od konta zostaje ona usunięta. W konsekwencji operacja podłączenia jej do nowego konta nie powiedzie się. Rozwiązaniem jest utworzenie dla tego konta nowej skrzynki za pomocą polecenia Enable-Mailbox. Przekazywane do niego parametry to: Identity (AccountName nowego konta), Alias (MailNickname nowego konta) oraz Database (baza skrzynki).

string accountName = "JKowalski";
string mailNickname = "jan.kowalski2";
var parameters = new Dictionary<string, object>();
List<Dictionary<string, string>> results;
List<string> errors;

parameters.Add("Identity", accountName);
parameters.Add("Alias", mailNickname);
parameters.Add("Database", database);
InvokeCommand("Enable-Mailbox", parameters, out results, out errors);

if (errors.Count > 0)
{
		//Obsługa błędów
}

Więcej informacji na temat zarządzania skrzynkami Exchange poprzez polecenia cmdlet: Mailbox cmdlets.