HybridManagment.psm1

Wenn Sie auf einem Windows Server für die Nutzung von Exchange Hybrid den Weg über "Modern Hybrid" wählen und den Hybrid Agent  installieren, dann erhalten Sie nicht nur einen Dienst sondern auch ein PowerShell-Module. Diese Seite behandelt, was Sie damit anstellen können, dass einige Dinge wohl fehlerhaft sind und wie Sie dies umgehen.

Das PS1M-Modul

Das Hybrid Management kommt nicht als Standard-Modul mit der Powershell mit und kann nicht einfach mit "Import-Module" geladen werden. Die PowerShell durchsucht beim Import-Module nur einige vordefinierte Verzeichnisse und dazu gehört nicht das "Programm Files"-Verzeichnis.

Daher müssen Sie das Modul erst einmal in einer Powershell laden:

Hinweis: Anscheinend gibt es von Microsoft immer mal wieder ein Update unter der URL https://aka.ms/hybridconnectivity. Bitte laden Sie die aktuelle Version herunter.

Achtung: Das Modul lädt auch unter Powershell 7 aber davon abhängige Module funktionieren nur mit PowerShell 5. Daher bitte für das Hybrid Management die "alte" PowerShell nutzen (Stand Nov 2024). Prüfen Sie die Version einfach mit "$PSVersionTable.psversion.tostring()".

PS C:\> Import-Module "C:\Program Files\Microsoft Hybrid Service\HybridManagement.psm1"

Die im Modul enthaltenen Commandlets sind schnell aufgelistet.

PS C:\> Get-Command -Module HybridManagement

CommandType     Name                                               ModuleName
-----------     ----                                               ----------
Function        GetAuthHeader                                      HybridManagement
Function        GetAuthToken                                       HybridManagement
Function        Get-HybridAgent                                    HybridManagement
Function        Get-HybridApplication                              HybridManagement
Function        New-HybridApplication                              HybridManagement
Function        Remove-HybridApplication                           HybridManagement
Function        TestEndpoints                                      HybridManagement
Function        Test-HybridConnectivity                            HybridManagement
Function        TestoneEndpoint                                    HybridManagement
Function        TestProxySettings                                  HybridManagement
Function        TestTLSSettings                                    HybridManagement
Function        Update-HybridApplication                           HybridManagement

Allerdings fällt das inkonsistente Namensschema auf. Normalerweise folgen PowerShell-Commandlets dem "Verb-Noun"-Schema und oft noch mit dem Bereichsnamen. So gibt es mehrere Commandlets, die sich alle auf einen Benutzer beziehen aber produktspezifisch sind, z.B. Get-ADUser, Get-AzureADUser, Get-MSOLUser, Get-CSUser. In den Anfangszeiten der PowerShell war das aber wohl noch nicht so bekannt und Exchange 2007 hat "Get-User" für sich beansprucht und hält es bis heute bei. Nur Exchange Online hat ein "Get-EXOUser".

Das HybridManagement-Module hat aber gerade mal drei Commandlets und sieht auch sonst ziemlich zusammengestückelt aus.

Ursache ist aber, dass Microsoft interne Funktionen nicht versteckt hat. Test-HybridConnectivity ruft seinerseits nämlich "TestEndpoints" und "TestoneEndpoint" auf. Das hätte man über ein Manifest oder Export-ModuleMember schöner lösen können.

Test-HybridConnectivity

Das erste Commandlet, welches sie einfach so nutzen können, ist Test-HybridConnectivity. Es prüft die Auflösung und Erreichbarkeit der aus Sicht von Microsoft wichtigen Gegenstellen zur Funktion des Hybrid Agent.

PS C:\> Test-HybridConnectivity
Testing connection to mscrl.microsoft.com on port 80
Testing connection to crl.microsoft.com on port 80
Testing connection to ocsp.msocsp.com on port 80
Testing connection to www.microsoft.com on port 80
Testing connection to login.windows.net on port 443
Testing connection to login.microsoftonline.com on port 443
Testing connection to aadap-portcheck-seaus.connectorporttest.msappproxy.net on port 8080
WARNING: Ping to aadap-portcheck-seaus.connectorporttest.msappproxy.net failed -- Status:
DestinationHostUnreachable
WARNING: TCP connect to aadap-portcheck-seaus.connectorporttest.msappproxy.net:8080 failed
Performing GET on https://aadap-portcheck-seaus.connectorporttest.msappproxy.net:8080
Invoke-WebRequest : Unable to connect to the remote server
At C:\Program Files\Microsoft Hybrid Service\HybridManagement.psm1:196 char:19
+         $result = Invoke-WebRequest -Method Get -Uri $uri
+                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [I
   nvoke-WebRequest], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.Inv
   okeWebRequestCommand

