🇩🇪 Deutsch (dieser Abschnitt) · 🇬🇧 English version below
OnePage-Web-App (PowerShell/Pode), die per Microsoft Graph die Postfachgrössen und OneDrive-Belegung aller Benutzer eines Microsoft-365-Tenants ausliest, in einer SQLite-Datenbank historisiert und als Dashboard darstellt:
- Kacheln: Gesamtbelegung, Mailboxen, OneDrive, Zuwachs seit Beginn sowie Benutzerzahl mit Aufschlüsselung pro Lizenztyp (inkl. freigegebener Postfächer)
- Diagramm: Belegung ĂĽber Zeit (Mailbox / OneDrive getrennt, Tooltip mit Gesamt)
- Benutzerliste: sortierbar (Klick auf Spaltenkopf), Volltextsuche, Filter (Quota > 80 %, Top 10, freigegebene Postfächer), Lizenzspalte, Quota-Balken mit Warnschwellen (80 % / 95 %). Freigegebene Postfächer werden erkannt (Postfach vorhanden, Konto deaktiviert, keine Lizenz) und separat ausgewiesen
- Einstellungen im UI: Tenant-ID, Client-ID, Client-Secret (verschlĂĽsselt gespeichert), Demo-Modus, Sprache (Deutsch/Englisch)
- Darstellung: Design Dunkel (Standard) oder Hell sowie eigene Farben für Akzent, Mailboxen, OneDrive, SharePoint, Seitenhintergrund und Karten, getrennt je Design. Live-Vorschau im Dialog, «Standardfarben» setzt das gewählte Design zurück. Praktisch, um das Dashboard optisch ins Intranet einzupassen
- SharePoint-Reiter: alle Websites mit Speicherbelegung, Typ (Team/Kommunikation), Besitzer, Dateizahl, aktiven Dateien, Seitenaufrufen und letzter Aktivität; Inaktiv-Markierung (> 90 Tage) als Archivierungs-Kandidaten, eigener Zeitverlauf, Suche/Sortierung/Filter wie bei den Benutzern
- Automatik: eingebauter Wochen-Schedule (Standard: Montag 06:00) erfasst die Daten selbstständig, solange der Server läuft
- Windows Server oder PC (getestet auch unter macOS/Linux fĂĽr Entwicklung)
- PowerShell 7 (
winget install Microsoft.PowerShell) - Module:
Install-Module Pode, PSSQLite -Scope CurrentUser(unter macOS/Linux wird statt PSSQLite automatisch das System-sqlite3benutzt)
Am einfachsten zuerst den Server starten und dann das Setup ausfĂĽhren, angemeldet als Global Administrator des Kunden-Tenants:
pwsh ./start.ps1
pwsh ./setup.ps1setup.ps1 erledigt alles in einem Lauf:
- installiert fehlende Module (Microsoft Graph, Pode, PSSQLite)
- legt die App-Registrierung an (oder verwendet die vorhandene), setzt die vier Graph-Berechtigungen samt Administratorzustimmung und erstellt ein Client-Secret (24 Monate)
- schaltet die Pseudonymisierung der Berichte ab (
-KeepConcealedNameslässt sie) - trägt Tenant-ID, Client-ID und Secret direkt im Dashboard ein. Läuft der Server, gehen die Werte über seine API, damit er das Secret unter seinem eigenen Konto verschlüsselt (passt also auch, wenn er als Dienst unter SYSTEM läuft)
- testet die Verbindung und startet die erste Erfassung. Solange neue Berechtigungen noch nicht aktiv sind, versucht es das bis zu 10 Minuten erneut
Das Script kann gefahrlos erneut laufen, etwa wenn das Secret abläuft: Es ergänzt
fehlende Berechtigungen und erstellt ein neues Secret.
Im Ordner setup/ liegen zusätzlich die Einzelschritte und die PDF-Anleitung
(neu erzeugen mit python3 setup/build-anleitung.py, braucht reportlab);
02-get-ids.ps1 prĂĽft eine bestehende Registrierung (Berechtigungen, Secret-Ablauf).
- Entra Admin Center → App-Registrierungen → Neue Registrierung (Name z. B. „Speicher-Dashboard", nur dieser Tenant).
- API-Berechtigungen → Hinzufügen → Microsoft Graph →
Anwendungsberechtigungen →
Reports.Read.All,User.Read.All,Organization.Read.AllundSites.Read.All→ Administratorzustimmung erteilen. (Reports.Read.Allist Pflicht.User.Read.All/Organization.Read.Allliefern Lizenztypen und die Erkennung freigegebener Postfächer;Sites.Read.Alldie Anzeigenamen der SharePoint-Websites, da der Usage-Report in vielen Tenants keine Site-URLs mehr enthält. Fehlen optionale Rechte, läuft die Erfassung trotzdem — das Dashboard zeigt dann einen Hinweis.) - Zertifikate & Geheimnisse → Neuer geheimer Clientschlüssel (Laufzeit z. B. 24 Monate) → Wert sofort kopieren.
- Notieren: Verzeichnis-ID (Tenant), Anwendungs-ID (Client), Secret.
- Wichtig — Klarnamen in Berichten: Im Microsoft 365 Admin Center unter Einstellungen → Organisationseinstellungen → Berichte die Anzeige verborgener Benutzernamen aktivieren (Haken bei Pseudonymisierung entfernen), sonst liefern die Reports nur Hashes. Das Dashboard zeigt in dem Fall eine Warnung.
pwsh ./server.ps1Dashboard: http://localhost:8080 (Port in settings.json änderbar).
Nach setup.ps1 ist alles eingetragen. Wer die App von Hand registriert hat,
öffnet ⚙︎ Einstellungen, trägt die drei Werte ein, klickt Verbindung testen,
speichert und dann Jetzt erfassen.
Der Demo-Modus (Checkbox in den Einstellungen) erzeugt Beispieldaten mit 13 Wochen Historie — praktisch für Vorführungen ohne Tenant-Zugriff.
Am einfachsten als geplanter Task, der beim Systemstart läuft:
Register-ScheduledTask -TaskName 'M365UsageDashboard' `
-Action (New-ScheduledTaskAction -Execute 'pwsh.exe' -Argument '-NoProfile -File C:\Apps\m365-usage-dashboard\server.ps1') `
-Trigger (New-ScheduledTaskTrigger -AtStartup) `
-Settings (New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)) `
-User 'SYSTEM' -RunLevel HighestHinweis: Wird der Task unter einem anderen Konto ausgefĂĽhrt als dem, mit dem das Secret gespeichert wurde, muss das Secret einmal unter diesem Konto neu gespeichert werden (DPAPI ist benutzergebunden).
Fertiges Image fĂĽr amd64 und arm64: ghcr.io/aussiech/m365-usage-dashboard.
docker-compose.yml anpassen (Passwort und SchlĂĽssel)
und starten, auf der Synology ĂĽber Container Manager > Projekt > Erstellen:
docker compose up -d
Im Container lauscht der Server im Netzwerk und verlangt deshalb immer ein Login
(DASHBOARD_PASSWORD). Das Client-Secret wird mit DASHBOARD_SECRET_KEY
per AES-GCM verschlĂĽsselt abgelegt. Tenant-ID, Client-ID und Secret lassen sich
auch per M365_TENANT_ID, M365_CLIENT_ID, M365_CLIENT_SECRET vorgeben.
Daten liegen im Volume /data. FĂĽr den Zugriff ĂĽber das Internet HTTPS
davorschalten (Synology Reverse-Proxy). Details in Kapitel 6 der PDF-Anleitung.
data/usage.db— SQLite; Tabellesnapshotsmit einem Datensatz pro Benutzer und Snapshot-Datum (Primärschlüsselsnapshot_date, upn), Tabellesite_snapshotsanalog pro SharePoint-Site (snapshot_date, site_id), Tabellemeta.- Erfassung überschreibt denselben Tag idempotent (
INSERT OR REPLACE) — mehrfaches „Jetzt erfassen" erzeugt keine Duplikate. settings.json— Konfiguration; das Client-Secret ist unter Windows per DPAPI verschlüsselt und wird von der API nie zurückgegeben.- Die Graph-Reports werden von Microsoft mit ca. 24–48 h Verzögerung aktualisiert; das Snapshot-Datum entspricht dem „Report Refresh Date" des Reports.
| Datei | Zweck |
|---|---|
server.ps1 |
Pode-Server: statisches UI, REST-API, Wochen-Schedule |
src/Graph.psm1 |
Token (Client Credentials) + Reports-Abruf/-Merge, Demodaten |
src/Storage.psm1 |
SQLite-Zugriff (PSSQLite bzw. sqlite3-CLI) |
src/Settings.psm1 |
Einstellungen laden/speichern, Secret-VerschlĂĽsselung |
src/App.psm1 |
Erfassungslauf + Dashboard-Datenaggregation |
public/ |
OnePage-Frontend (Vanilla JS + Chart.js, lokal gebĂĽndelt) |
| Route | Zweck |
|---|---|
GET /api/data |
Status + Historie + aktuelle Benutzerliste |
GET/POST /api/settings |
Konfiguration lesen/schreiben (Secret nur schreibend) |
POST /api/test |
Verbindungstest (Token-Abruf) |
POST /api/collect |
Erfassung sofort ausfĂĽhren |
MIT — frei nutzbar, auch kommerziell. Entwickelt mit Unterstützung von Claude (Anthropic) unter menschlicher Anleitung und Review; Details in der Commit-Historie.
Das Dashboard ist kostenlos und Open Source — von Nutzern für Nutzer. Wer das Projekt unterstützen möchte:
Single-page web app (PowerShell/Pode) that reads mailbox sizes, OneDrive usage and SharePoint site storage for an entire Microsoft 365 tenant via Microsoft Graph, stores the history in a SQLite database and presents it as a dashboard:
- Tiles: total tenant storage (mail + OneDrive + SharePoint) with the three shares broken out, growth since the first snapshot, and user count broken down by license type (incl. shared mailboxes)
- Chart: storage over time — mailboxes, OneDrive and SharePoint as separate lines, tooltip shows the total
- User list: sortable (click a column header), full-text search, filters (quota > 80 %, top 10, shared mailboxes), license column, quota bars with warning thresholds (80 % / 95 %). Shared mailboxes are detected automatically (mailbox present, account disabled, no license) and shown separately
- SharePoint tab: all sites with real display names, storage, type
(team/communication), owner, file count, active files, page views and last
activity; sites inactive for more than 90 days are flagged as archiving
candidates; separate growth chart; search/sort/filter like the user list
(deep link:
#sharepoint) - Settings in the UI: tenant ID, client ID, client secret (stored encrypted via DPAPI on Windows), demo mode with generated sample data, UI language (German/English)
- Automation: built-in weekly schedule (default: Monday 06:00) collects a new snapshot as long as the server is running
Screenshots (demo mode) are shown above.
- Windows server or PC (development also tested on macOS/Linux)
- PowerShell 7 (
winget install Microsoft.PowerShell) - Modules:
Install-Module Pode, PSSQLite -Scope CurrentUser(on macOS/Linux the systemsqlite3CLI is used instead of PSSQLite)
Easiest way: start the server, then run the setup as a Global Administrator of the customer tenant:
pwsh ./start.ps1
pwsh ./setup.ps1setup.ps1 does everything in one go: installs missing modules, creates (or
reuses) the app registration with all four Graph permissions incl. admin consent
and a client secret (24 months), disables report pseudonymization
(-KeepConcealedNames skips this), hands tenant ID, client ID and secret to the
running dashboard via its API (so the server encrypts the secret under its own
account, which also works for a service running as SYSTEM), tests the connection
and runs the first collection, retrying for up to 10 minutes while new
permissions propagate. It is safe to re-run, e.g. when the secret expires.
The setup/ folder additionally contains a step-by-step guide (PDF, German) and
the individual steps as separate scripts:
01-register-app.ps1— creates the app registration, service principal, all Graph permissions incl. admin consent and a client secret, then prints the three values the dashboard needs02-get-ids.ps1— reads tenant ID / client ID of an existing registration, lists secret expiry dates and verifies all permissions;-Fixassigns any missing app roles directly (more reliable than the portal's consent button, which can silently revoke existing grants)03-report-settings.ps1— checks report pseudonymization and disables it on request (-Forcefor non-interactive use)
Manual portal setup works too: register an app (single tenant), add the Graph application permissions below, grant admin consent, create a client secret.
| Permission | Purpose | Required? |
|---|---|---|
Reports.Read.All |
usage reports: mailbox, OneDrive and SharePoint storage | yes |
User.Read.All |
per-user license assignments, shared-mailbox detection | recommended |
Organization.Read.All |
license master data (SKU names) | recommended |
Sites.Read.All |
SharePoint site display names and URLs (the usage report no longer includes site URLs in many tenants) | recommended |
If the optional permissions are missing, storage collection still works — the dashboard shows a hint and leaves the affected columns empty.
Important — real names in reports: Microsoft pseudonymizes usage reports in
many tenants by default (hashes instead of names and site URLs). Disable this
once in the Microsoft 365 Admin Center under
Settings → Org settings → Reports, or run 03-report-settings.ps1. The
dashboard detects concealed reports and shows a warning.
pwsh ./start.ps1
Dashboard: http://localhost:8080 (port configurable in settings.json).
On first start, open ⚙︎ Settings, enter the three values, click
Test connection, save, then Collect now. stop.ps1 stops the server.
The demo mode checkbox generates 13 weeks of sample data — handy for a
first look without any tenant access.
To run permanently on Windows, register a scheduled task at system startup
(see the German section above for a ready-made Register-ScheduledTask call).
Prebuilt image for amd64 and arm64: ghcr.io/aussiech/m365-usage-dashboard.
Adjust docker-compose.yml (password and key)
and run docker compose up -d (Synology: Container Manager > Project > Create).
In the container the server listens on the network and therefore always requires
a login (DASHBOARD_PASSWORD). The client secret is stored AES-GCM encrypted with
DASHBOARD_SECRET_KEY. Tenant ID, client ID and secret can also be set via
M365_TENANT_ID, M365_CLIENT_ID, M365_CLIENT_SECRET. Data lives in the /data
volume. Put HTTPS in front (e.g. Synology reverse proxy) before exposing it to the internet.
data/usage.db— SQLite; one row per user and snapshot date (snapshots), one row per SharePoint site and snapshot date (site_snapshots), plus ametatable- Collections are idempotent (
INSERT OR REPLACE) and serialized — collecting twice on the same day never creates duplicates settings.json— configuration; the client secret is DPAPI-encrypted on Windows and is never returned by the API- Graph usage reports lag by roughly 24–48 h; the snapshot date is the report's "Report Refresh Date"
| File | Purpose |
|---|---|
server.ps1 |
Pode server: static UI, REST API, weekly schedule |
src/Graph.psm1 |
token (client credentials), report fetching/merging, site names, demo data |
src/Storage.psm1 |
SQLite access (PSSQLite or sqlite3 CLI) |
src/Settings.psm1 |
settings load/save, secret encryption |
src/App.psm1 |
collection run + dashboard data aggregation |
public/ |
single-page frontend (vanilla JS + bundled Chart.js) |
| Route | Purpose |
|---|---|
GET /api/data |
status + history + current user and site lists |
GET/POST /api/settings |
read/write configuration (secret is write-only) |
POST /api/test |
connection test (token acquisition) |
POST /api/collect |
run a collection immediately |
MIT — free to use, including commercially. Developed with the assistance of Claude (Anthropic) under human direction and review; see the commit history for details.
The dashboard is free and open source — built by users, for users. If it saves you a few francs in backup costs:


