Get-O365Usage
Sie kennen doch sicher im Office 365 Portal die Webseite mit der "Adoption Rate". Auf der Grafik ist das schön anzuschauen und es gibt auch rechts oben den Link zum "Exportieren" als CSV-Datei aber ich hätte da schon gerne automatisiert in mein lokales Monitoring überführt.
Sehr schnell hatte ich die Links zum Thema gefunden, die mich aber auf die Graph-API verweisen.
- App Password
- reportRoot:
getTeamsUserActivityUserDetail
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/reportroot_getteamsuseractivityuserdetail - Working with Office 365 usage reports in
Microsoft Graph
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/report
Also war es nun doch mal an der Zeit sich per PowerShell der Schnittstelle zu nähern.
Hinweis: Einige Daten können Sie auch per PowerBI auswerten. Siehe dazu auch PowerBI und Microsoft 365 Usage
AppID und Key statt Benutzer/Kennwort
Wenn wir mal von anonymen Webseiten absehen, erfolgt heute jeder Zugriff auf Information erst nach einer erfolgreichen Authentifizierung des zugreifenden Prozesses unter Beachtung der zugewiesenen Berechtigungen (Autorisierung) und eventuell einer Protokollierung (Auditierung). Lange Zeit war es üblich, dass die Anmeldung mit einer Kombination aus Benutzername und Kennwort erfolgt. In Ausnahmefällen auch per Zertifikat, z.B. über eine Smartcard. In beiden Fällen konnten diese Informationen von unterschiedlichen Programmen genutzt werden. Es war also eine gewisse Mobilität des Zugriffs möglich. Interessanter ist hier natürlich die Funktion einer Applikation direkt Rechte zu geben. Ein Benutzername und Kennwort ist dann nicht mehr erforderlich.
Das bedeutet natürlich auch, dass Sie in Programmen und Skripten nicht mehr mit Benutzername und Kennworten hantieren müssen, sondern mit einer Zeichenkette für die Identifizierung der App (App ID) und einer kryptografischen Information (ClientSecret), mit der Sie ihre Anforderungen signieren und die dann nur von der Gegenseite verifiziert werden kann. Die Gegenseite ist dabei aber ein Token-Server, der ihnen dann ein Zugriffsticket ausstellt, in dem auch die Berechtigungen in Form von Rollen, hinterlegt sind. Damit kann eine Firma dann einer App bestimmte Rechte einräumen. Die App kann zwar auch andere Zugriffe versuchen aber das Zielsystem kann allein anhand des Zugriffstokens schon erkennen, ob der Zugriff gewährt werden soll.
Solange der Entwickler der App die ApplicationID und das ClientSecret geheim hält, kann er den Code auch anpassen und erweitern. Er kann aber nie mehr Rechte in Anspruch nehmen, als er bei der Anmeldung zugeteilt bekommt. Das funktioniert wunderbar per HTTPS und das Zielsystem muss auch keine direkte Verbindung zum Token-Issuer haben. Es muss aber dem Token-Issuer vertrauen. Aber schauen wir und das am Beispiel von AzureAD App. Graph und PowerShell einmal an. Mein Ziel ist es, die Nutzungsstatistiken von Office 365 Pro Plus auszulesen.
App Registration anlegen
Damit sich eine App später an Graph anmelden kann, muss ich die App erst einmal in meinem Azure Tenant registrieren. Das geht unter https://portal.azure.com mit folgenden Schritten:
Zuerst lege ich eine neue App Registration an:
Ich muss natürlich einen Namen vergeben und wähle "Web app / API" als Type. Die Sign-on URL ist für mein Beispiel nicht relevant aber muss ausgefüllt werden.
Danach ist die App angelegt und die Informationen werden angezeigt:
Hier ist der Inhalt des Feld "Application-ID" wichtig. Das ist eine GUID, die AzureAD mir nun zugewiesen hat. Diesen Wert muss ich später bei der Anmeldung im Skript verwenden.
Client_Secret anlegen
Als nächstes brauche ich einen Schlüssel, mit dem ich meine Anmeldung später signiere. Ich könnte mir selbst ein Schlüsselpaar errechnen und diesen dann hier hochladen. Einfacher ist es, von Azure einen Schlüssel generieren zu lassen. Dazu pflege ich einfach eine Beschreibung, wähle die Gültigkeit aus und drücke auf "Save".
Im nächsten Dialog ist dann der "Client Secret" zu sehen. Dies ist quasi das Kennwort für die App und sollte nie "öffentlich" sein. Kopieren Sie sich diesen String und übernehmen Sie ihn in ihr Programm. Sie können ihn nicht erneut anzeigen lassen. Sie können aber natürlich einen neuen Key generieren. Das ist hier nach einem Jahr auch erforderlich.
Berechtigungen für API zuweisen
Nachdem der Key erstellt ist, kann ich einen Punkt tiefer nun die Berechtigungen zuweisen. Dazu muss ich zuerst eine API auswählen. Bei mir ist es "Graph" als Zielsystem. Office 365 bietet ihnen natürlich noch viele andere APIs an.
Nach der Auswahl der API kann ich dann die zu dieser API unterschiedlichen Rollen, d.h. Berechtigungen, vergeben. Wenn sie mit der Maus etwas über dem Eintrag stehen bleiben, sehen Sie den internen Namen.
Das "Yes" dahinter bedeutet, dass dieses Recht nur durch einen Administrator freigegeben werden kann. Es gibt nämlich auch die Möglichkeit, dass ein Anwender selbst einer von jemand anderem bereitgestellten Applikation entsprechende Berechtigungen vergibt. Darauf gehe ich hier aber erst mal nicht weiter ein.
Für mein Beispiel brauche ich später das Application-Recht "Reports.Read.All".
- How to select permissions for a given
API
https://docs.microsoft.com/en-us/azure/active-directory/develop/perms-for-given-api?WT.mc_id=UI_AAD_Registered_Apps_Troubleshooting_L2_Overview - Berechtigungsbereiche |
Graph-API-Konzepte
https://docs.microsoft.com/de-de/previous-versions/azure/ad/graph/howto/azure-ad-graph-api-permission-scopes
Powershell: Graph Token erhalten
Ich habe das Skript in die Einzelteile aufgespalten, um die Abschnitte zu erklären.
get-o365usagedata.20190208.ps1
Hier ist das komplette Skript aber natürlich ohne die
ClientSecrets meines Tenant
Zuerst definiere ich die wesentlichen Variablen als Parameter. Hier müssen Sie natürlich ihre eigenen AppID und AppKey eintragen
param ( [string]$LoginUrl = "https://login.microsoft.com", # Graph API URLs. [string]$ResourceUrl = "https://graph.microsoft.com", # Ressource API URLs. [string]$AppID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # App ID aus dem Azure Portal . [string]$Appkey = "xxxxxxxxxxxxxxx", # App Key from Portal [string]$TenantName = "msxfaq.onmicrosoft.com", # tenant name. [string]$GraphUrl = "https://graph.microsoft.com/v1.0/reports/getOffice365ActivationsUserDetail" [string]$csvfilename = ".\report.csv" )
Diese Daten nutze ich dann um ein OAUTH Token zu erhalten. Dazu muss ich einen Body formatieren und einen REST-Aufruf starten
# Get OAUTH Token $Body = @{ grant_type = "client_credentials"; resource = $ResourceUrl; client_id = $AppID; client_secret = $AppKey } [object]$OAuth = Invoke-RestMethod ` -Method Post ` -Uri "$($LoginUrl)/$($TenantName)/oauth2/token?api-version=1.0" ` -Body $Body
Die Variable OAUTH enthält dann in etwa folgen Daten. Das AccessToken habe ich natürlich gekürzt
token_type : Bearer expires_in : 3600 ext_expires_in : 3600 expires_on : 1549577032 not_before : 1549573132 resource : https://graph.microsoft.com access_token : eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFRQUJBQUFBQUFDRWZleFh4amFtUWIzT2VHUTRHdWd2U3dkVFVvZnA2dll6Q2NOTlpsUThEdXBNcVNrQ1NJbmhVTHdEQmhHVnhBOVJjckVYSHR2 SmFiRVNfdW1XWWNmby1EbzFtX3QzU2E0Z2pUczFtQTVSc3lBQSIsImFsZyI6IlJTMjU2IiwieDV0IjoiLXN4TUpNTENJRFdNVFB2WnlKNnR4LUNEeHcwIiwia2lkIjoiLXN4TUpNTEN JRFdNVFB2WnlKNnR4IsInN1YiI6ImRmNjJmZDRlLTVhMmMtNGQwNC1hNDg2LTI2OTAyNDYyMzQ5YSIsInRpZCI6ImRlMjFjMzAxLWE0YWUtNDI5Mi1hYTA5LTZkYjcxMGE1OTBhNiIs 9j39HXL9vsw
Sie können das Token auf verschiedenen Webseiten (z.B. jwt.ms auch decodieren lassen.
In dem Token müssen Sie ihre AppID und den Namen aber vor allem auch die Rollen sehen. Wenn die Rollen fehlen, dann ist die Zuweisung im AzureAD noch nicht korrekt umgesetzt worden.
- Get access tokens to call Microsoft
Graph
https://docs.microsoft.com/en-us/graph/auth-overview - ID tokens
https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens - Oauth v2.0-Protokolle – SPAs unter
Verwendung des impliziten Flusses
https://docs.microsoft.com/de-de/azure/active-directory/develop/v2-oauth2-implicit-grant-flow - Azure Active Directory v2.0 und der
OAuth 2.0-Clientanmeldeinformations-Flow
https://docs.microsoft.com/de-de/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
Authentication Header bauen
Über den Reiter "claims" werden die Felder noch mit einer Beschreibung versehen. Diese Token nutze ich nun, um die eigentliche Anfrage an die Graph-API zu stellen. Dazu baue ich zuerst den Header mit der Authentifizierung zusammen:
$HeaderParams = @{ 'Authorization' = "$($OAuth.token_type) $($OAuth.access_token)" }
In der Variable landet dann etwas wie:
$HeaderParams Name Value ---- ----- Authorization Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFR....
Damit spreche ich dann einfach die Graph-API an. Die Try/Catch-Anweisung ist erforderlich, damit ich im Falle eines Fehlers auf den Error-Stream zugreifen und die Details ausgeben kann:
if ($null -eq $OAuth.access_token) { Write-Error "No Access Token" } else { # Perform REST call. $HeaderParams = @{ 'Authorization' = "$($OAuth.token_type) $($OAuth.access_token)" } try { Invoke-WebRequest -UseBasicParsing -Headers $HeaderParams -Uri $GraphUrl -outfile $csvfilename write-host " Result:" $Result } catch { $resultstream = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($resultstream) $ErrorBody = $global:reader.ReadToEnd(); write-host " ResultError :" $ErrorBody } }
Wenn der Abruf fehlerfrei erfolgt ist, dann finden Sie eine CSV-Datei mit folgendem Aufbau:
Das ist natürlich die "Details-Seite, in der jeder Benutzer für jedes Produkt eine eigene Zeile hat. Allerdings sind hier die Computer selbst nicht enthalten.
Fiddler
Mit Fiddler kann auch sehr gut die Konversation analysiert werden. Es sind drei Request:
Im Detail bedeuten diese:
- Paket 7
Der Client greift auf login.microsoft.com zu, um mit den Anmeldedaten ein Bearer-Token zu erhalten
- Paket 9
Das Skript greift nun mit der Authorization "Bearer" auf die Ressource zu.
Ich erhalte hier aber nicht die Information direkt, sondern einen "302 Found" mit einer Umleitung auf eine neue URL, die schon vorauthentifiziert ist - Paket 12: Download der CSV-Datei
Invoke-WebRequest folgt alleine schon dem 302 und lädt direkt die CSV-Datei herunter
Alles kein Hexenwerk, wenn es einmal funktioniert.
Fehlermeldungen
Es hat aber schon einige Sessions und Versuche gebraucht, bis ich das Ergebnis letztlich erreicht habe. Selbst kleinste Tippfehler in der URL oder anderswo führen einfach zu einem Fehler. Hier eine kleine Sammlung und ihrer Ursachen:
Meldung | Eine Ursache |
---|---|
{ "error": { "code": "BadRequest", "message": "Resource not found for the segment 'getOffice365ActivationsUserDetails'.", "innerError": { "request-id": "bfbbc1ea-e315-452e-9527-8512e387dcc4", "date": "2019-02-08T09:22:43" } } } |
Einfach eine falsche GraphURL. Beliebt sind Verwechselungen von Plural und Singular, also "getOffice365ActivationsUserDetails" statt "getOffice365ActivationsUserDetail" oder "getOffice365ActivationsUserCount" statt "getOffice365ActivationsUserCounts" Sie sehen schon hier, dass manchmal ein Plural und manchmal ein Singular genutzt wird. |
{
"error":"unauthorized_client",
"error_description":"AADSTS700016: Application with identifier '<guid>' was not found
in the directory 'msxfaq.onmicrosoft.com'. This can happen if the application has not
been installed by the administrator of the tenant or consented to by any user in the tenant.
You may have sent your authentication request to the wrong tenant\r\n
Trace ID: 12345678-1234-1234-1234-1234567890ab\r\n
Correlation ID: xxxx\r\nTimestamp: 2019-02-08 09:32:49Z",
"error_codes":[700016],
"timestamp":"2019-02-08 09:32:49Z",
"trace_id":"12345678-1234-1234-1234-1234567890ab",
"correlation_id":"12345678-1234-1234-1234-1234567890ab"}
|
Prüfen Sie die GUID der AppID |
{ "error":"invalid_client", "error_description":"AADSTS7000215: Invalid client secret is provided.\r\n Trace ID: 12345678-1234-1234-1234-1234567890ab\r\n Correlation ID:12345678-1234-1234-1234-1234567890ab\r\n Timestamp: 2019-02-08 09:38:39Z", "error_codes":[7000215], "timestamp":"2019-02-08 09:38:39Z", "trace_id":"12345678-1234-1234-1234-1234567890ab", "correlation_id":"12345678-1234-1234-1234-1234567890ab"} |
Das Client Secret passt nicht zur App |
{ "error": { "code": "Authorization_RequestDenied", "message": "Insufficient privileges to complete the operation.", "innerError": { "request-id": "12345678-1234-1234-1234-1234567890ab", "date": "2019-02-08T09:41:50" } } } |
SSie greifen auf eine URL zu, für die sie keine Berechtigungen haben |
Weitere Auswertungen
Vielleicht reicht ihnen ja auch eine weniger umfangreiche Auswertung. Dann nutzen sie den gleichen Code einfach mit anderen Graph-URLs. Hier ein paar Beispiele für URls, die Sie für den Parameter "GraphURL" alternativ verwenden können
Berichtsurl |
Datensatzbeispiel |
---|---|
Office 365 Aktivierungen
nach Produkt als Übersicht |
Report Refresh Date : 2019-02-05 Product Type : Office 365 ProPlus Assigned : 124 Activated : 89 Shared Computer Activation : 26 |
Office 365 Aktivierungen
nach Produkt und Client |
Report Refresh Date : 2019-02-05 Product Type : Office 365 ProPlus Windows : 160 Mac : 3 Android : 25 iOS : 38 Windows 10 Mobile : 25 |
Office 365 Aktivierungen
pro Benutzer und Client |
Report Refresh Date : 2019-02-05
User Principal Name : user1@uclabor.de
Display Name : User1
Product Type : Office 365 ProPlus
Last Activated Date : 2019-01-18
Windows : 3
Mac : 0
Windows 10 Mobile : 0
iOS : 5
Android : 0
Activated On Shared Computer : True
|
SharePoint
Benutzerinformation |
Report Refresh Date : 2019-02-05 User Principal Name : user1@uclabor.de Is Deleted : False Deleted Date : Last Activity Date : 2018-12-19 Viewed Or Edited File Count : 0 Synced File Count : 0 Shared Internally File Count : 0 Visited Page Count : 0 Assigned Products : OFFICE 365 ENTERPRISE E3+Microsoft Flow Free Report Period : 7 |
SharePoint Dateiaktionen |
Report Refresh Date : 2019-02-05 Viewed Or Edited : 718 Synced : 37 Shared Internally : 4 Shared Externally : Report Date : 2019-02-05 Report Period : 7 |
Teams: Endgeräte-Details pro
Anwender |
Report Refresh Date : 2019-02-05 User Principal Name : user1@uclabor.de Last Activity Date : 2019-02-05 Is Deleted : False Deleted Date : Used Web : No Used Windows Phone : No Used iOS : Yes Used Mac : No Used Android Phone : No Used Windows : Yes Report Period : 7 |
Teams: Nutzung pro Anwenders |
Report Refresh Date : 2019-02-05 User Principal Name : user1@uclabor.de Last Activity Date : 2019-02-05 Is Deleted : False Deleted Date : Assigned Products : ENTERPRISE MOBILITY + SECURITY E3+OFFICE 365 ENTERPRISE E5 WITHOUT AUDIO CONFERENCING+AUDIO CONFERENCING Team Chat Message Count : 0 Private Chat Message Count : 48 Call Count : 1 Meeting Count : 0 Has Other Action : Yes Report Period : 7 |
Mit dem Recht "Reports.Read.All" können Sie quasi alle URls aus dem Bereich der Office 365 Reports nutzen.
- Arbeiten mit Office
365-Verwendungsberichten in Microsoft Graph
https://docs.microsoft.com/de-de/graph/api/resources/report?view=graph-rest-1.0
Weitere Links
- Bearer Decoding
- Graph API
- App Password
- Test-Bearer - Schnell prüfen, ob die Gegenseite BEARER anbietet
-
PowerBI und Microsoft 365 Usage
Microsoft Office 365 Benutzungsdaten in PowerBi Desktop auswerten - Announcing “30 Days of Microsoft Graph”
Blog Series
https://aka.ms/30DaysMSGraph
https://developer.microsoft.com/en-us/graph/blogs/announcing-30-days-of-microsoft-graph-blog-series/ - reportRoot: getOffice365ActivationCounts
https://docs.microsoft.com/en-us/graph/api/reportroot-getoffice365activationcounts?view=graph-rest-1.0 - Office 365-Berichte im Admin Center -
Microsoft Office-Aktivierungen
https://docs.microsoft.com/de-de/office365/admin/activity-reports/microsoft-office-activations - Changelog for Microsoft Graph
https://docs.microsoft.com/en-us/graph/changelog - ADAL.NET
https://GitHub.com/AzureAD/azure-activedirectory-library-for-dotnet - Microsoft identity platform (Azure
Active Directory for developers)
http://aka.ms/aaddev - JWT OAUTH Token Decoder
https://jwt.ms - JSON Webtoken Decoder
https://jwt.io/ - Get access tokens to call Microsoft
Graph
https://docs.microsoft.com/en-us/graph/auth-overview - Pre requisite Register your application with Azure Active Directory
https://GitHub.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Pre-requisite-Register-your-application-with-Azure-Active-Directory - Azure-AD-Authentication-with-PowerShell-and-ADAL
https://GitHub.com/shawntabrizi/Azure-AD-Authentication-with-PowerShell-and-ADAL - reportRoot:
getTeamsUserActivityUserDetail
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/reportroot_getteamsuseractivityuserdetail - Working with Office 365 usage reports in
Microsoft Graph
https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/report - Retrieve and analyze Office 365 Usage
Data with PowerShell and Microsoft Graph API
https://www.altitude365.com/2018/09/23/retrieve-and-analyze-office-365-usage-data-with-powershell-and-microsoft-graph-api/ - Using the Microsoft Graph API with
PowerShell
https://adamtheautomator.com/microsoft-graph-api-powershell/
Beschreibt aber die Anmeldung des Skripts als "App" und nicht des Anwenders