Die PSM1-Datei ist natürlich Klartext und so können wir direkt sehen, welche Endpunkte Microsoft hier codiert hat:

Das sind nun keine große Überraschungen enthalten. Die meisten Adressen und Ports beziehen sich auf HTTP/HTTPS und nur eine Zeile mit Port 8080 fällt aus dem Rahmen. Dieser Host und Port sind in den Requirements nicht aufgeführt und der Hybrid Agent funktioniert bei mir auch ohne diesen Port. Als wen hier jemand eine Gegenstelle für Rückmeldungen vorgesehen hat. Port 8080 ist aber meist ein alternativer Webserver-Port ohne Verschlüsselung.

Azure und SHA256Cng

Auf dem Weg zu den anderen interessanten Commandlets ist natürlich eine Anmeldung an EntraID erforderlich. Dazu bedient sich das Hybrid Management am "Azure"-Modul, welche nicht mit installiert wurde. Das sehen sie z.B. beim Aufruf von "Get-HybridAgent".

PS C:\> Get-HybridAgent -Credential (Get-Credential)

cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:
Credential
Import-Module : The specified module 'Azure' was not loaded because no valid module file was
found in any module directory.
At C:\Program Files\Microsoft Hybrid Service\HybridManagement.psm1:11 char:5
+     Import-Module Azure
+     ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (Azure:String) [Import-Module], FileNotFou
   ndException
    + FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModu
   leCommand

Hier fehlt noch das Azure-Modul, welches ich schnell nachinstallieren muss. Damit aber noch nicht genug, denn es fehlt auch noch Azure.Storage

# erforderliche Module installieren
Install-Module -Name Azure, Azure.Storage -Repository PSGallery -AllowClobber 
-Force

# Module inportieren
Import-Module -Name Azure

#Anmelden, damit eine Session besteht
Add-AzureAccount

Aber selbst dann bekomme ich noch keine Verbindung und stattdessen einen Fehler, dass CNG256 nicht geladen werden konnte.

Add-AzureAccount : Could not load type 'System.Security.Cryptography.SHA256Cng' from assembly 'System.Core, Version=4.0.0.0, Culture=neutral...

Die Ursache dabei ist aber, dass ich eine "zu alte Powershell" genutzt habe. Wer noch nicht auf PowerShell 5.1 ist, sollte WMF 5.1 (oder neuer) aktualisieren

Windows Management Framework 5.1
https://www.microsoft.com/en-us/download/details.aspx?id=54616

Die PowerShell 4 oder älter kennt die neueren Encryption Verfahren noch nicht.

Anmeldung mit MFA

Nachdem auch diese Hürde übersprungen wurde, dürfte sie die MFA-Hürde erwischen. Selbst ein im November 2024 neu installierter Hybrid Agent bringt immer noch eine HybridManagement.psm1-Datei von 2019 mit, die kein MFA unterstützt. Microsoft hat aber über die Security Defaults eine MFA-Anmeldung für Administratoren erzwungen

Entsprechend bekommen Sie dann eine Fehlermeldung:

PS C:\> Get-HybridAgent -credential $cred
GetAuthToken : Exception calling "AcquireToken" with "3" argument(s): "AADSTS50076: Due to a
configuration change made by your administrator, or because you moved to a new location, you
must use multi-factor authentication to access '00000003-0000-0000-c000-000000000000'. Trace
ID: 210f40a5-7867-487a-9aaf-8eacc6d56a00 Correlation ID: 15a25832-beed-45e2-8a22-ceca3e19b2c7
Timestamp: 2024-11-06 10:29:36Z"

Wer eine EntraID P1/Premium-Lizenzen hat, kann mittels Conditional Access z.B. MFA für den Zugriff von diesem Server über die öffentliche IP-Adresse abschalten. Einfacher ist es aber wohl, einfach eine aktueller Version der HybridManagement.psm1 herunterzuladen. Microsoft hat dazu extra eine eigene Kurz-URL bereitgestellt:

Download der aktuellen HybridManagement.psm1
https://aka.ms/hybridconnectivity

Fragen Sie mich bitte nicht, warum Microsoft nicht gleich diese Version mit in das MSI-Paket inkludiert, zumal Der Hybrid-Agent sogar einen eigenen Hybrid Updater Agent mitbringt. Sie müssen dann aber statt "-Credentials" den Parameter "-UserPrincipalName" nutzen

