PowerShell-Script zur monatlichen Verrechnung der eCall SMS/FAX-Logfiles auf Kostenstellen und Innenaufträge.
eCall stellt die Logfiles des Vormonats als ZIP-Anhang per E-Mail zu. Das Script holt diese Mails per Microsoft Graph aus einem dedizierten Postfach ab, liest die In-/Out-Logfiles beider eCall-Accounts ein, verrechnet die Punkte anhand einer Stammdaten-Tabelle und erzeugt daraus ein Verrechnungs-CSV. Zum Abschluss wird ein HTML-Report per Mail versendet.
Wichtig — die Logfiles kommen nicht automatisch. Der monatliche Versand der Logfiles muss beim eCall-Support aktiv beantragt werden. Erst wenn eCall die Einlieferung für die betroffenen Accounts eingerichtet hat, treffen die ZIP-Anhänge im konfigurierten Postfach ein. Ohne diese Freischaltung findet das Script keine Logfiles vor und meldet nur
WARN-Zeilen.
Warum Graph statt EWS? Exchange Web Services (EWS) wird für Exchange Online am 1. Oktober 2026 abgeschaltet. Diese Version verwendet durchgängig Microsoft Graph mit App-Only-Authentifizierung (Zertifikat) — es werden keine Passwörter mehr im Script oder auf der Platte benötigt.
- Abholen: Alle Mails im Posteingang des Log-Postfachs werden verarbeitet; ZIP-Anhänge werden in einen Import-Ordner heruntergeladen und entpackt. Verarbeitete Mails wandern in „Gelöschte Elemente".
- Einlesen: Die entpackten Logfiles werden anhand ihres Dateinamens den vier Datensätzen zugeordnet (Account 1/2, jeweils In/Out). Mehrere Dateien pro Datensatz werden akkumuliert.
- Verrechnen: Jede Punktesumme wird über den
Buchungstextder Stammdaten einer Kostenstelle / einem Innenauftrag zugeordnet:- Account 1: vollständige Absender- bzw. Antwortadresse
- Account 2: Maildomain des Absenders bzw. der Antwortadresse
- Alle Accounts:
ExterneID(API-Meldungen); IDs mit PräfixeCallURLlaufen gesammelt auf die PositioneCallURL - Nicht zuordenbare Punkte landen auf dem konfigurierbaren Default-Eintrag.
- Kontrolle: Die Gesamtpunkte laut Logfiles werden gegen die verrechneten Punkte geprüft; eine Restdifferenz wird auf den Default-Eintrag gebucht und geloggt.
- Output: Verrechnungs-CSV im Reporting-Ordner + HTML-Report-Mail mit Logfile und CSV als Anhang.
-
Windows Server mit PowerShell 5.1 oder 7.x
-
Microsoft Graph PowerShell SDK (nur die drei benötigten Module):
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Mail, Microsoft.Graph.Users.Actions -Scope AllUsers
-
Exchange-Online-Postfach, in dem die eCall-Logfiles eintreffen (dediziertes Shared Mailbox empfohlen)
-
Schreibzugriff des ausführenden Kontos auf die konfigurierten Netzwerkfreigaben
- Entra Admin Center → App registrations → New registration
- Name: z. B.
eCall-Log-Analyzer - Supported account types: Accounts in this organizational directory only
- Name: z. B.
- Unter API permissions → Add a permission → Microsoft Graph → Application permissions:
Mail.ReadWrite(Logfiles lesen, Mails in den Papierkorb verschieben)Mail.Send(Report-Mail versenden)
- Grant admin consent ausführen.
- Tenant-ID und Application (client) ID von der Übersichtsseite notieren → im Script bei
$TenantIdund$GraphClientIdeintragen.
Auf dem Server, der das Script ausführt (im Kontext des Task-Kontos bzw. in LocalMachine, wenn ein Dienstkonto verwendet wird):
$cert = New-SelfSignedCertificate -Subject 'CN=eCall-Log-Analyzer' `
-CertStoreLocation 'Cert:\LocalMachine\My' `
-KeyExportPolicy NonExportable -KeySpec Signature `
-KeyLength 2048 -NotAfter (Get-Date).AddYears(2)
# Public Key exportieren (wird in Entra hochgeladen)
Export-Certificate -Cert $cert -FilePath .\eCall-Log-Analyzer.cer
$cert.Thumbprint # -> im Script bei $GraphCertThumbprint eintragenDann in der App-Registrierung unter Certificates & secrets → Certificates → Upload certificate die .cer-Datei hochladen.
Das ausführende Konto (Scheduled-Task-Benutzer) braucht Leserecht auf den privaten Schlüssel: Zertifikat in
certlm.msc→ Rechtsklick → All Tasks → Manage Private Keys.Vor Ablauf des Zertifikats (hier: 2 Jahre) rechtzeitig ein neues erstellen, hochladen und den Thumbprint im Script aktualisieren.
Mail.ReadWrite/Mail.Send als Application Permission gelten sonst tenant-weit. Mit einer Application Access Policy wird die App auf das Log- und das Absenderpostfach begrenzt (Exchange Online PowerShell, einmalig):
# Mail-aktivierte Sicherheitsgruppe mit den beiden Postfächern anlegen,
# z. B. "eCall-Log-Analyzer-Mailboxes", dann:
New-ApplicationAccessPolicy -AppId '<Application-ID>' `
-PolicyScopeGroupId 'eCall-Log-Analyzer-Mailboxes@example.com' `
-AccessRight RestrictAccess `
-Description 'eCall Log Analyzer: nur Log- und Reporting-Postfach'
# Wirksamkeit pruefen:
Test-ApplicationAccessPolicy -AppId '<Application-ID>' -Identity 'ecall-logs@example.com' # Granted
Test-ApplicationAccessPolicy -AppId '<Application-ID>' -Identity 'irgendwer@example.com' # DeniedAlle Einstellungen stehen gebündelt im Block #region Konfiguration von billing.ps1:
| Variable | Bedeutung |
|---|---|
$TenantId, $GraphClientId, $GraphCertThumbprint |
Werte aus Schritt 1 und 2 |
$LogMailbox |
Postfach, in dem die eCall-Logfiles eintreffen |
$MailFrom, $MailTo |
Absender und Empfänger der Report-Mail |
$SourceFolder |
Ordner mit Stammdaten.csv und Leistungsarten.csv |
$ShareFolder, $ImportRoot, $ReportingFolder, $LockFolder |
Ablage für Logs, entpackte Logfiles, Verrechnungs-CSV und Monats-Lockfile |
$DefaultBuchungstext |
Stammdaten-Eintrag, auf den Unzuordenbares gebucht wird (muss existieren, sonst Abbruch) |
$Zuweisungsgruppe, $ServiceName |
Texte für Report-Mail und Dateinamen |
$LogFileMap |
Dateinamensmuster → Datensatz; weitere eCall-Accounts = eine Zeile mehr |
Beide Dateien liegen im $SourceFolder auf dem Server (nicht im Repo). Als Vorlage mit dem exakten Format liegen sie unter examples/ — die realen Dateien mit echten Kostenstellen und Mailadressen gehören nicht ins Repository (per .gitignore geschützt).
Stammdaten.csv (Semikolon-getrennt, mit Kopfzeile) — die Zuordnungstabelle:
Innenauftrag;Kostenstelle;Kundenauftrag;Auftragsposition;Buchungstext;Menge
IA100001;KST5000;KA200001;10;alerting@example.com;0
IA100002;KST5100;KA200002;10;subdomain.example.com;0
IA100003;KST5200;KA200003;10;MeineExterneID;0
IA100009;KST5900;KA200009;10;eCallURL;0
IA100010;KST5999;KA200010;10;default.example.com;0Der Buchungstext ist der Zuordnungsschlüssel: je nach Account eine vollständige Mailadresse, eine Domain oder eine ExterneID. Die Einträge eCallURL und der Default-Eintrag müssen vorhanden sein.
Leistungsarten.csv (Semikolon-getrennt, mit Kopfzeile) — wird 1:1 in den Kopf des Verrechnungsfiles übernommen:
Leistungsart;Bezeichnung
L001;SMS Versand
L002;SMS Empfang
L003;FAX Versand
L004;FAX EmpfangDas Script ist für einen monatlichen Lauf (Anfang Monat, nach Zustellung der eCall-Logfiles) als geplante Aufgabe gedacht:
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "D:\Temp\SCRIPTS\Log_Analyzer\billing.ps1"'
$trigger = New-ScheduledTaskTrigger -At 06:00 -Daily
Register-ScheduledTask -TaskName 'eCall-Verrechnung' -Action $action -Trigger $trigger `
-User 'DOMAIN\svc-ecall' -Password (Read-Host 'Passwort Dienstkonto')Ein eingebauter Monats-Lock (Check_<JJJJMM>.txt im $LockFolder) verhindert Doppelverrechnung: Läuft das Script ein zweites Mal im selben Monat, bricht es mit Fehler ab (inkl. Eintrag im Windows-Eventlog). Der Trigger darf also ruhig täglich feuern — verrechnet wird trotzdem nur einmal pro Monat, sobald Logfiles im Postfach liegen. Bewusster zweiter Lauf:
.\billing.ps1 -Force| Artefakt | Ablage |
|---|---|
Verrechnungs-CSV <ServiceName>_SMS_FAX_<JJJJMM>.csv |
$ReportingFolder |
Tageslogfile logfile_<JJJJMMTT>.txt |
$ShareFolder\logs |
Entpackte Roh-Logfiles (ein Unterordner pro Mail, inkl. metadata.json) |
$ImportRoot\<Zeitstempel> |
| HTML-Report-Mail (mit CSV und Logfile als Anhang) | an $MailTo |
| Symptom | Ursache / Lösung |
|---|---|
Access is denied / ErrorAccessDenied bei Graph-Aufrufen |
Admin Consent fehlt, oder die Application Access Policy schließt das Postfach aus (Test-ApplicationAccessPolicy prüfen) |
Certificate with thumbprint ... not found |
Zertifikat liegt nicht im Store des ausführenden Kontos, oder das Task-Konto hat kein Leserecht auf den privaten Schlüssel |
| Abbruch „bereits ausgeführt" | Gewollt (Monats-Lock). Bewusster zweiter Lauf: -Force |
WARN Kein Stammdaten-Eintrag fuer '...' im Log |
Neuer Absender/Domain/ExterneID ohne Stammdaten-Zeile — Punkte liefen auf den Default-Eintrag. Zeile in Stammdaten.csv ergänzen |
WARN Kein Logfile fuer Account..._... |
eCall-Mail noch nicht eingetroffen oder Dateinamensmuster in $LogFileMap passt nicht |
| Hohe Menge auf dem Default-Eintrag | Erwartet für alles Unzuordenbare — im Tageslogfile stehen die betroffenen Buchungstexte |
Die Logfiles kommen als ZIP-komprimierte, semikolongetrennte CSV-Dateien ohne Kopfzeile; die Spalten werden rein über ihre Position zugeordnet. Das Script erwartet exakt diese Reihenfolge:
| # | Out-Logfile | In-Logfile |
|---|---|---|
| 1 | Referenz | Referenz |
| 2 | Startdatum | Startdatum |
| 3 | Meldung (optional) | Gesendete Meldung (optional) |
| 4 | Resultatcode | Empfangene Meldung (optional) |
| 5 | Absender | Resultatcode |
| 6 | Empfängernummer | Sende-Adresse |
| 7 | ExterneID | eCall-Adresse |
| 8 | Punkte | Antwort-Adresse |
| 9 | Empfängername | Antwort-Info |
| 10 | ExterneID | |
| 11 | Punkte |
Fett markiert sind die für die Verrechnung ausgewerteten Felder.
Achtung — unterdrückbare Meldungsfelder. Die Meldungsspalten (Out-Feld 3, In-Felder 3 + 4) enthalten den SMS-/FAX-Klartext bzw. die Antworten und können bei eCall „auf Wunsch unterdrückt" werden. Das ist aus Datenschutzsicht sinnvoll (sonst liegt personenbezogener Nachrichteninhalt im Postfach und auf den File-Shares). Da die Zuordnung aber positionsbasiert ist, gilt: Klärt mit dem eCall-Support ab, dass eine Unterdrückung die Spalte leer lässt und nicht entfernt — nur dann bleibt die Feldreihenfolge stabil. Wird eine Spalte komplett entfernt, verschieben sich alle folgenden Felder und die Verrechnung liest falsche Werte. In diesem Fall müssen
$OutHeader/$InHeaderim Script angepasst werden.
- Zeilen, die sowohl eine Absender-/Antwortadresse als auch eine
ExterneIDenthalten, werden in beiden Durchgängen gebucht. Die Schlusskontrolle gleicht die Gesamtsumme über den Default-Eintrag wieder aus, die einzelnen Kostenstellen wären in diesem Fall aber überzeichnet. Bei eCall ist pro Meldungstyp üblicherweise nur eines der Felder gefüllt. - Punktedifferenzen zwischen Logfile-Summe und verrechneter Summe werden immer auf den Default-Eintrag gebucht und im Log ausgewiesen — das Verrechnungsfile geht in der Gesamtsumme also immer auf.
- Verrechnet wird ausschliesslich über das Punkte-Feld (verbrauchte Punktzahl je Auftrag), unabhängig vom
Resultatcode. Das entspricht der eCall-Logik, bei der nur tatsächlich abgerechnete Aufträge Punkte tragen; fehlgeschlagene oder wartende Meldungen fliessen mit ihrem jeweiligen Punktewert (i. d. R. 0) ein. - Wurden in einem Monat keine Meldungen versendet bzw. empfangen, liefert eCall statt Datenzeilen einen Hinweistext im Logfile. Eine solche Zeile hat keine gültige Absender-/Antwortadresse und keinen numerischen Punktewert und wird von der Verrechnung automatisch ignoriert.