Hybrid Agent Statusabfrage
Immer mehr Firmen nutzen den "Modern Hybrid"-Mode über lokal installierte Agenten. Sie habe damit fast alle Möglichkeiten einer Koexistenz, inklusive Migration, Free/Busy-Anzeige etc. Nur der Zugriff in Microsoft Teams auf den Kalender von OnPremises liegenden Postfächer ist nicht möglich. Dennoch müssen Sie natürlich überwachen, ob der lokale Agent funktioniert..
Beachte dazu auch die Seite HybridManagment.psm1 und Azure AD Application Proxy
Diese Seite beschreibt auch die Herleitung, d.h. Angangspunkt, Analyse, Test, Konfigurationsanpassungen und ein Skript zur Integration in eigene Dienste.
Status abfragen
Leider liefert ihnen Microsoft 365 keine Stelle, an der Sie den aktuellen Status des Hybrid Agenten ablesen können. Das geht zum Teil über eine Überwachung mit dem lokalen Eventlog (Siehe HCW Logging) aber wichtiger ist die Information aus Microsoft 365. Für die Anfrage des Status zum Modern Hybrid Agenten bietet Microsoft zwei Optionen an:
- HCW
Sie können den HCW - Hybrid Configuration Wizard starten und während der Konfiguration des Modern HybridMode zeigt er ihnen direkt an, welche Agenten es gibt und welchen Status diese haben.
Das ist natürlich nicht - HybridManagement-PowerShell
Die zweite Option ist eine Powershell, die auf dem Agenten mit installiert wird, aber (Stand Nov 2024) nicht die gewünschte Code-Qualität hat.
Zum PowerShell-Modul habe ich eine eigene Seite auf HybridManagment.psm1, die explizit auf die Probleme etc. eingeht.
Die GUI eignet sich natürlich nicht für entsprechende Automatisierungen und auch die HybridManagment.psm1-PowerShell erlaubt keine automatische Anmeldung und MFA, hat eine Abhängigkeit zur AzureAD PowerShell und erst eine aktuelle Version von "https://aka.ms/hybridconnectivity" unterstützt z.B.: MFA.
Commandlets im Modul
Aber das Schöne an einer Powershell ist, dass die Quellen "offen" sind und Code von Microsoft kann oft als Quelle für eine eigene Optimierung genutzt werden. Diese PS1M-Datei ist allerdings "gemischt". Wenn ich das Modul importiert habe, stellt es mehrere Commandlets bereit:
PS C:\Downloads> Import-Module .\HybridManagement.psm1 PS C:\Downloads> get-command -Module hybridmanagement CommandType Name Version Source ----------- ---- ------- ------ Function GetAuthHeader 0.0 HybridManagement Function GetAuthToken 0.0 HybridManagement Function Get-HybridAgent 0.0 HybridManagement Function Get-HybridApplication 0.0 HybridManagement Function New-HybridApplication 0.0 HybridManagement Function Remove-HybridApplication 0.0 HybridManagement Function TestEndpoints 0.0 HybridManagement Function Test-HybridConnectivity 0.0 HybridManagement Function TestoneEndpoint 0.0 HybridManagement Function TestProxySettings 0.0 HybridManagement Function TestTLSSettings 0.0 HybridManagement Function Update-HybridApplication 0.0 HybridManagement
Für die Anwendung sind nur eine Teilmenge an Befehlen relevant aber die internen Commandlets sind nicht als "privat" deklariert und damit versteckt. Auch die Namensgebung ohne den "Bindestring" zwischen Verb und Subject entspricht eher nicht nach Microsoftvorgaben. Zumindest folgen aber die öffentlichen Commandlets einer "*-hybrid*" Namenskonvention.
Im Code wird dann manuell ein AuthToken abgerufen, ein AuthHeader erstellt und mit Invoke-RestMethod ein Graph-Aufruf auf eine "/EDU/"-URL gestartet.
Das macht es natürlich interessant, wie der Wert gefüllt wird. Im Code sehen Sie daher einmal den Aufruf von "GetAuthToken" gefolgt von GetAuthHeader, bei dem das erhaltene Token so umformatiert wird, dass der Invoke-RestMethod damit arbeiten kann. Auch der Code ist enthalten:
Damit habe ich aber genug gesehen um mir meine Statusüberwachung selbst zu bauen.
Status mit MGGraph
Zuerst habe ich es einfach gemacht und als "Delegate" den Status abgerufen. Zuerst habe ich MgGraph installiert und verbunden.
Install-Module Microsoft.Graph Connect-MgGraph ` -TenantId msxfaqlab.onmicrosoft.com ` -ClientId "1950a258-227b-4e31-a9cf-717495945fc2" ` -Scopes AuditLog.Read.All,Directory.AccessAsUser.All,email,openid,profile
Die ClientID "1950a258-227b-4e31-a9cf-717495945fc2" steht übrigens für "Microsoft Azure PowerShell".
- Verify first-party Microsoft
applications in sign-in reports
https://learn.microsoft.com/en-us/troubleshoot/entra/entra-id/governance/verify-first-party-apps-sign-in
Die ist aber schon abgekündigt. Siehe EOL MSOnline und AzureAD PowerShell. Es geht aber auch mit einer anderen ClientID. Da müssen sie aber wieder "Consent" erteilen. Das habe ich mir aber erst mal gespart, denn später soll die Abfrage ja eh automatisiert mit App-Permissions laufen. Aus den Fiddler-Logs des HCW habe ich gesehen, wie HCW die Connectoren ausliest.
Invoke-MGGraphRequest -Uri "https://graph.microsoft.com/edu/connectorGroups?`$expand=members"
Damit habe ich sowohl die öffentliche IP-Adresse, die Exchange von mir sieht als auch einen "Status". Wobei der MgGrPH-Aufruf hier etwas tückisch ist, denn so liefert er ein Array zurück:
PS C:\temp> (Invoke-MGGraphRequest -Uri "https://graph.microsoft.com/edu/connectorGroups?`$expand=members").value.gettype() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array PS C:\temp> (Invoke-MGGraphRequest -Uri "https://graph.microsoft.com/edu/connectorGroups?`$expand=members").value Name Value ---- ----- isDefault False id e71c6f90-34d7-4b0e-be95-1f9e952a7bed connectorGroupType syncFabric name Default group for AD Sync Fabric members {} isDefault False id f371a12e-3cd8-4cf2-9629-2fc2d4e8d63c connectorGroupType syncFabric name Group-UCLABOR.DE-96e5bfea-ccfd-406c-bc6e-6673d01a4722 members {} isDefault False id 61914166-6324-4790-8515-49e30ac19114 connectorGroupType exchangeOnline name Default group for Exchange Online members {f92ae649-f9e5-48e4-ab32-99e84ba8b119}
Und da gibt es bei mir schon drei Gruppen mit eigenen "Member"-Einträgen. Erst wenn ich die Rückgabe mit "-OutputType PSObject" anfordere, sehe ich die Struktur:
Die URL "/edu" ist natürlich etwas suspekt und daher habe ich ein paar andere URLs ausprobiert und eine offizielle URL gefunden, die sogar noch die Version liefert.
URL | Ergebnis |
---|---|
https://graph.microsoft.com /edu /connectorGroups |
200 OK { "@odata.context":"https://graph.microsoft.com/edu/$metadata#connectorGroups(members())", "value": [ { "id":"e71c6f90-34d7-4b0e-be95-1f9e952a7bed", "name":"Default group for AD Sync Fabric", "connectorGroupType":"syncFabric", "isDefault":false, "members":[] }, { "id":"f371a12e-3cd8-4cf2-9629-2fc2d4e8d63c", "name":"Group-UCLABOR.DE-96e5bfea-ccfd-406c-bc6e-6673d01a4722", "connectorGroupType":"syncFabric", "isDefault":false, "members":[] }, { "id":"61914166-6324-4790-8515-49e30ac19114", "name":"Default group for Exchange Online", "connectorGroupType":"exchangeOnline", "isDefault":false, "members": [ { "id":"f92ae649-f9e5-48e4-ab32-99e84ba8b119", "machineName":"EX01.UCLABOR.DE", "externalIp":"80.66.20.27", "status":"inactive"} ] } ] } |
Diese API liefert aber auch offiziell den Agent https://graph.microsoft.com /beta /onPremisesPublishingProfiles /applicationProxy /connectorGroups/$expand=members |
200 OK { "@odata.context":"https://graph.microsoft.com/beta/$metadata#onPremisesPublishingProfiles('applicationProxy')/connectorGroups(members())", "value": [ { "id":"e71c6f90-34d7-4b0e-be95-1f9e952a7bed", "name":"Default group for AD Sync Fabric", "region":"eur", "connectorGroupType":"syncFabric", "isDefault":false, "members":[] }, { "id":"f371a12e-3cd8-4cf2-9629-2fc2d4e8d63c", "name":"Group-UCLABOR.DE-96e5bfea-ccfd-406c-bc6e-6673d01a4722", "region":"eur", "connectorGroupType":"syncFabric", "isDefault":false, "members":[] }, { "id":"61914166-6324-4790-8515-49e30ac19114", "name":"Default group for Exchange Online", "region":"eur", "connectorGroupType":"exchangeOnline", "isDefault":false, "members": [ { "id":"f92ae649-f9e5-48e4-ab32-99e84ba8b119", "machineName":"EX01.UCLABOR.DE", "externalIp":"80.66.20.27", "status":"inactive", "version":"1.5.732.0"} ] } ] } |
https://graph.microsoft.com /beta /connectorGroups?$expand=members |
400 Bad Request { "error":{ "code":"BadRequest", "message":"Resource not found for the segment 'connectorGroups'.", "innerError":{"date":"2024-11-12T14:32:27",....} } } |
https://graph.microsoft.com /1.0 /connectorGroups?$expand=members |
404 Not Found { "error":{ "code":"ResourceNotFound", "message":"Invalid version: onpremisespublishingprofiles", "innerError":{"date":"2024-11-12T14:35:54", ...... } } } |
https://graph.microsoft.com /connectorGroups?$expand=members |
400 Bad Request { "error":{ "code":"BadRequest", "message":"Resource not found for the segment 'connectorGroups'.", "innerError":{"date":"2024-11-12T14:36:13", ...... } } } |
https://graph.microsoft.com /onPremisesPublishingProfiles /applicationProxy /connectorGroups?$expand=members |
404 Not Found { "error":{ "code":"ResourceNotFound", "message":"Invalid version: onpremisespublishingprofiles", "innerError":{"date":"2024-11-12T14:36:55", ...... } } } |
Anscheinend ist dies eine recht versteckte URL und
API, die zwar hinter graph.microsoft.com agiert und sicher
etwas mit AzureADAppProxy zu tun hat, aber eigenständig ist.
Mit den klassischen URLs kommen wir da nicht weiter.
- connectorGroup resource type
https://learn.microsoft.com/en-us/graph/api/resources/connectorgroup?view=graph-rest-beta - List members
https://learn.microsoft.com/en-us/graph/api/connectorgroup-list-members?view=graph-rest-beta&tabs=http
Und damit komme ich natürlich auch nicht mit den PowerShell-Gegenstücken wie z.B. "Get-MgBetaOnPremisePublishingProfileConnectorGroupMember" der MgGraphPowerShell heran.
- Get-MgBetaOnPremisePublishingProfileConnectorGroupMember
https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.beta.applications/update-mgbetaonpremisepublishingprofileconnectorgroup
Mit dem Connect am Anfang und der passenden URL mit etwas Parsing habe ich aber des Status
Install-Module Microsoft.Graph Connect-MgGraph ` -TenantId msxfaqlab.onmicrosoft.com ` -Scopes AuditLog.Read.All,Directory.AccessAsUser.All,email,openid,profile $Result = Invoke-MGGraphRequest ` -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=members' ` -OutputType PSObject $status = ($Result.value | where {$_.connectorGroupType -eq "exchangeOnline"}).members.status $status
Die Ausgabe ist dann einfach "Inactive" oder Active". Die meisten Firmen werden immer nur genau einen Modern Hybrid Agent installiert haben.
Berechtigungen
Es gilt auch hier das Prinzip der "geringsten Rechte" (Least Privilege). Leider ist das beim Abrufen des Status nicht sehr hilfreich, wenn mittels Graph Explorer (https://aka.ms/ge) kann ich ermitteln, welche Berechtigungen ich zum Aufruf eines Service benötige. Mit der URL erhalten wir:
Ein "Directory.ReadWrite.All" sollten Sie eigentlich nur sehr vertrauenswürdige Applikationen gewähren und diese auch schützen. Ein "weniger umfangreiches Recht" wie z.B. Application.ReadAll oder zumindest "Directory.Read.All" ist wohl nicht vorgesehen.
- Overview of Microsoft Graph permissions
https://learn.microsoft.com/en-us/graph/permissions-overview - Microsoft Graph permissions reference
https://learn.microsoft.com/en-us/graph/permissions-reference - Microsoft Graph permissions reference - Directory.ReadWrite.All
https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall
Status as App: App Anlegen
Damit ist es nun natürlich nicht mehr weit, solche Abfragen zu automatisieren und von einem Benutzer abzukoppeln. Nur über eine App kommen wir dann an MFA vorbei. Ich habe in meinem Tenant daher eine passende App angelegt. Das geht sehr einfach über das Azure Portal mit Anlage eines Client Secret und der Zuweisung der Berechtigungen. Da wir aber schon MGGraph gestartet haben, schreibe ich die MgGraph Powershell-Befehle auf:
param( $AppName = "MSXFAQ-HybridAgentStatus" ) # Sofern noch nicht erfolgt, bitte installieren oder aktualisieren. Install-Module Microsoft.Graph #Update -Module Microsoft.Graph # Gesondert Importieren müssen wir die relevanten mi PowerShell 5/7 nicht mehr # Import-Module Microsoft.Graph.Authentication # # Bitte interaktiv mit einem Admin-User # Sie benötigen die Entra ID Rolle "Application Adminstrator" oder "Global Administrator" # Evemtuell müssen Sie noch "Consent" erteilenn. Connect-MgGraph -Scopes Application.Read.All,Application.ReadWrite.All,User.Read.All # Nun legen wir eine neue App mit dem Namen an $App = New-MgApplication -DisplayName $AppName # Als Muster nutze ich hier ein App-Kennwort # Es sind maximal 2 Jahre Gültigkeitsdauer möglich $AppCred = @{ "displayName" = "$($Appname)-Credentials" "endDateTime" = (Get-Date).AddMonths(24) } $AppSecret = Add-MgApplicationPassword ` -ApplicationId $App.id ` -PasswordCredential $AppCred # Ausgabe der beiden Daten für die spätere Nutzung Write-Host "AppID : $($App.Id)" Write-Host "AppSecret: $($AppSecret.SecretText)" $App.PasswordCredentials # Reply Addressen setzen $RedirectURI = @("https://login.microsoftonline.com/common/oauth2/nativeclient") Update-MgApplication ` -ApplicationId $App.ID ` -IsFallbackPublicClient ` -PublicClient @{RedirectUris = @($RedirectURI)} #Add Application Permission #Directory.ReadWrite.All Application 19dbc75e-c2e2-444c-a770-ec69d8559fc7 $params = @{ RequiredResourceAccess = @( @{ ResourceAppId = "00000003-0000-0000-c000-000000000000" ResourceAccess = @( @{ Id = "19dbc75e-c2e2-444c-a770-ec69d8559fc7" Type = "Role" } ) } ) } Update-MgApplication -ApplicationId $App.ID -BodyParameter $params # Admin Consent einholeb $URL = "https://login.microsoftonline.com/$($App.PublisherDomain)/adminconsent?client_id=$($App.AppID)" # Browser starten, um als Admin einmalig den Comsent für die App Permission einzuholen Start-Process $URL
Wir können sehr viel in den Apps per PowerShell konfigurieren, aber der Admin Consent ist nicht einfach automatisierbar.
Wer natürlich im Hintergrund dem Azure Portal bzw. Browser auf die Finger schaut, kann den Aufruf ans Backend auch synthetisch nachbauen.
- App Password
- Grant admin consent to an AzureAD
application via PowerShell
https://f12.hu/2021/01/13/grant-admin-consent-to-an-azuread-application-via-powershell/
Kontrolle Azure Portal
Im Azure Portal finden wir dann die App samt (nicht mehr einsehbarem) AppSecret und Berechtigungen. Eventuell müssen Sie von "Owned Applications" auf "All Applications" klicken, da ich per PowerShell keinen Owner geflegt habe
Im Bereich "Certificates & Secrets" sehen Sie den Eintrag für das App-Kennwort.
Und unter API-Permissions die Berechtigungen
Nun können wir den Code auf "Anmeldung als App" umstellen.
$AppID="8d5f94a2-499b-460d-9062-deb1cd7f4e37" $AppSecret="<hier muss das lange App Secret rein" $AppPass = ConvertTo-SecureString -String $AppSecret -AsPlainText -Force $AppCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, $AppPass Connect-MgGraph ` -TenantId msxfaqlab.onmicrosoft.com ` -ClientSecretCredential $AppCred $Result = Invoke-MGGraphRequest ` -Uri 'https://graph.microsoft.com/beta/onPremisesPublishingProfiles/applicationProxy/connectorGroups/?$expand=members' ` -OutputType PSObject $status = ($Result.value | where {$_.connectorGroupType -eq "exchangeOnline"}).members.status $status
Nun ist es natürlich an ihnen, denn Code in geeigneter Weise zu erweitern, damit er in ihr Systemmonitoring integriert werden kann.
- Microsoft Graph permissions reference
https://learn.microsoft.com/en-us/graph/permissions-reference#all-permissions-and-ids - Microsoft Graph permissions reference - Directory.ReadWrite.All
https://learn.microsoft.com/en-us/graph/permissions-reference#directoryreadwriteall - How to use Connect-MgGraph – All Options
https://lazyadmin.nl/powershell/connect-mggraph/#client-secret - Create an Entra app with API permissions, admin consent and a secret using
Microsoft Graph PowerShell SDK
https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-microsoft-graph-powershell-sdk/ - Create Azure AD App Registration with Microsoft.Graph PowerShell
https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell
Weitere Links
- HybridManagment.psm1
- Graph und Exchange On-Premises
- Exchange Hybrid
- Exchange Hybrid Agent
- HCW Logging
- Azure AD Application Proxy
- Authentifizierung im Wandel der Zeit
- EOL MSOnline und AzureAD PowerShell
- Get-O365Usage
- App Password
- Microsoft Hybrid Agent
https://docs.microsoft.com/en-us/exchange/hybrid-deployment/hybrid-agent#checking-the-status-of-your-hybrid-agents - Configure Microsoft Entra application proxy using Microsoft Graph APIs
https://learn.microsoft.com/en-us/graph/application-proxy-configure-api?tabs=http - Create Azure AD App Registration with Microsoft.Graph
PowerShell
https://blog.icewolf.ch/archive/2022/12/02/create-azure-ad-app-registration-with-microsoft-graph-powershell