PS C:\> Get-HybridAgent -UserprincipalName admin@msxfaqlab.onmicrosoft.com | fl

id          : c9b35e0d-a6f8-4440-aab9-ae7530c0b120
machineName : EX01.UCLABOR.DE
externalIp  : 80.66.20.27
status      : active

*-HybridApplication

Zu den Befehlen der HybridManagement.psm1-Modul gehört aber noch folgende Befehle:

 Get-HybridApplication
 New-HybridApplication
 Remove-HybridApplication
 Update-HybridApplication

Diese können Sie aber nicht so einfach nutzen, denn alle Befehle erwarten eine Angabe der "AppID", d.h. der GUID zur passenden Applikation. Auch das Commandlet "Get-HybridApplication" hat keine Funktion, um alle Agenten aufzulisten. Die AppID ist einmalig und keine generische Nummer. Der Versuch mir dann aber auch die EntraID-Application zu besorgen, hat wieder nicht funktioniert. Zuerst habe ich die ID des Hybrid Agent genutzt, die aber nicht gefunden wurde.

Mit der Ausgabe von "Verbose" und einem Blick in den Code würde ich sagen, das das nie funktionieren sollte, denn sie sehen hier das "/edu/" in der URL.

 

Ich war mir ziemlich sicher, dass dieser Code so nicht in den regulären Umgebungen funktionieren kann. Zumal ich in den HCW-Logdateien (HCW Logging) folgende URLs gefunden habe:

2024.11.05 17:12:12.382   10386 [Client=UX, Thread=19] Previous Connector Application Name found: eb92551b-9c63-4472-a8dc-2be7fcf00313
2024.11.05 17:12:12.897   10332 [Client=UX, fn=SendAsync, Thread=19] 
                                     START GET https://graph.microsoft.com/beta/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/
                                                   eb92551b-9c63-4472-a8dc-2be7fcf00313/onPremisesPublishing 
2024.11.05 17:12:13.100   10333 [Client=UX, fn=SendAsync, Thread=19] 
                                      FINISH Time=203,1ms Results=OK 
                                      {
                                         "@odata.context":"https://graph.microsoft.com/beta/$metadata#applications('eb92551b-9c63-4472-a8dc-2be7fcf00313')/onPremisesPublishing",
                                         "externalUrl":"https://eb92551b-9c63-4472-a8dc-2be7fcf00313.resource.mailboxmigration.his.msappproxy.net/",
                                         "internalUrl":"https://ex01.uclabor.de/",
                                         "alternateUrl":null,
                                         "externalAuthenticationType":"passthru",
                                         "isTranslateHostHeaderEnabled":false,
                                         "isTranslateLinksInBodyEnabled":false,
                                         "isOnPremPublishingEnabled":true,
                                         "isHttpOnlyCookieEnabled":false,
                                         "isSecureCookieEnabled":false,
                                         "isPersistentCookieEnabled":false,
                                         "isBackendCertificateValidationEnabled":true,
                                         "applicationServerTimeout":"Default",
                                         "useAlternateUrlForTranslationAndRedirect":false,
                                         "applicationType":"",
                                         "isStateSessionEnabled":true,
                                         "isAccessibleViaZTNAClient":false,
                                         "isDnsResolutionEnabled":false,
                                         "verifiedCustomDomainCertificatesMetadata":null,
                                         "verifiedCustomDomainKeyCredential":null,
                                         "verifiedCustomDomainPasswordCredential":null,
                                         "segmentsConfiguration":null,
                                         "singleSignOnSettings":{
                                            "singleSignOnMode":"none",
                                            "kerberosSignOnSettings":null
                                         },
                                         "onPremisesApplicationSegments":[]
                                      }

HCW nutzt aber nicht die ConnectorID als GUID sondern eine andere Nummer, die bislang noch nicht aufgetaucht ist. Ich habe sie nur beim Migration Endpoint gefunden und im HCWLog sehe ich auch, dass der HCW genau diesen Befehl auch aufruft.

