Modern Agent mit MGGraph

Bei einem Kunden konnte HCW die Installation des Modern Agent nicht abschließen. In meinem Lab habe ich daher die Aktionen des HCW analysiert und einige Requests dokumentiert. Das lag auch daran, dass das eingebaute Management mit der HybridManagment.psm1-PowerShell doch sehr eingeschränkt ist.

Warnung

Es gibt nur wenige Seiten, denen ich einen eigenen Abschnitt "Warnung" voranstelle. Hier ist es aber angeraten, den die Beispiele hier werden mit dem Recht "Directory.ReadWrite.All" ausgeführt und bei falscher Ausführung sind die Folgen nicht absehbar.

Zusammenhänge

Wenn Sie vielleicht die Seite Exchange Modern Hybrid Agent gelesen haben, dann wissen sie, dass der lokale Agent eine HTTPS-Verbindung zu einer Gegenstelle in Azure aufbaut, um eingehende Anfragen von Exchange Online anzunehmen. Das ist eine sehr vereinfachte Sichtweise, denn in der Konfiguration gibt es gleich mehrere Objekte, die aufeinander aufbauen.

 

Die Komponenten sind. 

Baustein Beschreibung

Exchange Online Migration Service

Der aktive Part in Exchange Online wird durch Migration Batches gesteuert, welche eine Liste der zu migrierenden Postfächer, Zeiten und vor allem einen Verweis auf den zu verwendenden Migrationsendpunkt enthalten,

Exchange Online Migration Endpunkt

Der Migrationsendpunkt ist hingegen nur eine Konfigurationseinstellung, welche den Migrationsweg und die Gegenstelle enthält. Sie erhalten den Endpunkt mit "Get-MigrationEndpoint" in der Exchange Online PowerShell, welcher den Link zum nächsten Baustein enthält:

PS C:\> Get-MigrationEndpoint | fl

Identity      : Hybrid Migration Endpoint - EWS (Default WebSite)
EndpointType  : ExchangeRemoteMove
RemoteServer  : <AppID-GUID>.resource.mailboxmigration.his.msappproxy.net
Username      : UCLABOR\svcmig
Guid          : ca5e0b9a-8367-44a8-8c81-8baafbd3c1fc
IsRemote      : True

AzureAD Application

Damit der Migration Endpoint eine Gegenstelle findet, legt HCW eine "Application" an, welche HTTPS-Requests auf den Namen "<AppID-GUID>.resource.mailboxmigration.his.msappproxy.net" annimmt und über eine AzureAD Connector Group an einen Agenten weiterreicht, der dann die hinterlegte interne URL anspricht.

externalUrl                              : https://<AppID-GUID>.resource.mailboxmigration.his.msappproxy.net/
internalUrl                              : https://ex01.uclabor.de/
externalAuthenticationType               : passthru
isTranslateHostHeaderEnabled             : False
isTranslateLinksInBodyEnabled            : False
isOnPremPublishingEnabled                : True
isHttpOnlyCookieEnabled                  : False
isSecureCookieEnabled                    : False
isPersistentCookieEnabled                : False
applicationServerTimeout                 : Default
applicationType                          :
verifiedCustomDomainCertificatesMetadata :
verifiedCustomDomainKeyCredential        :
verifiedCustomDomainPasswordCredential   :
singleSignOnSettings                     : @{SingleSignOnMode=none;KerberosSignOnSettings=}

Wie sie dies auslesen, zeige ich ihnen weiter unten auf der Seite. 

AzureAD Connector Group

Die für die Verbindung erforderlichen Connectoren werden in einer Connectorgroup zusammengefasst, den denen es per Default auch wieder einige gibt.

id                 : e71c6f90-34d7-4b0e-be95-1f9e952a7bed
name               : Default group for AD Sync Fabric
region             : eur
connectorGroupType : syncFabric
isDefault          : False

id                 : f371a12e-3cd8-4cf2-9629-2fc2d4e8d63c
name               : Group-UCLABOR.DE-96e5bfea-ccfd-406c-bc6e-6673d01a4722
region             : eur
connectorGroupType : syncFabric
isDefault          : False

id                 : 61914166-6324-4790-8515-49e30ac19114
name               : Default group for Exchange Online
region             : eur
connectorGroupType : exchangeOnline
isDefault          : False

AzureAD Connector

Der letzte Eintrag in Microsoft 365 /AzureAD ist der eigentliche Connector. Das ist letztlich nur die Information über einen Namen, seine Public IP und den Status:

id          : <connectorID-GUID>
machineName : EX01.UCLABOR.DE
externalIp  : 80.66.20.27
status      : active
version     : 1.5.732.0

OnPremises Hybrid Agent

Zum Connector in der Cloud gehört dann ein lokaler Dienst, der die Verbindung zu Microsoft 365 und zum lokalen Exchange Server aufbaut. Er ist das "Relay", um die über den etablierten HTTPS-Kamal eingehenden Anfragen anzunehmen und zum Exchange zu senden.

OnPremises Exchange Server

Der lokale Exchange Server bekommt die Zugriffe unter seiner internen URL "https://<internal EWS Url>/ews/" und beantwortet diese.

Ein Teil der Objekte sind "Programme" aber viele Objekte sind auch einfach nur Konfigurationseinstellungen, die aber zueinander passen müssen. Über Microsoft Graph können Sie aber einige Informationen direkt abfragen.

Anmeldung mit MGGraph

Dazu ist aber erst einmal eine Anmeldung erforderlich. Microsoft stellt dazu zwar das "HybridManagement.PSM1"-Modul bereit, was aber sehr eingeschränkt ist. Mit der aktuellere MGGraph PowerShell geht das meiner Ansicht nach viel bequemer.

Für die folgenden Befehle benötigen Sie einen PC mit installierter "MGGraph-PowerShell" und ein privilegiertes Konto.

Normalerweise nutzt die MGGraph-PowerShell ihre eigene "ClientID", die aber erst vom Administrator "erlaubt" werden muss (Consent). Aus dem Modul HybridManagment.psm1 geht aber hervor, dass Microsoft selbst in diesem Modul auch einfach die ClientID der "AzureAD PowerShell" verwendet. Das mache ich dann auch.

Connect-MgGraph `
           -TenantId msxfaqlab.onmicrosoft.com `
           -ClientId "1950a258-227b-4e31-a9cf-717495945fc2"

Allerdings lieferte dieser Versuch eine Fehermeldung:

Connect-MgGraph: InteractiveBrowserCredential authentication failed: 
AADSTS65002: Consent between first party application '1950a258-227b-4e31-a9cf-717495945fc2' 
and first party resource '00000003-0000-0000-c000-000000000000' must be configured via preauthorization - 
applications owned and operated by Microsoft must get approval from the API owner before requesting tokens for that API. 

Zuerst dachte ich, dass Microsoft einen besonderen Schutz in der Cloud eingebaut hat, dass sich nicht jeder als "hybridManagement.psm1" ausgeben kann. Allerdings habe ich im Sourcecode keine weiteren "Besonderheiten" gesehen, die eine Verwendung dieser ClientID unterbunden hätte. Im Code von Hybridmanagemt.psm1 ist aber eine Funktion "GetAuthToken" enthalte und damit habe ich mir ein Token besorgt und dieses angeschaut. So bin ich an die Scopes gekommen und habe meinen Aufruf einfach etwas erweitert:

Connect-MgGraph `
           -TenantId msxfaqlab.onmicrosoft.com `
           -ClientId "1950a258-227b-4e31-a9cf-717495945fc2" `
           -Scopes AuditLog.Read.All,Directory.AccessAsUser.All,email,openid,profile

Durch die explizite Angabe der Scopes habe ich eine gültige Session bekommen, was Sie mit Get-MGContext prüfen können:

 

 

Ich habe später auch auf die ClientID verzichtet und mich mit der MGGraph-ID ohne Probleme angemeldet und konnte die gleichen Informationen abrufen, wenn ein Admin-Consent erteilt wurde.

Dennoch muss ich bei Gelegenheit mich noch mal mit der First Party Application befassen.

ConnectorGroup auslesen

Der erste Anlaufpunkt ist die Abfrage der Connectoren und Connector Groups. Dazu brauche ich keine GUIDs eines Tenant, einer Application oder eines Agenten, so dass die Abfrage ein erster Test bezüglich Verbindung und Berechtigung ist. Er ist zudem nur Lesend und damit ungefährlich.

(Invoke-MGGraphRequest `
    -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/' `
    -OutputType PSObject `
).value

id                 : e71c6f90-34d7-4b0e-be95-1f9e952a7bed
name               : Default group for AD Sync Fabric
region             : eur
connectorGroupType : syncFabric
isDefault          : False

id                 : f371a12e-3cd8-4cf2-9629-2fc2d4e8d63c
name               : Group-UCLABOR.DE-96e5bfea-ccfd-406c-bc6e-6673d01a4722
region             : eur
connectorGroupType : syncFabric
isDefault          : False

id                 : 61914166-6324-4790-8515-49e30ac19114
name               : Default group for Exchange Online
region             : eur
connectorGroupType : exchangeOnline
isDefault          : False

Hinweis: Ohne "-OutputType PSObject" bekomme ich das Ergebnis als Hashtable, in der der Value zum key "value" ein Array ohne Gruppierung ist.

Bei Microsoft wird noch ein anderes Beispiel aufgeführt:

Import-Module Microsoft.Graph.Beta.Applications
Get-MgBetaOnPremisePublishingProfileConnectorGroup -OnPremisesPublishingProfileId $onPremisesPublishingProfileId

Allerdings habe ich keine Idee, was er hier als "onPremisesPublishingProfileId" erwartet.

Connector/Agent auflisten

Es gibt auch eine API, um die AzureAD Connectoren aufzulisten.

Der Aufruf der URL bringt auch keinen Fehler aber eben auch keinen "Value".

Invoke-MGGraphRequest `
   -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectors'

Allem Anschein nach lassen sich die Modern hybrid-Connectoren so nicht auflisten.

Es funktioniert aber über den Umweg sich bei den ConnectorGroups auch die Member mittels "$expand=members" mit anzeigen zu lassen.

PS C:\Users\fcarius> (Invoke-MGGraphRequest `
>> -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=members' `
>> -OutputType PSObject).value.members

id : e53d89eb-331d-449d-b120-448d930bf9db
machineName : EX01.UCLABOR.DE
externalIp : 80.66.20.27
status : active
version : 1.5.732.0

Das ist so etwas unsauber, denn wir bekommen so die Members aller Gruppen. Daher sollten wir uns vorher die id der "Default group for Exchange Online" holen und dann mit der ID einsteigen.

Vermutlich geht das mit "$filter=" als Parameter noch einfacher aber irgendwie habe ich keine funktionierende Syntax gefunden.
Use the $filter query parameter https://learn.microsoft.com/en-us/graph/filter-query-parameter?tabs=http

Daher habe ich wieder per PowerShell Pipeline gefiltert:

