Nachdem das letzte PRTG Skript zur Überwachung der Office 365 Dienste anscheinend doch ganz gut angekommen ist, habe ich eine Anfrage erhalten, ob ich nicht auch etwas schreiben könne, mit dem man die Lizenzen in Office 365 überwachen kann (später haben wir dann noch den DirSync Status mit dazugenommen). Na ja, das hat dann nicht so lange gedauert, bis es mich in den Fingern juckte … der Anfang war relativ leicht, allerdings hat PRTG dann für etwas Hirn-Akrobatik gesorgt, dazu aber später mehr …Deshalb das Wichtigste zuerst: Das funktionierende Skript. Nach einigem Testen und Verfeinern (Danke für die Idee und das Feedback, Ollie), habe ich das fertige Skript auf GitHub veröffentlicht:

PRTG-O365Licensing auf Github.

Die Installation

Einfach die aktuelle Version des Skripts aus GitHub (hier) herunterladen und in den Ordner “Custom Sensors\EXEXML” ablegen.

Da sich das Skript per PowerShell an Office 365 anmelden muss, müssen auf der Probe noch folgende Komponenten installiert werden:

  1. Microsoft Online Services-Sign in assistant (Download)
  2. Azure Active Directory-Module for Windows PowerShell (Download)

Wichtig: PRTG startet PowerShell Skripte in der 32-Bit Umgebung, damit muss die Execution Policy der PowerShell 32-Bit min. auf RemoteSigned umgestellt werden. Da aber das Azure Active Directory Modul nur als 64-Bit Version verfügbar ist (und das Skript automatisch auf 64-Bit umschaltet), muss in der PowerShell 64-Bit auch die Execution Policy geändert werden.

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

Update: Windows markiert heruntergeladene Dateien aus dem Internet als potentiell unsicher. Dadurch wird die Ausführung in PowerShell erst einmal unterbunden. Das Skript muss also zuvor entweder per PowerShell oder über die Eigenschaften der Datei im Explorer freigeschaltet werden.

Unblock-File -Path Get-PRTGO365Licenses.ps1

Die Konfiguration

office365licenses_sensordetailsDas Skript benötigt für die Abfrage der entsprechenden Informationen ein Login zum Office 365 Tenant (administrativ). Da ich persönlich ungerne Login Daten im Klartext hinterlege, habe ich diese im übergeordneten Gerät (in PRTG) als Linux Benutzername und Passwort hinterlegt. Somit kann das Skript dann mit folgenden Parametern gestartet werden:

-O365User %linuxuser -O365pass %linuxpassword

Da das Skript schon etwas Last auf die Probe bringt, sollte man den Scanning Interval auf 5 Minuten oder höher stellen.

Das Ergebnis

office365licenses_outputNachdem der Sensor den ersten Durchlauf absolviert hat, sollte eine Ausgabe ähnlich der nebenstehenden erfolgen. Wahrscheinlich wollt ihr die Schwellwerte für die verschiedenen Kanäle noch an eure Ansprüche anpassen, aber grundsätzlich sollte das schon mal das Wichtigste abdecken. Es werden folgende Informationen angezeigt:

  • Time since last DirSync: Zeit in Stunden seit dem letzten DirSync Lauf (falls DirSync eingesetzt wird)
  • Time since last PasswordSync: Zeit in Stunden seit der letzten Passwort Synchronisation (falls PasswordSync eingesetzt wird)
  • <Tenant>:<Lizenz> – Active Licenses: Gesamtzahl der Lizenzen
  • <Tenant>:<Lizenz> – Available Licenses: Anzahl der verfügbaren Lizenzen
  • <Tenant>:<Lizenz> – Consumed Licenses: Anzahl der benutzten Lizenzen
  • <Tenant>:<Lizenz> – Warning Licenses: Anzahl der Lizenzen mit Warnungen oder Hinweisen

Je nach dem, welche Berechtigungen der für die Anmeldung an Office 365 benutzte Account hat, kann es sein, dass mehrere Office 365 Tenants abgefragt werden können. Außerdem ist es möglich, dass mehrere Lizenzverträge innerhalb eines Tenants auftauchen. In jedem dieser Fälle erscheinen dann zusätzliche Lizenzaufstellungen für all diese Kombinationen. Werden zu viele Lizenzen angezeigt, kann man dies noch einschränken.

Das Feintuning

windows-powershell_showskusWenn die Abfrage also Lizenzverträge anzeigt, die mich nicht interessieren, oder aber für jeden Vertrag ein eigener Sensor erstellt werden soll, kann das Skript mit weiteren Parametern aufgerufen werden:

  • -IncludeSku “Sku1″,”Sku2”,…: Nur die aufgezählten SKUs (also <Tenant>:<Lizenz>) werden ausgelesen
  • -ExcludeSku “Sku1″,”Sku2”,…: Die angegebenen SKUs werden nicht ausgelesen
  • -ShowMySkus: Ist nur für die manuelle Ausführung in PowerShell gedacht: Gibt eine Liste der SKUs in lesbarer Form aus. Diese kann für das Füllen der Parameter IncludeSku und ExcludeSku verwendet werden

Beispiele:

# Liest nur die Lizenzen zu den Subscriptions ENTERPRISEPACK und POWER_BI_STANDARD des Tenants "Tenant1" aus
Get-PRTGO365Licenses.ps1 -O365User user@tenant.onmicrosoft.com -O365Pass MySecretPass -IncludeSku "Tenant1:ENTERPRISEPACK","Tenant1:POWER_BI_STANDARD"

# Schließt die Subscription POWER_BI_STANDARD aus dem Monitoring aus
Get-PRTGO365Licenses.ps1 -O365User user@tenant.onmicrosoft.com -O365Pass MySecretPass -ExcludeSku "Tenant1:POWER_BI_STANDARD"

# Zeigt alle verfügbaren Lizenzen in lesbarer Form an
Get-PRTGO365Licenses.ps1 -O365User user@tenant.onmicrosoft.com -O365Pass MySecretPass -ShowMySkus

AccountSkuId              ActiveUnits WarningUnits ConsumedUnits
------------              ----------- ------------ -------------
xxxxxxxxxx:ENTERPRISEPACK 77          0            74

Die Herausforderung

Das Schwierigste am Skript war nicht die Abfrage der relevanten Daten aus Office 365 oder die Aufbereitung für PRTG, sondern ein “hausgemachtes” Problem von PRTG: Der Probe Prozess von PRTG läuft immer unter 32-Bit, auch wenn das Betriebssystem in 64 Bit installiert ist und der ausführende Computer mehr als 6 GB RAM hat (was beim Core dafür sorgt, dass die 64-Bit Version installiert wird). Nun gibt es das Azure Active Directory Modul für PowerShell nicht als 32-Bit Version und somit kann die 32-Bit PowerShell, die vom PRTG Probe Prozess gestartet wird, auch nicht auf das Modul zugreifen.

Somit muss das Skript zum einen erkennen, dass es in einer 32-Bit Umgebung läuft (einfach) und dann dafür sorgen, dass es sich selbst in einer 64-Bit Umgebung neu aufruft (WTF?). Ok, klingt einfach (die Lösung ist dann auch einfach), aber wie? Ich habe wirklich einige Zeit mit meinem Freund Google verbracht, um den richtigen Weg zu finden. Da ich euch das ersparen möchte, hier die Essenz:

Wird auf einem 64-Bit System ein Prozess gestartet, werden verschiedene Systemordner von Windows automatisch umgeleitet (je nach “Bit-igkeite” des Prozesses), um es den Programmen (oder besser den Programmierern) “einfach” zu machen, weiterhin zu funktionieren, auch ohne genau zu wissen, auf welche DLLs sie zugreifen müssen (32- oder 64-Bit).

Systemordner 32-Bit Prozess 64-Bit Prozess
%windir%\System32 32-Bit Versionen der Dateien 64-Bit Versionen der Dateien
%windir%\SysWOW64 nicht verfügbar 32-Bit Versionen der Dateien
%windir%\Sysnative 64-Bit Versionen der Dateien nicht verfügbar

administrator_-windows-powershell-x86Die Verzeichnisse System32 und SysWOW64 kennt man ja aus der Explorer Ansicht, das Verzeichnis Sysnative ist mir so aber noch nie begegnet. Selbst in einer 32-Bit PowerShell ist es nicht sichtbar, allerdings kann darauf zugegriffen werden. Da es nur für einen 32-Bit Prozess verfügbar ist und darin dann die 64-Bit Systemdateien enthalten sind, kann darüber auch eine 64-Bit PowerShell gestartet werden. Und genau darin liegt der Trick, aus einer 32-Bit PowerShell eine 64-Bit PowerShell zu machen. Der Code dazu sieht dann (in meinem Fall) so aus:

#Check for 32 bit environment
if ($env:PROCESSOR_ARCHITECTURE -eq "x86") {
    # Launch script in 64 bit environment
    $ScriptParameter = "-O365User '$O365User' -O365Pass '$O365Pass' "
    if ($IncludeSku -ne $null) {
        $ScriptParameter += "-IncludeSku '$($IncludeSku -join "','")' "
    }
    if ($ExcludeSku -ne $null) {
        $ScriptParameter += "-ExcludeSku '$($ExcludeSku -join "','")' "
    }
    if ($ShowMySkus) {
        $ScriptParameter += "-ShowMySkus "
    }
    # Use Sysnative virtual directory on 64-bit machines
    Invoke-Expression "$env:windir\sysnative\WindowsPowerShell\v1.0\powershell.exe -file '$($MyInvocation.MyCommand.Definition)' $ScriptParameter"
    Exit
}

Hier ist Zeile 15 eigentlich die interessanteste. Das Skript ruft also eine 64-Bit PowerShell auf und übergibt dorthin den vollständigen Pfad zur eigenen Datei und die (davor mühsam zusammengeklöppelten) Parameter (die zu allem Überfluss auch noch Arrays enthalten können).

Das Fazit

Funktioniert … und wieder was gelernt. Wenn ihr Fehler findet oder Verbesserungsvorschläge habt, benutzt bitte gerne den Issue Tracker des Github Projekts. Ich freue mich natürlich auch immer über Feedback, wenn ihr das Tools einsetzt. Gerne könnt ihr es auch wie Ollie machen und mich mit guten Ideen für neue PRTG Sensoren bombardieren – wenn ich Zeit habe und das Thema eine Lücke auch für andere füllen kann, setze ich mich (sehr wahrscheinlich) dran. Also: Benutzt die Kommentarfunktion!