2024.11.05 17:16:44.724   10276 [Client=UX, Session=Tenant, Cmdlet=Get-MigrationEndpoint, Thread=7] START 
2024.11.05 17:16:45.159   10277 [Client=UX, Session=Tenant, Cmdlet=Get-MigrationEndpoint, Thread=7] FINISH Time=434,6ms Results=1
                                       {
                                          AcceptUntrustedCertificates=False 
                                          EndpointType=ExchangeRemoteMove 
                                          Guid=1b2ab337-614c-443a-b5d9-8ab57596e95d 
                                          Identity=Hybrid Migration Endpoint - EWS (Default Web Site) 
                                          Identity(ObjectId)=Hybrid Migration Endpoint - EWS (Default Web Site) 
                                          IsPublicFolderMailboxesMigrationSource=False 
                                          IsRemote=True 
                                          IsValid=True 
                                          MailboxPermission=Admin 
                                          MaxConcurrentIncrementalSyncs=10 
                                          MaxConcurrentMigrations=20 
                                          RemoteServer=eb92551b-9c63-4472-a8dc-2be7fcf00313.resource.mailboxmigration.his.msappproxy.net 
                                          Username=UCLABOR\admfc}

Ich kann nur vermuten, dass der HCW hier im Hintergrund über die Exchange Online PowerShell sich den "Remoteserver" und daraus die AppID besorgt. Mit dieser GUID gelingt dann auch die Abfrage mit Get-HybridApplication. Anscheinend hat "Graph" hier einen Alias, denn auch der Zugriff über den "/edu/"-Pfad funktioniert:

 

Wenn Sie nun glauben, dass ihre Werte hier falsch sind, dann können Sie diese mit einem "Update-HybridApplication" natürlich korrigieren. Die "HybridManagement.psm1" enthält den passenden Code und sie haben auch die Berechtigungen. Allerdings erlaubt das Commandlet nur das Setzen der TargetURL.

 

Das kann mal erforderlich sein, wenn Sie mitten in der Migration sich entschließen, eine neue lokale URL zu verwenden. Das ist aber eher unüblich.

Eigene Abfragen

Dieser Abschnitt ist entstanden, ehe ich mit MGGraph einen besseren Weg gefunden und auf HCW Modern Agent mit MGGraph beschrieben habe. Aber vielleicht interessiert jemand auch der Zugriff ohne MGGraph PowerShell

Das ganze geht natürlich auch ohne MGGraph mit direkten PowerShell-aufrufen. Das Microsoft Modul gibt sich als "AzureAD PowerShell" aus und das kann ich genauso. Damit erspare ich mir weitere Consent-Abfragen und bin sicher, dass ich genau die gleichen Berechtigungen habe.

$ClientID     = "1950a258-227b-4e31-a9cf-717495945fc2"
$TenantDomain = "msxfaqlab.onmicrosoft.com"
$Token        = Get-MsalToken -TenantId $TenantDomain -ClientId $ClientID

$TenantID = $Token.TenantID

Mit "Get-MSALToken" kommt dann auch der klassische Anmeldedialog, der auch MFA unterstützt und am Ende enthält die Variable "$token" ein AccessToken und einige andere Informationen. Das AccessToken ist wieder ca. 75 Minuten gültig. Hier nur ein Auszug:

{
  "aud": "https://graph.microsoft.com",
  "iat": 1730893988,
  "nbf": 1730893988,
  "exp": 1730898197,
  "amr": [
    "pwd",
    "mfa"
  ],
  "app_displayname": "Microsoft Azure PowerShell",
  "appid": "1950a258-227b-4e31-a9cf-717495945fc2",
  "idtyp": "user",
  "name": "fcadm msxfaqlab",
  "scp": "AuditLog.Read.All Directory.AccessAsUser.All email openid profile",
  "tenant_region_scope": "EU",
  "tid": "463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c",
  "unique_name": "fcadm@msxfaqlab.onmicrosoft.com",
  "upn": "fcadm@msxfaqlab.onmicrosoft.com",
}

Diese Token setzen wir dann in einen HTTP-Header und starten den Graph-Abruf:

$authHeader = @{
    'Content-Type'='application\json'
    'Authorization'=$token.CreateAuthorizationHeader()
}

