verbinde-MX (PowerShell-Funktion)

Die Idee

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:

verbinde-MX (PowerShell-Funktion)

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:

  1. 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.
  2. 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.

Von der Idee zum Code

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:

verbinde-MX (PowerShell-Funktion)

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:

verbinde-MX (PowerShell-Funktion)

Leider erhalten nur Administratoren der Exchange Server die URI-Liste angezeigt. Alle anderen Benutzer ohne besondere Rechte (aber mit einem Postfach) gehen leer aus:

verbinde-MX (PowerShell-Funktion)

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"
    }
verbinde-MX (PowerShell-Funktion)

Der Rest der Funktionslogik war dann recht einfach.

Das fertige Script

Das fertige Script habe ich zur besseren Strukturierung in einzelne Regionen unterteilt. Damit kann man relativ einfach durch die Anweisungen navigieren:

verbinde-MX (PowerShell-Funktion)

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
}

Mögliche Aufrufe

Und so könnten die Szenarien der Verwendung aussehen. Der klassische Aufruf erfolgt ohne Parameter:

verbinde-MX (PowerShell-Funktion)
Verbindungsaufbau: Das Laden der Remotebefehle.
verbinde-MX (PowerShell-Funktion)
Die RemoteSession zum Exchange Server
verbinde-MX (PowerShell-Funktion)
Erneute Verbindungen werden vermieden.

Alternativ lässt sich der Aufruf auch in Bedingungen verwenden:

verbinde-MX (PowerShell-Funktion)

Und bestehende Verbindungen können erneuert werden:

verbinde-MX (PowerShell-Funktion)

Zusammenfassung

Die Funktion ist fertig und ich kann sie in meine Exchange Scripte integrieren. Und hier gibt das Script noch als zip-Archiv .

Stay tuned!

Kommentar hinterlassen