Ich arbeite viel mit Exchange Servern. Dabei benutze ich sehr gerne die PowerShell ISE statt der Exchange Management Shell – gerade auch wegen der Remoting-Funktionalität: Denn so muss ich nicht immer erst auf meinen Exchange Server mit RDP springen.
Für den Verbindungaufbau sind nur 2 Zeilen erforderlich:
$MX = New-PSSession -ConnectionUri 'http://ws-mx1/powershell' -Authentication Kerberos -ConfigurationName microsoft.exchange Import-PSSession -Session $MX -DisableNameChecking
Danach steht dann die Verbindung und vor allem die vielen Exchange-cmdlets zur Verfügung:
Das bietet sich auch in Scriptdateien an. Aber es gibt ein Problem: Der ConnectionURI muss einen existierenden und erreichbaren Exchange Server enthalten. Und das gibt 2 mögliche Probleme:
- Der Server wird in Scriptdateien statisch hinterlegt. Ersetzt ein neuer Exchange Server den alten, der im URI benannt wird, dann läuft das Script nicht mehr. Bei einer Vielzahl von Scripten bedeutet dies viel Arbeit durch nachträgliche Anpassungen.
- Nicht selten sind mehrere Exchange Server vorhanden. Es kann aber im ConnectionURI nur einer hinterlegt werden. Bei einem Ausfall-Szenario oder einer geplanten Wartung würden gespeicherte Scripte keine Verbindung mehr zum Exchange Server aufbauen können.
So kam mir die Idee zu einer einfach aufrufbaren Funktion, welche im Hintergrund alle möglichen Exchange Server ermittelt und dann der Reihe nach durchprobiert.
Die Exchange Server tragen sich in der Configuration-Partition im Active Directory ein. Diese kann mit einfachen LDAP-Befehlen direkt aus der PowerShell heraus abgefragt werden – Ohne die Notwendigkeit des PS-Modules “ActiveDirectory”. Hier sind die gewünschten Informationen im ADSIedit sichtbar:
Und mit diesen Zeilen kann man sehr einfach nach der ObjectClass msExchPowerShellVirtualDirectory suchen:
$Partition = "LDAP://" +([adsi] "LDAP://RootDSE").Get("configurationNamingContext") $Searcher = New-Object DirectoryServices.DirectorySearcher $Searcher.SearchRoot = $Partition $Searcher.Filter = "(objectClass=msExchPowerShellVirtualDirectory)" $Searcher.FindAll().Properties.msexchinternalhostname
Das Ergebnis ist ein Array aller PowerShell-VirtualDirectories mit den URI’s:
Leider erhalten nur Administratoren der Exchange Server die URI-Liste angezeigt. Alle anderen Benutzer ohne besondere Rechte (aber mit einem Postfach) gehen leer aus:
Eine Alternative zur Abfrage wäre daher die Ermittlung der Exchange-Servernamen und daraus abgeleitete URIs:
$Partition = "LDAP://" +([adsi] "LDAP://RootDSE").Get("configurationNamingContext") $Searcher = New-Object DirectoryServices.DirectorySearcher $Searcher.SearchRoot = $Partition $Searcher.Filter = "(objectCategory=msExchExchangeServer)" $Searcher.FindAll().Properties.cn | ForEach-Object { "http://$_/powershell" }
Der Rest der Funktionslogik war dann recht einfach.
Das fertige Script habe ich zur besseren Strukturierung in einzelne Regionen unterteilt. Damit kann man relativ einfach durch die Anweisungen navigieren:
Zuerst werden bestehende Verbindungen zu einem Exchange Server ermittelt. Sind diese vorhanden, dann wird die Funktion beendet. Alternativ kann mit einem Parameter -NewSession auch ein Beenden der bestehenden Verbindungen erfolgen, bevor eine neue Verbindung aufgebaut wird. Dann sucht die Funktion nach URIs. Dabei wird zuerst der “AdminMode” verwendet. Nur, wenn dieser keine Ergebnisse liefert, werden die URIS aus den Servernamen generiert. Dann wird die URI-Liste durchprobiert, bis eine erfolgreiche Verbindung aufgebaut wurde oder die Liste kene weiteren Einträge enthält. Zuletzt gibt es noch einen Ausgabebereich.
Das ist nun der finale Code:
function verbinde-MX { <# .Notes Scriptreihe: verbinde MX (Exchange Server 2013,2016,2019) Datum: 2020-05-24 Version: V1.00 Programmierer: Stephan Walther .Synopsis Die Funktion verbindet die aktuelle PowerShell mit einem Exchange Server. .DESCRIPTION Mit dieser Funktion kann die aktuelle PowerShell-Session bequem mit einem verfuegbaren Exchange Server verbunden werden. Dies funktioniert mit den Versionen zwischen 2013 und 2019. Hybrid-Szenarien und Exchange Online wurden noch nicht getestet. Die Funktion ermittelt via LDAP die existierenden Exchange Server und probiert dabei alle durch, bis eine erfolgreiche Verbindung zustande kommt oder keine weiteren Server mehr vorhanden sind. .EXAMPLE verbinde-MX Ohne Parameter sucht die Funktion nach einem verfuegbaren Exchange Server und stellt eine Verbindung her. Dabei werden Meldungen ausgegeben. .EXAMPLE verbinde-MX -NewSession Wurde bereits eine Verbindung aufgebaut, dann wird ein erneuter Aufruf dies erkennen und keine neue Session erstellen. Mit dem Zusatz -NewSession wird die bestehende Verbindung entfernt und neu aufgebaut. .EXAMPLE if (verbinde-MX -ReturnBool -Silent) { # erfolgreiche Verbindung } else { # fehlgeschlagene Verbindung } Diese Variante eignet sich besonders für Bedingungen, da keine Meldungen erzeugt werden. Stattdessen wird ein Boolean-Wert für die Bedingung zurueckgegeben. .PARAMETER Silent Der Parameter unterdrueckt die Meldungstexte. Im Standard werden via Write-Host Meldungen angezeigt. .PARAMETER ReturnBool Der Parameter gibt als Ergebnis des Verbindungaufbaus ein true oder false zurueck. Damit kann die Funktion direkt in if-Bedingungen verwendet werden. Im Standard wird nur der Meldungstext ausgegeben. .PARAMETER NewSession Mit diesem Parameter wird eine neue Verbindung erzwungen, wenn schon eine zu einem Exchange Server besteht. Die alte Verbindung wird beendet. #> [outputtype([boolean])] param( [parameter(Mandatory=$false)] [switch] $Silent = $false, [parameter(Mandatory=$false)] [switch] $ReturnBool = $false, [parameter(Mandatory=$false)] [switch] $NewSession = $false ) #region Variablen $weiter = $true $WriteHost = -not $silent #endregion #region besteht bereits eine Verbindung? if ($weiter) { $MXSessions = Get-PSSession | Where-Object {$_.ConfigurationName -eq 'microsoft.exchange' -and $_.State -eq 'opened'} if ( $MXSessions ) { if ( $NewSession ) { try { $MXSessions | Remove-PSSession if ($WriteHost) {Write-Host "Eine bestehende Verbindung zum Server $($MXSessions.ComputerName -join ' und ') wurde beendet." -ForegroundColor Green} } catch { if ($WriteHost) {Write-Host "FEHLER bei Beenden der Verbindung: $($error[0].Exception.Message)" -ForegroundColor Yellow} $weiter = $false } } else { if ($WriteHost) {Write-Host "Es besteht bereits eine Verbindung zum Server $($MXSessions.ComputerName -join ' und ')." -ForegroundColor Green} $weiter = $false } } } #endregion #region Exchange-Powershell-URLs finden if ($weiter) { try { $URLs = @() # Variante 1: direkte Abfrage im LDAP ==> setzt Exchange-Berechtigung voraus $Partition = "LDAP://" +([adsi] "LDAP://RootDSE").Get("configurationNamingContext") $Searcher = New-Object DirectoryServices.DirectorySearcher $Searcher.SearchRoot = $Partition $Searcher.Filter = "(objectClass=msExchPowerShellVirtualDirectory)" $Searcher.FindAll().Properties.msexchinternalhostname | ForEach-Object { if ($_.length -gt 0) { $URLs += $_ if ($WriteHost) {Write-Host "Server gefunden: $_" -ForegroundColor Green} } } # Variante 2: falls keine URLs wegen fehlenden Rechten gefunden wurden ==> MX-Server ermitteln if ($URLs.count -eq 0) { $Searcher.Filter = "(objectCategory=msExchExchangeServer)" $Searcher.FindAll().Properties.cn | ForEach-Object { $URLs += "http://$_/powershell" if ($WriteHost) {Write-Host "Server gefunden: http://$_/powershell" -ForegroundColor Green} } } # Ergebnis: if ($URLs.count -eq 0) { if ($WriteHost) {Write-Host "Es wurden keine Exchange Server gefunden!" -ForegroundColor Yellow} $weiter = $false } } catch { if ($WriteHost) {Write-Host "FEHLER bei Verbindung mit LDAP: $($error[0].Exception.Message)" -ForegroundColor Yellow} $weiter = $false } } #endregion #region Verbindung aufbauen if ($weiter) { $verbunden = $false $URLs | ForEach-Object { if ($verbunden -eq $false){ try { $URL = $_ $MX = New-PSSession -ConnectionUri $URL -Authentication Kerberos -ConfigurationName microsoft.exchange -ErrorAction Stop Import-PSSession -Session $MX -DisableNameChecking | Out-Null $verbunden = $true if ($WriteHost) {Write-Host "verbunden mit $URL" -ForegroundColor Green} } catch { if ($WriteHost) {Write-Host "FEHLER bei Verbindung mit $($URL): $($error[0].Exception.Message)" -ForegroundColor Yellow} } } } } #endregion #region besteht jetzt eine Verbindung? ==> Ausgabe if ($ReturnBool) { $MXSessions = Get-PSSession | Where-Object {$_.ConfigurationName -eq 'microsoft.exchange' -and $_.State -eq 'opened'} if ( $MXSessions ) { return $true } else { return $false } } #endregion }
Und so könnten die Szenarien der Verwendung aussehen. Der klassische Aufruf erfolgt ohne Parameter:
Alternativ lässt sich der Aufruf auch in Bedingungen verwenden:
Und bestehende Verbindungen können erneuert werden:
Die Funktion ist fertig und ich kann sie in meine Exchange Scripte integrieren. Und hier gibt das Script noch als zip-Archiv .
Stay tuned!