$uri = "https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups?$expand=members"
$ConnectorGroups = Invoke-RestMethod `
                      -URI $uri `
                      –Headers $authHeader  `
                      –Method GET

Die Variable $ConnectorGroups enthält dann die JSON-Payload im Property "value". In meinem LAB-Tenant finde ich da drei Einträge:

Es ist also nicht nur der Hybrid Connector, welcher hier aufgeführt wird, sondern auch mein EntraID Connect Sync und vermutlich auch GroupWriteback. Darauf gehe ich aber nicht weiter ein und mir reicht die ID meines Exchange Hybrid Sync.

# Die ConnectorID hole ich mir wie folgt:
$Connectorid = ($ConnectorGroups.value | where-object {$_.connectorgroupType -eq "exchangeOnline"}).id

ConnectorApplicationName

Ich möchte aber noch mehr über die Konfiguration des AppProxy erfahren, den ich nicht im AzureAD meines Tenants sehe. Im HCW Logging finde ich dazu folgende URL:

https://graph.microsoft.com/beta/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/eb92551b-9c63-4472-a8dc-2be7fcf00313/onPremisesPublishing

Der Wert "eb92551b-9c63-4472-a8dc-2be7fcf00313" finde ich im HCW-Logging als "ConnectorApplicationName" aber keine weitere Information, woher die ID ausgelesen wird. Google liefert dazu nicht und auch eine Suche im AzureAD zeigt nichts an.

 

Auch im lokalen Active Directory konnte ich keinen Treffer auf die GUID landen. Im HCW-Logging sehe ich aber einen Aufruf auf folgende URL:

Den Tenant mit seiner ID kenne ich aber die AppID scheint sogar eine Zufallszahl zu sein. Im Code von "Hybridmanagement.psm1" steht sogar die Generierung wie folgt drin:

Das passt aber nicht mit meinen Betrachtungen, dass ich die App komplett löschen und neu anlegen lassen kann und die GUID doch wieder die gleiche ist. Der Prozess ist etwas undurchsichtig. Zumal die Application auch nicht aufgeführt wird. Wenn ich die URL etwas anpasse, dann bekomme ich schon ein Antwort, aber nicht die gewünschte Applikation.

# $uri = "https://graph.microsoft.com/beta/$($token.tenantID)/applications/$ClientID/onPremisesPublishing"
$url="https://graph.microsoft.com/beta/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/eb92551b-9c63-4472-a8dc-2be7fcf00313/onPremisesPublishing" 
$application = Invoke-RestMethod -Uri $uri –Headers $authHeader –Method GET
$application.value

Das ist nun unerwartet, denn ich hätte entweder einen Fehler oder leere Rückgabe erwartet, aber keine Liste aller von mir selbst definierten Apps. Aber auch der Versuch mit "/edu/" liefert genau die gleichen Informationen zurück. Ich habe mir dann noch einmal den aktuellen Code von "HybridManagement.psm1" angeschaut und noch eine Unstimmigkeit gefunden.

 

Die Variable "$foundApplications" kommt sonst nirgends vor und liefert dann wohl ein "$null". Eine Rückgabe liefert das Commandlet wohl nur aufgrund von "$applications" in Zeile 103. Ich kann aber keinen Unterschied zwischen dem REST-Aufruf in dem Microsoft Module und meiner interaktiven Aufrufe erkennen aber die Ergebnisse unterscheiden sich.

Die GUID der Application habe ich indirekt über die Exchange Online PowerShell ermitteln können, weil es natürlich einen Migration Endpoint gibt.

Install-Module ExchangeOnlineManagement
Connect-ExchangeOnline

Get-MigrationEndpoint | fl identity,remoteserver

Identity     : Hybrid Migration Endpoint - EWS (Default Web Site)
RemoteServer : eb92551b-9c63-4472-a8dc-2be7fcf00313.resource.mailboxmigration.his.msappproxy.n
               et

Das ist aber keine verlässliche Quelle.

Einschätzung

Ich hoffe, dass Sie möglichst wenig mit dem Modul "HybridManagement" und dem "Modern Hybrid Agent" zu tun haben. Es ist eine Möglichkeit etwas hinter die Kulissen zu schauen und mögliche Fehler bei der Installation durch den HCW zu erkennen. Die Korrekturmöglichkeiten sind aber doch stark eingeschränkt und die Handhabung ist nicht gerade einfach. Andere Managementmodule kennen ein "Connect-*" Befehl, um einmal die Authentifizierung zu erledigen. Hier müssen entweder jedes Mal die Credentials oder den UPN mitgeben. Auch dass eigentlich "interne" Funktionen "Exportiert" werden, ist unschön. Die Namen könnten mit bestehenden Commandlets oder Skripten kollidieren.

Dennoch wird es wohl die ein oder andere Notwendigkeit geben, damit zu arbeiten. Dann wissen sie nun aber um die Fallstricke und notwendige Voraussetzungen wie z.B. die Notwendigkeit der Powershell 5, die Erfordernis weiterer Azure-Module und dass der MFA-Support nur mit der aktuellsten Version enthalten ist. Das kann man es fast verschmerzen, dass das Skript die AppID der "Azure AD Powershell" zur Anmeldung kapert. Wenn Sie aber schon immer mal ein kleines Skript analysieren wollten, wie es sich ein AuthToken besorgt und damit Graph-Aufrufe durchführt, dann können Sie einmal spicken.

Weitere Links