((Invoke-MGGraphRequest `
   -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=Members' `
   -OutputType PSObject).value `
| where {$_.connectorGroupType-eq "exchangeOnline"} ).members

Damit habe wird auch den Connector in der Gruppe samt ID, Servername, öffentliche IP-Adresse und sogar den Status. Dieses Wissen habe ich auf der Seite Hybrid Agent Statusabfrage gleich weiter verarbeitet.

Connector löschen

Was passiert, wenn sie lokal einen Hybrid Agent deinstallieren? Nach meiner Erfahrung bleibt der Connector im Tenant erhalten aber erhält den Status "inaktiv". Ich habe bei keinen Versuchen einer Deinstallation gesehen, dass der Connector wieder deregistriert wird.

Nach meiner Einschätzung bleibt er in dem Status "inaktiv". 

Ich habe aber auch keinen Weg gefunden, den Connector zu löschen. Ich kann ihn vielleicht aus der Zuordnung zur Gruppe entfernen aber wird er damit auch komplett gelöscht?

HybridApplication auflisten

Eine weitere Komponente ist die Hybrid Application, deren Konfiguration in AzureAD die Verbindung von einem öffentlichen Namen über die Connectorgroup zum Agenten herstellt. Ehe wir sie im übernächsten Abschnitt weiter anzeigen müssen wir sie erst einmal finden. Microsoft Graph stellt natürlich eine API zum Anzeige von AzureAD Apps bereit.

Import-Module Microsoft.Graph.Applications

# Anzeigen aller Apps
Get-MgApplication 

# Anzeigen einer bestimmten App 
Get-MgApplication -ApplicationId $applicationId

Bei mir liefert dies auch eine Ausgabe aber nur die "regulären" Apps, die ich auch im Azure Portal sehe.

Meine Hybrid App ist hier nicht darunter

So komme ich nicht an die Daten. Allerdings kann ich wieder über die ConnectorGroups gehen, bei denen ich mir nicht nur die "member", d.h. die Connectoren, sondern auch die verknüpften Applications holen kann. Ich muss nur ein "?$expand=applications" an die URL anhängen:

((Invoke-MGGraphRequest `
   -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=applications' `
   -OutputType PSObject).value `
   | where {$_.connectorGroupType-eq "exchangeOnline"} ).applications

id                                   appId                                onPremisesPublishing
--                                   -----                                --------------------
eb92551b-9c63-4472-a8dc-2be7fcf00313 25103235-c936-4d6b-8686-f1eca4e70286 @{externalUrl=…

((Invoke-MGGraphRequest `
   -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=applications' `
   -OutputType PSObject).value `
   | where {$_.connectorGroupType -eq "exchangeOnline"} ).applications[0].onpremisespublishing

Auch hier habe ich auf "connectorGroupType = exchangeOnline" gefiltert und mir die erste Application anzeigen lassen.

Hier finde ich aber keine ID o.ä. aber aus dem "externalURL"-Feld kann ich mir die AppID aus dem Hostnamen extrahieren. 

HybridApplication anzeigen

Mit dem Wissen um die AppID kann ich nun über einen anderen Endpunkt auch die gleichen Details erhalten. Den Pfad habe ich auch wieder aus dem Installationsprotokolle des HCW. Im Pfad ist die TenantID und die AppID enthalten:

GET https://graph.microsoft.com/beta/<TenantID>/applications/<AppID>/OnPremisesPublishing

Meine Application im Labor-Tenant hatte zu der Zeit folgende Daten:

Invoke-MGGraphRequest `
   -Uri "https://graph.microsoft.com/edu/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/eb92551b-9c63-4472-a8dc-2be7fcf00313/OnPremisesPublishing" `
   -Method get -OutputType PSObject

Die Herausforderung ist dabei natürlich, die beiden IDs zu erhalten. Die TenantID können Sie noch recht einfach im AzureAD-Portal unter https://portal.azure.com direkt auf der Startseite ablesen. Für die Ermittlung der AppID habe ich aber noch keinen offiziellen Weg gefunden.

ich habe den Verdacht, dass HCW die Information aus dem dem Namensteil der ExternalURL beim bereit konfigurierten "Migration Endpoint" ausliest und bei einer neuen Installation eine neue GUID erstellt. Auch im Code von HybridManagment.psm1 gibt es eine Funktion "New-HybridApplication", die so vorgeht:

Wenn Sie aber den HCW ausführen, dann finden sie im Log auch den Namen der Application. Suchen Sie nach dem Text "connectorapplicationname". Sie sollten etwas in der Form finden:

 

Eventuell kommt er auch irgendwo aus dem lokalen Active Directory und der Exchange Organization. Aber selbst ein LDIFDE-Export und eine suche nach der GUID als auch nach der BASE64 codierten GUID brachte keine Treffer.

$guid = "eb92551b-9c63-4472-a8dc-2be7fcf00313" 
[system.convert]::ToBase64String(([GUID]($guid)).ToByteArray())

Was dann wieder für eine Abfrage aus dem AzureAD spricht. Ich hoffe Sie haben ihre AppID aus dem HCWLog gefunden.

HybridApplication löschen

Aus dem Code des Moduls HybridManagment.psm1 kann ich auch die Funktion "Remove-HybridApplication" analysieren. Das Modul nutzt Invoke-Webrequest, um mit einem jedes mal neu erstellten AuthToken einen Graph-Aufruf zu starten. Das geht mit MGGraph einfacher:

Invoke-MGGraphRequest `
   -Uri "https://graph.microsoft.com/beta/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/eb92551b-9c63-4472-a8dc-2be7fcf00313" `
   -Method PATCH `
   -Body "{"onPremisesPublishing": null}"

Indem man den Body mit "Null" setzt, wird die App gelöscht.

Ich habe Kundenumgebungen gesehen, in denen es scheinbar keine Apps mehr gegeben hat aber die InternalURL des Exchange Servers die Neuanlage einer App mittels HCW verhindert hat.

Ich habe noch keinen Weg gefunden, wie ich im AzureAD-Verzeichnis nach solchen Werten suchen kann 

HybridApplication anlegen

Wenn ihr HCW die Applikation nicht anlegen kann, dann könnte es helfen, wenn Sie die Applikation anlegen. Bei der Installation durch HCW erfolgt dies alles im Hintergrund. Mit Fiddler habe ich aber den entsprechenden Aufruf mitgeschnitten:

PATCH https://graph.microsoft.com/beta/463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c/applications/eb92551b-9c63-4472-a8dc-2be7fcf00313 HTTP/1.1
Authorization: Bearer eyJ0e.....
Content-Type: application/json; charset=utf-8
Host: graph.microsoft.com
Content-Length: 563
Expect: 100-continue

{
   "onPremisesPublishing":{
      "applicationServerTimeout":"Default",
      "applicationType":"microsoftapp",
      "externalAuthenticationType":"passthru",
      "externalUrl":"https://eb92551b-9c63-4472-a8dc-2be7fcf00313.resource.mailboxmigration.his.msappproxy.net:443/",
      "internalUrl":"https://ex01.uclabor.de:443/",
      "isOnPremPublishingEnabled":true,
      "isTranslateHostHeaderEnabled":false,
      "isTranslateLinksInBodyEnabled":false,
      "singleSignOnSettings":null,
      "verifiedCustomDomainCertificatesMetadata":null,
      "verifiedCustomDomainKeyCredential":null,
      "verifiedCustomDomainPasswordCredential":null
   }
}

Hier legt HCW in meinem Tenant (GUID = 463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c) eine neue Application (GUID = eb92551b-9c63-4472-a8dc-2be7fcf00313) an. Auch das geht per Powershell und in HybridManagment.psm1 ist auch ein Beispiel im Code von "Update-HybridApplication" enthalten. Eine Neuanlage ist nämlich auch nur ein "Update" auf eine nicht vorhandene Application:

param (
   $TenantID="463e98ae-b7d4-4af2-8ef8-3eda0b4d8a7c",  #MSXFAQLAB
   $AppID="eb92551b-9c63-4472-a8dc-2be7fcf00313",
   $targetUri = "https://ex01.uclabor.de"
)

    $application = @{
        "onPremisesPublishing" = @{
                                    "applicationServerTimeout" = "Default"
                                    "applicationType" = "microsoftapp"
                                    "externalAuthenticationType" = "passthru"
                                    "externalUrl" = "https://$($AppId).resource.mailboxmigration.his.msappproxy.net:443/"
                                    "internalUrl" = $targetUri
                                    "isOnPremPublishingEnabled" = $true
                                    "isTranslateHostHeaderEnabled" = $false
                                    "isTranslateLinksInBodyEnabled" = $false
                                    "singleSignOnSettings" = $null
                                    "verifiedCustomDomainCertificatesMetadata" = $null
                                    "verifiedCustomDomainKeyCredential" = $null
                                    "verifiedCustomDomainPasswordCredential" = $null
                                    }
                    }

Invoke-MGGraphRequest `
   -Uri "https://graph.microsoft.com/edu/$($tenant)/applications/$($appId)" `
   -Method PATCH `
   -Body ($application | ConvertTo-Json)

Stelle Sie natürlich sicher, dass die Parameter passen. Ansonsten dürfen Sie gleich den vorherigen Abschnitt zum Entfernen wieder durchführen.

All diese Apps sind nicht im Azure Portal zu sehen!

Weitere Links