Wenn Aufgaben der Aufgabenplanung nicht laufen oder mit 0x1 enden — Ursachen eingrenzen und zuverlässigen Betrieb entwerfen
· Go Komura · Aufgabenplanung, Windows, PowerShell, Betriebsautomatisierung, Stapelverarbeitung, Betrieb, Troubleshooting, Technische Beratung
„Ich möchte ein mit PowerShell gebautes Auswertungsskript jeden Morgen um 6 Uhr laufen lassen.“ „Von Hand ausgeführt funktioniert es, aber sobald ich es in die Aufgabenplanung lege, läuft es nicht.“ Wenn wir zu Betriebsautomatisierung beraten, landet das Gespräch fast immer irgendwann bei genau diesem Thema.
Auch in diesem Blog haben wir bereits fortlaufend über Automatisierung geschrieben: die Automatisierung der Protokollpflege, das Testen von Skripten mit Pester, die Ausführung von PowerShell aus C# und die Automatisierung von Geschäftsprozessen mit Power Automate. Alle diese Artikel setzen letztlich voraus, dass die betreffende Aufgabe über die Aufgabenplanung regelmäßig ausgeführt wird. Doch die Aufgabenplanung selbst erweist sich als überraschend eigenwilliges Konstrukt: „Von Hand funktioniert es, aber die geplante Ausführung schlägt fehl“, „niemand hat bemerkt, dass es irgendwann aufgehört hat zu laufen“ — solche Zwischenfälle reißen nicht ab.
Dieser Artikel arbeitet die Teile der Aufgabenplanung durch, die unmittelbar zu Betriebsstörungen führen — Ausführungskonto und Anmeldetyp, die Eingrenzung eines „läuft nicht“, die typischen Ursachen für den Rückgabewert 0x1, wie man Protokolle hinterlässt, und die Steuerung von Mehrfachstarts — in der Reihenfolge, in der man in der Praxis üblicherweise darüber stolpert.
Voraussetzungen dieses Artikels
| Punkt | Inhalt |
|---|---|
| Ziel-Betriebssystem | Dieser Artikel setzt die Aufgabenplanung von Windows 10/11 und Windows Server 2016 oder neuer (die Task-Scheduler-2.0-Reihe) voraus. Sind in Ihrer Umgebung noch Aufgaben aus der Zeit des alten at-Befehls vorhanden, beginnen Sie zuerst mit deren Bestandsaufnahme |
| Angesprochene Leserschaft | IT- und Entwicklungsverantwortliche, die PowerShell oder Batch-Dateien schreiben können, aber ins Stocken geraten, sobald es darum geht, das Ganze in die geplante Ausführung zu bringen |
| Bedienung | Behandelt werden sowohl die GUI (taskschd.msc) als auch das PowerShell-Modul ScheduledTasks. Es sind keine Bildschirmaufnahmen enthalten. Stattdessen sind Tab-, Feld- und Schaltflächennamen genau wie in der tatsächlichen Beschriftung wiedergegeben — öffnen Sie die Aufgabenplanung parallel, während Sie lesen |
| Voraussetzung für Domänenumgebungen | gMSA (Abschnitt 3.3) betrifft ausschließlich Domänenumgebungen. In einer Arbeitsgruppenumgebung können Sie diesen Teil überspringen |
1. Das Wichtigste zuerst
- Der Großteil der Probleme mit der Aufgabenplanung entsteht nicht durch das Skript selbst, sondern durch eine falsche Vorstellung davon, „als wer und in welcher Art von Sitzung“ ausgeführt wird. Sobald Sie „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“ wählen, gehen Sie beim Entwurf davon aus, dass die Aufgabe in einer Welt läuft, die sich sowohl von der interaktiven Sitzung als auch von der Umgebung bei der Anmeldung unterscheidet.1
- Die Untersuchung eines „läuft nicht“ beginnt beim Verlauf-Tab (History) und dem Ereignisprotokoll. Der Aufgabenverlauf ist jedoch standardmäßig deaktiviert, daher muss „Alle Aufgabenverläufe aktivieren“ unbedingt eingeschaltet werden, bevor die Aufgabe in den produktiven Betrieb geht.2
- Das
0x1, das bei „Letztes Ausführungsergebnis“ erscheint, ist kein Fehler der Aufgabenplanung, sondern bedeutet, dass das gestartete Programm selbst den Beendigungscode 1 zurückgegeben hat. Die Ursache liegt beim Skript — entwerfen Sie deshalb zuerst die Beendigungscodes und einen Mechanismus, der selbst Protokolle hinterlässt.3 - PowerShell-Skripte ruft man grundlegend in der Form
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "vollständiger Pfad"auf, wobei Pfade im Skript relativ zu$PSScriptRootaufgebaut werden. Auch die klassische Falle, im Feld „Starten in (optional)“ Anführungszeichen zu setzen, tappt man hier gerne hinein — das darf nicht sein. - Um den Zwischenfall zu vermeiden, dass eine Aufgabe nach einer Kennwortänderung stillschweigend stirbt, braucht es ein durchdachtes Ausführungskonto (Bestandsaufnahme der Dienstkonten, in Domänenumgebungen die Prüfung eines gMSA).4
- Die Steuerung von Mehrfachstarts (Standard: „Keine neue Instanz starten“), das Zeitlimit für die Ausführung (Standard 3 Tage) und die Energiebedingung (Standard: nur bei Netzbetrieb) sind klassische Einstellungen, die unbemerkt auf ihrem Standardwert im Betrieb belassen werden. Legen Sie diese bei der Registrierung stets ausdrücklich fest.5
2. Der Aufbau der Aufgabenplanung ── Trigger, Aktion, Bedingungen, Einstellungen
Eine Aufgabe der Aufgabenplanung besteht im Wesentlichen aus vier Elementen.
| Element | Inhalt | Häufige Fehlerquelle |
|---|---|---|
| Trigger | Wann die Aufgabe startet (Uhrzeit, bei Anmeldung, bei einem Ereignis usw.) | Umgang mit einem verpassten Zeit-Trigger (StartWhenAvailable, siehe unten) |
| Aktion | Was ausgeführt wird (Programm, Argumente, Startordner) | Fehler beim Zitieren von Argumenten, falsch angegebener Startordner |
| Bedingungen | Ob die Ausführung überhaupt erlaubt ist (Energie, Netzwerk, Leerlauf) | Standardmäßig ist „nur bei Netzbetrieb“ aktiviert |
| Einstellungen | Verhalten während der Ausführung (Mehrfachstarts, Zeitlimit, Wiederholungsversuche) | Der Betrieb wird gestartet, ohne die Standardwerte zu prüfen |
Eine über die GUI (taskschd.msc) erstellte Aufgabe lässt sich als XML exportieren. Möchten Sie Aufgabendefinitionen mit Git verwalten oder dieselbe Aufgabe auf mehrere Rechner verteilen, empfiehlt es sich, sie zu skripten — entweder über XML-Export plus schtasks /Create /XML, oder mit dem PowerShell-Modul ScheduledTasks (New-ScheduledTaskAction / New-ScheduledTaskTrigger / New-ScheduledTaskSettingsSet / Register-ScheduledTask).5
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
-WorkingDirectory 'C:\Jobs'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00'
# Die Standard-Energiebedingung lautet "nur bei Netzbetrieb starten, bei Wechsel auf Akku stoppen".
# Soll der Job auch auf einem Notebook oder Außendienstgerät laufen, hier ausdrücklich erlauben
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
# Anmeldeinformationen über Get-Credential entgegennehmen, damit das Kennwort nicht auf dem Bildschirm erscheint
$cred = Get-Credential -UserName 'DOMAIN\svc-batch' -Message 'Anmeldeinformationen des Ausführungskontos'
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
-Action $action -Trigger $trigger -Settings $settings `
-User $cred.UserName -Password $cred.GetNetworkCredential().Password
Liegt die Aufgabendefinition als Code vor, lässt sich das auf dem Testrechner Geprüfte unverändert in die Produktion übernehmen, und man vermeidet den Zustand, „mit welchen Einstellungen das eigentlich läuft, weiß niemand“.
Für die Verteilung auf viele Rechner hat sich auch der Weg bewährt, eine in der GUI erstellte Aufgabe als XML zu exportieren und mit schtasks zu verteilen.
rem Die auf dem Testrechner erstellte Aufgabe exportieren
schtasks /Query /TN "KS-Cleanup-Logs" /XML > KS-Cleanup-Logs.xml
rem Auf jedem Endgerät importieren (Ausführungskonto und Kennwort bei der Registrierung angeben)
schtasks /Create /TN "KS-Cleanup-Logs" /XML KS-Cleanup-Logs.xml /RU DOMAIN\svc-batch /RP *
Da die XML sämtliche Trigger, Bedingungen und Einstellungen enthält, lassen sich Aufgabendefinitionen im Repository sowohl im Review als auch als Diff verwalten. Ein manuelles Anlegen derselben Aufgabe per GUI auf zehn Rechnern erzeugt dagegen unweigerlich einen einzelnen „Ausreißer“, dessen Einstellungen von den übrigen abweichen. Bevor die Anzahl der Geräte zweistellig wird, sollte die Definition sicherheitshalber als Code vorliegen.
3. Ausführungskonto und Anmeldetyp ── die häufigste Fehlerquelle
Die in den Aufgabeneigenschaften wählbaren Optionen „Nur ausführen, falls Benutzer angemeldet ist“ und „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“ sind intern eine Wahl des Anmeldetyps (LogonType). Ist das Verständnis hier lückenhaft, lässt sich der Großteil der Fälle „funktioniert manuell, aber nicht bei geplanter Ausführung“ nicht erklären, und man sucht auf gut Glück weiter.1
3.1 Die Unterschiede der drei Modi
| Auswahl | Interner Mechanismus | Merkmale/Einschränkungen |
|---|---|---|
| Nur ausführen, falls Benutzer angemeldet ist | Interaktives Token (InteractiveToken) | Das Fenster ist während der Anmeldung auf dem Bildschirm sichtbar. Bei Abmeldung startet die Aufgabe erst gar nicht |
| Ausführen, unabhängig davon, ob der Benutzer angemeldet ist | Kennwort gespeichert (Password) | Das Kennwort wird bei der Registrierung gespeichert. Läuft nicht interaktiv, kein sichtbares Fenster. Eine Kennwortänderung führt zum Fehlschlag |
| Wie oben + „Kennwort nicht speichern“ | S4U | Es wird kein Kennwort gespeichert, dafür ist kein Zugriff auf Ressourcen im Netzwerk und auf verschlüsselte Dateien (EFS) möglich1 |
Typische Zwischenfälle in der Praxis sehen so aus.
- Ein Skript, das auf einen Freigabeordner zugreift, wurde mit „Kennwort nicht speichern“ (S4U) registriert. Im lokalen Test funktionierte es, in der Produktion schlägt jedoch ausschließlich der Zugriff auf den Freigabeordner fehl. → Weil S4U über keine Netzwerk-Anmeldeinformationen verfügt.
- Eine mit „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“ registrierte Aufgabe lief mehrere Monate lang, bis das Kennwort der Domäne aufgrund des Ablaufdatums geändert wurde. Danach blieb die Aufgabe dauerhaft mit einem Anmeldefehler (
0x8007052E) stehen, ohne dass es jemand bemerkte. - Eine Aufgabe, die eine GUI-Anwendung startet, wurde mit „ungeachtet dessen ausführen“ registriert. Die Anwendung läuft zwar, ihr Fenster ist aber nirgends zu sehen, was fälschlich als „läuft nicht“ gedeutet wurde. → Weil sie in einer nicht interaktiven Sitzung läuft. Verarbeitungen, die einen interaktiven Bildschirm benötigen, lassen sich in dieser Konfiguration grundsätzlich nicht ausführen.
Zudem benötigt das unter Password oder S4U ausführende Konto das Recht „Anmelden als Batchauftrag“ (SeBatchLogonRight). Administratoren besitzen es standardmäßig, doch wenn Sie einen dedizierten Standardbenutzer zum Dienstkonto machen, prüfen Sie zusätzlich die lokale Sicherheitsrichtlinie.6
3.2 Unter welchem Konto ausführen
- SYSTEM: Erfordert keine Kennwortverwaltung und ist mächtig, doch die Rechte sind zu weitreichend. Für rein lokale Wartungsverarbeitung ist es praktisch, aber Jobs, die auf Geschäftsdaten zugreifen, sollten grundsätzlich nicht als SYSTEM laufen. Wie man beurteilt, ob Administratorrechte tatsächlich nötig sind, ordnen wir im separaten Artikel „Wann eine Windows-Anwendung tatsächlich Administratorrechte benötigt“ ein.
- Dediziertes Dienstkonto (ein Standardbenutzer): Ermöglicht minimale Rechte, erfordert im Gegenzug aber, dass die Aufgabe bei jeder Kennwortänderung aktualisiert wird. Verwalten Sie Kennwortablauf und Aufgabenbestand gemeinsam.
- gMSA (Group Managed Service Account): In einer Domänenumgebung die erste Wahl. Da das Kennwort vom Domänencontroller automatisch verwaltet wird, entfällt das Problem „die Aufgabe stirbt bei einer Kennwortänderung“ grundsätzlich. Die Aufgabenplanung unterstützt die Ausführung mit gMSA.4
Das Kontrollkästchen „Mit höchsten Privilegien ausführen“ bedeutet übrigens, dass von den durch UAC aufgeteilten Tokens das administrative (erhöhte) Token verwendet wird. Setzen Sie es nicht bei Jobs, die keine Administratorrechte benötigen.
3.3 Minimaler Ablauf zur Registrierung einer Aufgabe mit gMSA
Da wir gMSA als „erste Wahl“ bezeichnet haben, zeigen wir auch konkret, wie es sich registrieren lässt. Die Aufgabe der Aufgabenplanung ist offiziell als eine der Konfigurationen genannt, die gMSA unterstützen.4
Die Voraussetzungen sind die folgenden.4
- Die Funktionsebene von Domäne und Gesamtstruktur muss Windows Server 2012 oder neuer sein
- In der Domäne muss bereits ein KDS-Stammschlüssel erstellt worden sein (die Erstellung lässt sich im Operational-Protokoll von
KdsSvcanhand der Ereignis-ID 4004 nachvollziehen) - Zum Erstellen und Verwalten des gMSA ist eine Mitgliedschaft in Domain Admins oder Enterprise Admins erforderlich
- Der gMSA-Name muss nicht domänenweit, sondern gesamtstrukturweit eindeutig sein. Existiert derselbe Name bereits in einer anderen Domäne, schlägt die Erstellung fehl
Der Ablauf umfasst 3 Schritte. Schritte 1–2 führt ein Domänenadministrator aus, Schritt 3 auf jedem Endgerät, das die Aufgabe ausführen soll.
# --- 1. Auf Domänenseite: gMSA erstellen. Über eine Sicherheitsgruppe festlegen, welche Hosts das Kennwort abrufen dürfen ---
# <SecurityGroup> ist eine Gruppe, die die Computerkonten der Server enthält, auf denen die Aufgabe laufen soll
New-ADServiceAccount -Name 'svc-batch' -DNSHostName 'svc-batch.contoso.local' `
-PrincipalsAllowedToRetrieveManagedPassword 'GG-BatchHosts'
# --- 2. Auf jedem Endgerät, das die Aufgabe ausführen soll: gMSA installieren und prüfen, ob das Kennwort abgerufen werden kann ---
Install-ADServiceAccount -Identity 'svc-batch'
Test-ADServiceAccount -Identity 'svc-batch' # Bei Rückgabe von True ist es einsatzbereit
Der dritte Schritt ist die Registrierung der Aufgabe. Hier gibt es 2 wichtige Punkte.
# --- 3. Auf dem Endgerät, das die Aufgabe ausführen soll: die Aufgabe mit dem gMSA als Prinzipal registrieren ---
$action = New-ScheduledTaskAction -Execute 'pwsh.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"' `
-WorkingDirectory 'C:\Jobs'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00'
# Punkt 1: An den Kontonamen wird am Ende ein $ angehängt (Namensformat eines gMSA)
# Punkt 2: LogonType ist Password. -Password wird jedoch NICHT übergeben
# (das Kennwort des gMSA wird vom Domänencontroller verwaltet und vom Host selbst abgerufen)
$principal = New-ScheduledTaskPrincipal -UserId 'CONTOSO\svc-batch$' `
-LogonType Password -RunLevel Limited
Register-ScheduledTask -TaskName 'KS-Cleanup-Logs' `
-Action $action -Trigger $trigger -Principal $principal
Für -LogonType von New-ScheduledTaskPrincipal lassen sich die Werte None / Password / S4U / Interactive / Group / ServiceAccount / InteractiveOrPassword angeben.7 Wie in der Tabelle aus Abschnitt 3.1 zu sehen, hat S4U keinen Zugriff auf Netzwerkressourcen — wählen Sie es bei Jobs, die einen Freigabeordner ansprechen, nicht leichtfertig.
Zwei Ergänzungen. Erstens braucht auch dieser gMSA das Recht „Anmelden als Batchauftrag“ (dieselbe Sache wie am Ende von Abschnitt 3.1 — ein gMSA ist davon nicht ausgenommen).6 Zweitens: schtasks.exe geht davon aus, dass das Kennwort des Ausführungskontos über /RP übergeben wird, und die Registrierung mit gMSA ist dort offiziell nicht dokumentiert. Setzen Sie auf gMSA, ist es sicherer, die Aufgabenregistrierung über Register-ScheduledTask in PowerShell vorzunehmen. Kombiniert man das mit dem in Kapitel 2 beschriebenen Ansatz „XML exportieren und mit schtasks verteilen“, ergibt sich eine Konstruktion, bei der nur der Prinzipal-Teil per PowerShell überschrieben wird.
4. Vorgehen, um ein „läuft nicht“ einzugrenzen
4.1 Erst den Verlauf aktivieren, dann verdächtigen
Die im rechten Bereich der Aufgabenplanung verfügbare Option „Alle Aufgabenverläufe aktivieren“ ist standardmäßig deaktiviert. Bleibt der Verlauf deaktiviert, wird nicht einmal die Tatsache eines Fehlschlags protokolliert. Aktivieren Sie sie unbedingt, bevor die Aufgabe in den Betrieb geht. Das Vorgehen ist wie folgt.
- Starten Sie die Aufgabenplanung (
taskschd.msc, oder im Startmenü nach „Aufgabenplanung“ suchen). Starten Sie sie mit Administratorrechten. - Wählen Sie im linken Bereich in der Baumstruktur den obersten Eintrag „Aufgabenplanung (Lokal)“. Bei ausgewählter einzelner Aufgabe erscheint dieser Menüpunkt nicht.
- Klicken Sie in der Liste „Aktionen“ im rechten Bereich auf „Alle Aufgabenverläufe aktivieren“. Ist es bereits aktiviert, zeigt der Eintrag stattdessen „Alle Aufgabenverläufe deaktivieren“.
Der Verlauf entspricht im Kern dem Ereignisprotokoll unter Anwendungs- und Dienstprotokolle > Microsoft > Windows > TaskScheduler > Operational in der Ereignisanzeige.2 Der „Verlauf“-Tab einer einzelnen Aufgabe zeigt lediglich dieses Protokoll gefiltert auf die betreffende Aufgabe — wollen Sie über mehrere Aufgaben hinweg zeitlich verfolgen, öffnen Sie die Ereignisanzeige direkt.
Der grundlegende Ablauf zur Eingrenzung funktioniert bereits genau nach dem Muster von Microsofts Troubleshooting-Leitfaden.2
- Das Skript isoliert testen ── Bevor Sie es in die Aufgabe legen, prüfen Sie unter denselben Bedingungen wie das Ausführungskonto (nach Möglichkeit mit
runasoder auf einem Testrechner), ob das Skript selbst vollständig durchläuft. - Statusspalte und Verlauf-Tab betrachten ── Unterscheiden Sie, ob überhaupt ausgelöst wurde oder ob es gestartet, aber fehlgeschlagen ist. Wurde nicht ausgelöst, verdächtigen Sie die Trigger-Einstellungen und die Bedingungen (Energie, Netzwerk) und prüfen Sie per manueller Ausführung (Rechtsklick → Ausführen), ob die Aktion selbst funktioniert.
- Vorübergehend auf „Nur ausführen, falls Benutzer angemeldet ist“ umstellen ── Funktioniert es damit, lässt sich die Ursache auf die nicht interaktive Sitzung oder die Anmeldeinformationen (vorheriges Kapitel) eingrenzen.
4.2 Was im Verlauf-Tab erscheint, und was dann zu vermuten ist ── Übersichtstabelle der Ereignis-IDs
Jede Zeile im Verlauf-Tab (und im Operational-Protokoll) trägt eine Ereignis-ID. Anhand dieser ID lässt sich auf einen Blick erkennen, „wie weit es gekommen ist“. Ein normaler Aufgabenlauf reiht sich im Wesentlichen in der Reihenfolge „Start (100) → Prozessstart (129) → Aktion gestartet (200) → Aktion abgeschlossen (201) → Abschluss (102)“.
| Ereignis-ID | Bedeutung der Meldung | Was bei ihrem Auftreten zu vermuten ist |
|---|---|---|
| 106 | Ein Benutzer hat die Aufgabe registriert8 | Eintrag der Registrierung. Ausgangspunkt, um „wann hat wer etwas geändert“ zu verfolgen |
| 140 / 141 | Ein Benutzer hat die Aufgabe aktualisiert / gelöscht8 | Hier sucht man den „Schuldigen“, wenn „bis gestern lief es noch“ |
| 113 | Die Aufgabe konnte registriert werden, aber ein Teil der Trigger startet die Aufgabe nicht8 | Mangel in der Trigger-Definition. Bereits bei der Registrierung wird gewarnt |
| 116 | Die Konfiguration der Aufgabe konnte gespeichert werden, aber die für die Ausführung nötigen Anmeldeinformationen konnten nicht gespeichert werden8 | Angabe von Ausführungskonto und Kennwort. Tritt dies auf, läuft die Aufgabe naturgemäß nicht |
| 100 | Die Aufgabe wurde gestartet9 | Fehlt dieser Eintrag, wurde entweder gar nicht ausgelöst, oder der Start ist mit 101 fehlgeschlagen |
| 101 | Die Aufgabe konnte nicht gestartet werden. Mit Fehlerwert9 | Der Trigger hat ausgelöst, aber der Start ist fehlgeschlagen. Verdächtigen Sie die Anmeldeinformationen des Ausführungskontos (etwa 0x8007052E) oder fehlende Rechte. 100 erscheint nicht |
| 129 | Die Aufgabe wurde mit Prozess-ID gestartet9 | Der Prozess wurde erzeugt. Ab hier ist es eine Frage des Skripts |
| 200 / 201 | Die Aktion wurde gestartet / die Aktion wurde abgeschlossen9 | 201 bedeutet „das gestartete Programm hat sich beendet“ und sagt nichts über Erfolg oder Misserfolg des Inhalts aus. Das aktuelle Windows liefert im Meldungstext und in den Ereignisdaten von 201 den Rückgabewert (ResultCode) mit — dort schaut man nach (Abschnitt 4.3) |
| 202 | Die Aufgabenplanung konnte die Aktion nicht abschließen. Mit Fehlerwert9 | Fehlschlag auf Seiten der Aufgabenplanung. Dass dieser Eintrag erscheint, wenn das Programm mit 0x1 endet, ist nicht garantiert |
| 203 | Bereits der Start der Aktion selbst ist fehlgeschlagen. Mit Fehlerwert9 | Falscher Pfad zur ausführbaren Datei, ungültige Angabe bei „Starten in (optional)“, fehlende Rechte |
| 102 | Die Aufgabe wurde normal beendet9 | Endpunkt des Erfolgsfalls |
| 111 | Das Zeitlimit für die Ausführung wurde überschritten, die Aufgabe wurde beendet9 | „Zeit bis zum Stoppen“ (Standard 3 Tage) erreicht. Siehe Kapitel 7 |
| 322 | Eine andere Instanz derselben Aufgabe lief noch, daher kein Start10 | Die Steuerung von Mehrfachstarts greift. Der vorherige Lauf ist noch nicht beendet. Siehe Kapitel 7 |
| 323 | Eine laufende Instanz wurde gestoppt, um eine neue Instanz zu starten9 | MultipleInstances steht auf „bestehende Instanz stoppen“ |
| 327 | Instanz gestoppt, weil die Stromversorgung auf Akku umgeschaltet hat9 | Energiebedingung (Abschnitt 4.4). Häufig bei Notebooks/Außendienstgeräten |
| 328 | Instanz gestoppt, weil der Computer nicht mehr im Leerlauf war9 | Die Leerlaufbedingung ist aktiviert |
| 329 | Instanz gestoppt, weil die Aufgabe das Zeitlimit überschritten hat9 | Wie bei 111 die Gestaltung der Ausführungszeit überdenken |
| 330 | Instanz auf Anforderung eines Benutzers gestoppt9 | Jemand hat sie manuell gestoppt |
Für das Lesen gibt es drei zentrale Punkte.
- Fehlt 100, prüfen Sie zunächst, ob 101 (Aufgabe konnte nicht gestartet werden) erscheint. Ist das der Fall, hat der Trigger ausgelöst, aber der Start ist fehlgeschlagen — verdächtigen Sie den Fehlerwert und das Ausführungskonto (Kapitel 3). Fehlt auch 101, liegt die Ursache vor der Aufgabe selbst (Trigger, Bedingungen, Deaktivierung); erscheint 322, ist der unmittelbare Grund, dass die vorherige Instanz noch nicht beendet ist.
- Ist 100 vorhanden, aber 102 fehlt, wurde gestartet, aber nicht beendet. Bei 111/329 ist die Zeit abgelaufen, bei 203 ist bereits der Start selbst fehlgeschlagen, und bei 327/328 wurde die laufende Instanz durch die Energie- oder Leerlaufbedingung gestoppt (327/328 sind beide keine „nicht gestartet“-, sondern „gestoppt“-Einträge und erscheinen daher nach 100).
- Sind sowohl 100 als auch 102 vorhanden, das Ergebnis aber unerwartet, hat die Aufgabenplanung ihren Verantwortungsbereich vollständig durchlaufen. Von hier an lässt sich nur noch anhand der Protokolle auf Skriptseite weiterverfolgen (Kapitel 6).
flowchart TD
S["Läuft nicht / Ergebnis unerwartet"] --> Q1{"Gibt es Ereignis 100 (Start)?"}
Q1 -->|"nein"| Q1b{"Gibt es Ereignis 101?"}
Q1b -->|"ja"| A0["Trigger hat ausgelöst, Start fehlgeschlagen<br/>Fehlerwert von 101 und<br/>Ausführungskonto prüfen (Kapitel 3)"]
Q1b -->|"nein"| A1["Ursache liegt vor der Aufgabe selbst<br/>Trigger, Bedingungen, Deaktivierung (Abschnitt 4.4)<br/>bei 322 ist der vorherige Lauf noch nicht beendet"]
Q1 -->|"ja"| Q2{"Gibt es Ereignis 102 (normaler Abschluss)?"}
Q2 -->|"nein"| B["Gestartet, aber nicht beendet<br/>203 = Start selbst fehlgeschlagen<br/>111/329 = Zeit abgelaufen<br/>327/328 = durch Energie-/Leerlaufbedingung gestoppt<br/>202 = Fehlschlag auf Seiten der Aufgabenplanung"]
Q2 -->|"ja"| C["Die Aufgabenplanung hat vollständig durchlaufen<br/>bei 0x1 hat das Skript<br/>den Beendigungscode 1 zurückgegeben<br/>weiterverfolgen im Protokoll des Skripts (Kapitel 6)"]
Abbildung 1: Ob die Ereignisse 100 und 102 vorhanden sind, entscheidet, ob die weitere Suche bei der Aufgabenkonfiguration oder beim Skript ansetzt
An dieser Stelle ein Punkt, den man leicht falsch versteht. Endet ein Programm mit einem von null verschiedenen Wert wie 0x1, erscheint aus Sicht der Aufgabenplanung dennoch 201 (ACTION_SUCCESS), denn sie sieht nur „gestartet und beendet“. 202 ist die Meldung dafür, „dass die Aufgabenplanung die Aktion nicht abschließen konnte“, und keine Sammelstelle für einen von null verschiedenen Beendigungscode des Programms.9 Sucht man also nach 0x1, findet man 202 mitunter gar nicht. Zu betrachten sind stattdessen der Rückgabewert von 201 und die Spalte „Letztes Ausführungsergebnis“ der Aufgabe (Abschnitt 4.3). Da der ResultCode von 201 und das „Letzte Ausführungsergebnis“ nicht zwingend übereinstimmen, ist es am zuverlässigsten, wie in Kapitel 6 gezeigt, den eigenen Beendigungscode im Skript selbst zu protokollieren.
4.3 Wie „Letztes Ausführungsergebnis“ zu lesen ist
| Anzeige | Bedeutung |
|---|---|
0x0 |
Normal beendet (das gestartete Programm hat Beendigungscode 0 zurückgegeben) |
0x1 |
Das gestartete Programm hat Beendigungscode 1 zurückgegeben (kein Fehler der Aufgabenplanung selbst) |
0x41300 |
Wartet auf die nächste geplante Ausführung (SCHED_S_TASK_READY) |
0x41301 |
Wird derzeit ausgeführt (SCHED_S_TASK_RUNNING) |
0x41303 |
Wurde noch nie ausgeführt (SCHED_S_TASK_HAS_NOT_RUN) |
0x8007010B |
Ungültige Angabe des Startordners („Starten (optional)“). Typisches Symptom bei gesetzten Anführungszeichen |
0x8007052E |
Anmeldefehler. Gespeichertes Kennwort veraltet, fehlende Rechte usw. |
Werte der Reihe 0x413xx sind Statuscodes der Aufgabenplanung, 0x8007xxxx sind Windows-Fehlercodes, und kleine Werte wie 0x1 oder 0x2 sind Beendigungscodes des gestarteten Programms selbst.3 Kennt man diese Unterscheidung, sucht man von Anfang an nicht mehr an der falschen Stelle (Aufgabenkonfiguration oder Skript).
flowchart TB
R["Der bei „Letztes Ausführungsergebnis“ angezeigte Wert"]
R -->|"kleiner Wert wie 0x0/0x1/0x2"| P["Beendigungscode des gestarteten Programms selbst<br/>→ zu untersuchen: das Skript"]
R -->|"0x413xx"| ST["Statuscode der Aufgabenplanung<br/>(wartend, laufend, nicht ausgeführt)<br/>→ zeigt von vornherein keinen Fehlschlag an"]
R -->|"0x8007xxxx"| Q{"Wurde die Aktion gestartet?<br/>(Gibt es 201, erscheint 203 nicht?)"}
Q -->|"nicht gestartet"| W["Windows-Fehlercode<br/>(ungültiger Startordner, Anmeldefehler usw.)<br/>→ zu untersuchen: die Aufgabenkonfiguration"]
Q -->|"gestartet"| W2["Vom Kindprozess zurückgegebener Beendigungscode.<br/>Manche Anwendungen geben ihn im HRESULT-Format zurück<br/>→ zu untersuchen: das Skript"]
Abbildung 2: Im selben Feld vermischen sich Werte aus drei verschiedenen Systemen. 0x8007xxxx lässt sich allerdings nicht allein am Präfix entscheiden ── wurde die Aktion gestartet, handelt es sich um den vom Kindprozess zurückgegebenen Wert; gleichen Sie deshalb Ereignis 201 und 203 ab, um die Herkunft zu bestimmen
4.4 Vorsicht bei den Standardwerten von Bedingungen und Einstellungen
- Energiebedingung: Standardmäßig ist „Aufgabe nur starten, wenn der Computer über Netzstrom versorgt wird“ aktiviert. Wird ein Notebook als Testrechner verwendet, entsteht ein „nicht reproduzierbarer Fehler“, der nur bei Akkubetrieb auftritt. Ab Windows 10 werden zudem Trigger vieler Aufgaben verzögert, solange der Akkusparmodus aktiv ist.11
- Verpasster Startzeitpunkt: War der PC ausgeschaltet und ist der Startzeitpunkt bereits verstrichen, wird die Aufgabe standardmäßig erst zur nächsten geplanten Zeit ausgeführt. Aktivieren Sie „Die Aufgabe sofort ausführen, falls ein geplanter Start verpasst wurde“ (
-StartWhenAvailable) ausdrücklich, oder legen Sie als Entwurfsentscheidung fest, ob der Job verpasst werden darf.5 - Ruhezustand aufheben: Bei einem nächtlichen Job auf einem PC, der in den Ruhezustand geht, entscheiden Sie auch über die Notwendigkeit von „Zum Ausführen der Aufgabe den Ruhezustand beenden“ (
-WakeToRun).
Fassen wir zusammen, in welchem Tab der GUI die bisher genannten Einstellungen zu finden sind. Es handelt sich um den Aufbau des Dialogs, den Sie über Rechtsklick auf die Aufgabe → „Eigenschaften“ öffnen.
| Tab | Was hier festgelegt wird | Entsprechende Stelle in diesem Artikel |
|---|---|---|
| Allgemein | Aufgabenname, Ausführungskonto („Benutzer oder Gruppe ändern“), „Nur ausführen, falls Benutzer angemeldet ist“ / „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“, „Kennwort nicht speichern“, „Mit höchsten Privilegien ausführen“ | Kapitel 3 |
| Trigger | Wann die Aufgabe startet. Über „Neu“ Uhrzeit-, Anmelde- oder Ereignis-Trigger hinzufügen | Abschnitt 4.5 |
| Aktionen | Die drei Felder „Programm/Skript“, „Argumente hinzufügen (optional)“ und „Starten (optional)“. Hier keine Anführungszeichen bei „Starten (optional)“ setzen | Kapitel 5 |
| Bedingungen | Energiebedingung (nur bei Netzbetrieb / bei Akkuwechsel stoppen), Leerlaufbedingung, Netzwerkbedingung | Vorherige Aufzählung |
| Einstellungen | „Die Aufgabe sofort ausführen, falls ein geplanter Start verpasst wurde“, „Regel, die gilt, wenn die Aufgabe bereits ausgeführt wird“, „Zeit bis zum Stoppen“ | Vorherige Aufzählung, Kapitel 7 |
| Verlauf | Die Ereignisliste dieser Aufgabe. Standardmäßig deaktiviert und bis zur Aktivierung nach dem Vorgehen aus Abschnitt 4.1 leer | Abschnitt 4.1–4.2 |
4.5 Vorsicht beim Trigger-Entwurf selbst
Denkt man „läuft nicht“, kann es auch daran liegen, dass der Trigger-Entwurf von vornherein nicht der Absicht entsprach.
- „Jeden 31. des Monats“ wird in Monaten ohne einen 31. Tag nicht ausgeführt. Für eine Monatsendeverarbeitung ist es sicherer, den Entwurf auf „jeweils der letzte Tag des Monats“ auszurichten (den Vormonat zu Monatsbeginn verarbeiten, oder das Datum im Skript selbst prüfen).
- Die Uhrzeit ist die lokale Uhrzeit der Maschine, auf der die Aufgabe registriert wurde. Verteilen Sie dieselbe XML an Geräte an Auslandsstandorten oder an einen seltenen, auf UTC eingestellten Server, verschiebt sich der Ausführungszeitpunkt je nach Standort. Legen Sie als Spezifikation fest, ob „an allen Standorten 6 Uhr morgens japanischer Zeit“ oder „6 Uhr morgens Ortszeit jedes Standorts“ gemeint ist.
- Beginnt man, in der Aufgabenplanung kurze Wiederholungsintervalle (etwa alle 5 Minuten) einzusetzen, ist Vorsicht geboten. Als Werkzeug für „eine Stapelverarbeitung pro Tag“ ist sie hervorragend, doch sobald minutengenaues Polling oder eine dauerhafte Überwachung nötig wird, ist das die Domäne eines dauerhaft laufenden Prozesses (siehe Kapitel 8).
- Ereignis-Trigger sind mächtig, aber prüfen Sie vorab, ob das Zielereignis wirklich stabil protokolliert wird. Eine Konstruktion, die ein bestimmtes Ereignis-ID im Anwendungsprotokoll als Trigger verwendet, hört stillschweigend auf zu funktionieren, sobald sich durch ein Update der Anwendung die Art der Ereignisausgabe ändert. Ein Zeit-Trigger kombiniert mit einer Bedingungsprüfung im Skript lässt sich im Ergebnis oft leichter nachvollziehen.
5. Typische Muster für ein Ende mit 0x1 und der korrekte Aufruf von PowerShell
0x1 ist nur das Ergebnis eines fehlgeschlagenen Skripts, die Ursache liegt daher in Unterschieden der Skript-Ausführungsumgebung. Zwischen manueller und geplanter Ausführung unterscheiden sich vor allem folgende Punkte.
- Das aktuelle Verzeichnis ist unterschiedlich: Ohne Angabe bei „Starten (optional)“ läuft die Aufgabe etwa unter
C:\Windows\System32. Mit relativen Pfaden geschriebene Skripte brechen genau hier. Bauen Sie Pfade im Skript relativ zu$PSScriptRootauf, und geben Sie im Aufgabenteil bei „Starten (optional)“ den Arbeitsordner an. Setzen Sie dabei im Feld „Starten (optional)“ keine Anführungszeichen — schreiben Sie auch Pfade mit Leerzeichen ohne Anführungszeichen (mit Anführungszeichen schlägt es mit0x8007010Bfehl). - Umgebungsvariablen und Profil sind unterschiedlich: Gehen Sie davon aus, dass Umgebungsvariablen, die durch Anmeldeskripte oder das Benutzerprofil gesetzt werden, sowie zugeordnete Netzlaufwerke (z. B. X:) in einer nicht interaktiven Sitzung nicht existieren. Verwenden Sie direkt UNC-Pfade (
\\server\share\...) und schließen Sie mit-NoProfileProfilunterschiede aus. - Die Ausführungsrichtlinie ist unterschiedlich: Auch wenn für den Benutzer
RemoteSignedgesetzt ist, kann sie für das Dienstkonto ungesetzt sein. Geben Sie-ExecutionPolicy Bypassausdrücklich als Argument der Aufgabe an. - Die Beendigungscode-Konvention des Werkzeugs ist speziell:
robocopybeispielsweise gibt 1 zurück, wenn „alle Dateien erfolgreich kopiert wurden“. Ein Wrapper, der den Beendigungscode unverändert durchreicht, wirkt dadurch trotz Erfolg wie0x1— oder umgekehrt. Prüfen Sie unbedingt die Beendigungscode-Konvention jedes verwendeten externen Befehls.
Bei robocopy steht in der offiziellen Tabelle der Beendigungscodes, dass 0 bis 7 Kombinationen „ohne Fehlschlag“ sind, und 8 oder höher zeigt an, dass beim Kopiervorgang mindestens ein Fehlschlag aufgetreten ist.12 Der Wert darf also nicht unverändert durchgereicht werden — richtig ist ein auf 0/1 normalisierender Wrapper.
# Argumente selbst entgegennehmen. Nicht implizit von Variablen des Aufrufers abhängen
param(
[Parameter(Mandatory)][string]$Source,
[Parameter(Mandatory)][string]$Destination
)
# robocopy gibt den Beendigungscode in $LASTEXITCODE zurück.
# Auch bei gesetztem $ErrorActionPreference = 'Stop' wird ein von null
# verschiedener Beendigungscode nativer Befehle nicht zu einer Ausnahme,
# man muss ihn selbst auswerten
robocopy $Source $Destination /E /R:2 /W:5 /NP
$rc = $LASTEXITCODE
if ($rc -ge 8) {
Write-Error "robocopy ist fehlgeschlagen. Beendigungscode: $rc"
exit 1
}
# 0–7 bedeutet kein Fehlschlag. Protokollieren, was passiert ist, aber der Aufgabenplanung Erfolg melden
Write-Host "robocopy normal beendet. Beendigungscode: $rc"
exit 0
Die entscheidende Zeile ist -ge 8. Schreibt man stattdessen if ($rc -ne 0), gilt selbst der Fall, dass erfolgreich kopiert wurde (1), als Fehlschlag. Das ist die eigentliche Ursache hinter dem gängigen Anliegen „jeden Morgen kommt eine Benachrichtigung, das Backup sei fehlgeschlagen, dabei wurden die Dateien tatsächlich kopiert“.
Die Grundform des PowerShell-Aufrufs sieht so aus.
Programm/Skript: pwsh.exe (bei Windows PowerShell: powershell.exe)
Argumente hinzufügen: -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Jobs\Cleanup-Logs.ps1"
Starten (optional): C:\Jobs (ohne Anführungszeichen)
-File statt -Command verwendet man, weil dadurch nicht nur das Escaping von Argumenten unkomplizierter wird, sondern das exit n des Skripts unmittelbar zum Beendigungscode des Prozesses wird, sodass sich Erfolg und Misserfolg am „Letzten Ausführungsergebnis“ der Aufgabenplanung ablesen lassen. Legen Sie auch im Skript selbst fest, bei Erfolg 0 und bei Misserfolg explizit einen von 0 verschiedenen Wert zurückzugeben.
# Auf Stop setzen, damit auch "nicht abbrechende Fehler" von Cmdlets im catch landen.
# Ohne diese Einstellung können Fehlschläge etwa von Copy-Item unbemerkt durchrutschen und zu exit 0 führen
$ErrorActionPreference = 'Stop'
try {
Main
exit 0
}
catch {
Write-Error $_
exit 1
}
Wo man Ausnahmen abfängt und wie man sie protokolliert, behandeln wir ausführlich im separaten Artikel „Ausnahmen abfangen und Protokollierung entwerfen“.
6. Protokolle muss man selbst hinterlassen
Der Verlauf der Aufgabenplanung sagt nur, „ob gestartet wurde und wie der Beendigungscode lautet“. Was das Skript im Einzelnen getan hat, muss das Skript selbst als Protokoll hinterlassen.
Als Minimum bietet sich statt einer Umleitung über die Argumente der Aufgabe (die Umleitungssyntax im Feld „Argumente“ der Aufgabenplanung funktioniert nicht, da sie nicht über eine Shell läuft) eine Transkript-Aufzeichnung innerhalb des Skripts an.
$logDir = 'C:\Jobs\logs'
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Start-Transcript -Path (Join-Path $logDir ("cleanup-{0:yyyyMMdd}.log" -f (Get-Date))) -Append
try {
# Eigentliche Verarbeitung
}
finally {
Stop-Transcript
}
Für den Umgang mit dem Problem, dass sich Protokolle selbst immer weiter anhäufen (Generationsverwaltung, Archivierung), lässt sich der Inhalt aus „PowerShell-Skripte im Einsatz ── Protokolluntersuchung, Archivierung und Reporting sicher automatisieren“ unverändert übernehmen.
Einen Schritt weiter gedacht, lohnt sich auch das Schreiben ins Windows-Ereignisprotokoll. Anders als eine Dateiprotokollierung hat das den Vorteil, dass Erfolg oder Misserfolg dort ankommt, wo der Betrieb ohnehin schon hinschaut (Ereignisanzeige, bestehende Überwachungswerkzeuge).
# --- Einmalig bei der Einrichtung, mit Administratorrechten ausführen (Installationsprogramm oder Initialisierungsskript) ---
if (-not [System.Diagnostics.EventLog]::SourceExists('KS-Jobs')) {
[System.Diagnostics.EventLog]::CreateEventSource('KS-Jobs', 'Application')
}
# --- Der eigentliche Job (läuft mit dem Konto minimaler Rechte) führt nur das Schreiben aus.
# SourceExists kann für die Suche über alle Protokolle Administratorrechte erfordern und
# wird zur Laufzeit deshalb nicht aufgerufen.
# Der folgende Code ist für den Fehlerhandler (catch) von Main gedacht ---
catch {
$err = $_
try {
[System.Diagnostics.EventLog]::WriteEntry('KS-Jobs',
"Cleanup-Logs failed: $($err.Exception.Message)",
[System.Diagnostics.EventLogEntryType]::Error, 1001)
}
catch {
# Dass sich nicht ins Ereignisprotokoll schreiben lässt, darf nicht dazu führen,
# den eigentlichen Fehlschlag zu verschlucken
Write-Warning "Schreiben ins Ereignisprotokoll fehlgeschlagen: $_"
}
exit 1
}
Zwei Ergänzungen. Erstens erfordert die Registrierung der Ereignisquelle (CreateEventSource) Administratorrechte. Mischt man die Registrierung in den eigentlichen Job, entsteht beim ersten produktiven Fehlschlag unter dem Konto minimaler Rechte ein doppelter Fehlschlag: „Registrierungsversuch → Ausnahme → das eigentlich wichtige Ereignis kann nicht geschrieben werden“. Trennen Sie die Registrierung wie oben in die Einrichtung ab und beschränken Sie die Laufzeit auf das reine Schreiben. Zweitens: Verwenden Sie wie im Registrierungsbeispiel dieses Artikels pwsh.exe als Ausführungs-Engine, stehen die Cmdlets New-EventLog / Write-EventLog aus der Zeit von Windows PowerShell 5.1 nicht zur Verfügung (der Befehl wird nicht gefunden, und das gesamte Skript schlägt fehl). Ruft man wie oben die .NET-Klassen direkt auf, funktioniert es sowohl unter 5.1 als auch unter 7.
Ergänzend dazu verhindert ein einziger Mechanismus — „bei einem Fehlschlag erreicht es einen Menschen“ per E-Mail oder Benachrichtigung an Teams/Slack — den Zwischenfall, „erst bei einer Bestandsaufnahme bemerkt man, dass es seit Monaten stillsteht“. Eine aufwendige Benachrichtigungsinfrastruktur ist dafür nicht nötig; wenige Zeilen, die nur bei einem Fehlschlag per Webhook posten, reichen völlig aus. Umgekehrt wird „bei jedem Erfolg eine Benachrichtigung“ über kurz oder lang nicht mehr gelesen — beschränken Sie Erfolgsmeldungen besser auf eine wöchentliche Zusammenfassung und richten Sie die Erkennung auf Fehlschläge sowie ein „nicht ausgeführt“ (veralteter Zeitpunkt der letzten Ausführung) aus. Was ins Protokoll gehört, ordnen wir ebenfalls in „Ausnahmen abfangen und Protokollierung entwerfen“ ein.
7. Steuerung von Mehrfachstarts und langer Ausführungsdauer
Was passiert, wenn sich die vorherige Ausführung hinzieht und währenddessen der nächste geplante Zeitpunkt eintrifft? Das entscheidet sich über die im Einstellungen-Tab hinterlegte „Regel, die gilt, wenn die Aufgabe bereits ausgeführt wird“, der in PowerShell -MultipleInstances entspricht.5
| Einstellungswert | Verhalten | Geeignet für |
|---|---|---|
| IgnoreNew (GUI-Standard: Keine neue Instanz starten) | Läuft die Aufgabe bereits, wird der neue Start übersprungen | Idempotente periodische Batch-Jobs allgemein. Zuerst diese Wahl |
| Queue | Läuft die Aufgabe bereits, wird nach deren Ende der Reihe nach ausgeführt | Auswertungen, bei denen kein Durchlauf verloren gehen darf |
| Parallel | Startet parallel | Grundsätzlich vermeiden. Nur, wenn Parallelsicherheit gewährleistet ist |
Setzen Sie zusätzlich „Zeit bis zum Stoppen“ (-ExecutionTimeLimit, Standard 3 Tage) auf einen realistischen Wert (etwa das 2- bis 3-Fache der erwarteten Laufzeit) — das verhindert, dass ein hängender Prozess den Job des Folgetags mit sich reißt.5
Zu beachten ist, dass IgnoreNew oder Queue nur innerhalb derselben Aufgabendefinition schützen. Ruft eine andere Aufgabe dasselbe Skript auf, oder führt jemand es im Rahmen einer Störungsbehebung manuell aus, wird ein daraus entstehender Konflikt nicht verhindert. Greifen mehrere Wege auf dieselbe Ressource (Datei, Datenbank, externes System) zu, sollte auch das Skript selbst eine wechselseitige Sperre besitzen. Der Standardweg dafür ist ein benannter Mutex.
$mutex = New-Object System.Threading.Mutex($false, 'Global\KS-Cleanup-Logs')
$acquired = $false
try {
try {
$acquired = $mutex.WaitOne(0)
}
catch [System.Threading.AbandonedMutexException] {
# Der vorherige Job wurde zwangsbeendet, während er den Mutex hielt (Überschreitung
# der "Zeit bis zum Stoppen" der Aufgabe, gekillter Prozess, Stromausfall usw.).
# In diesem Fall gibt WaitOne nicht false zurück, sondern wirft eine Ausnahme,
# und der Besitz ist bereits auf uns übergegangen. Fängt man sie nicht ab, bleibt
# jede künftige Ausführung genau hier hängen, und der Job läuft nie wieder
$acquired = $true
Write-Warning 'Der vorherige Lauf wurde abnormal beendet. Prüfen Sie die Nachbereitung der unterbrochenen Arbeit.'
# Hier prüfen, ob teilweise geschriebene Dateien oder unvollständige Datensätze
# zurückgeblieben sind, bevor mit der eigentlichen Verarbeitung fortgefahren wird
}
if (-not $acquired) {
Write-Warning 'Wird beendet, da eine andere Instanz läuft.'
exit 0 # 0, wenn "nicht ausgeführt" nicht als Fehlschlag gelten soll; sonst ungleich 0
}
# Eigentliche Verarbeitung
}
finally {
if ($acquired) { $mutex.ReleaseMutex() }
$mutex.Dispose()
}
AbandonedMutexException ist eine Ausnahme, die anzeigt, „dass der vorherige Besitzer verschwunden ist, ohne freizugeben“, und der Besitz ist bereits in dem Moment, in dem sie geworfen wird, auf uns übergegangen. Bricht man hier deshalb einfach mit exit ab, bleibt der Mutex unfreigegeben, dieselbe Ausnahme tritt beim nächsten Mal erneut auf, und der Job läuft nie wieder. Richtig ist, sie abzufangen, den Zustand der unterbrochenen Arbeit zu prüfen und dann fortzufahren.
Ein vorangestelltes Global\ im Namen sorgt dafür, dass die Sperre auch über verschiedene Sitzungen hinweg wirkt (etwa zwischen der Aufgabe eines anderen Benutzers und einer manuellen Ausführung). Ob man auf die Sperre wartet (indem man WaitOne einen Timeout übergibt) oder sofort aufgibt, richtet sich nach der Natur des Jobs. Zu beachten ist, dass ein mit Global\ benanntes Objekt für jeden auf der Maschine sichtbar ist. Auf einem gemeinsam genutzten Server, auf dem sich mehrere Benutzer anmelden, führt es dazu, dass der Job — sei es aus Böswilligkeit oder aus Versehen — für immer übersprungen wird, wenn jemand zuerst einen Mutex gleichen Namens ergreift (und das sieht bei exit 0 sogar wie ein normaler Erfolg aus). Setzen Sie in einer solchen Umgebung entweder eine ACL (MutexSecurity) auf den Mutex, um festzulegen, welche Konten ihn erwerben dürfen, oder lassen Sie zumindest „Erwerb fehlgeschlagen, deshalb übersprungen“ in die Benachrichtigung und das Ereignisprotokoll aus dem vorherigen Abschnitt einfließen, damit sich eine Serie von Überspringungen per Überwachung erkennen lässt. Die wechselseitige Sperre bei einer Anbindung über Dateien behandeln wir ausführlich in „Best Practices für Dateiintegration und Sperren“.
8. Wann man von der Aufgabenplanung Abschied nimmt ── Abgrenzung zum dauerhaft laufenden Dienst
Die Aufgabenplanung ist nicht allmächtig. Wächst der Bedarf über sie hinaus, gibt es eine Grenze, an der es besser ist, das Konstrukt zu wechseln, statt sie erzwungen weiterzuverwenden.
| Anforderung | Geeignetes Konstrukt |
|---|---|
| Zeitgesteuerte Batch-Verarbeitung bis zu einigen Malen pro Tag | Aufgabenplanung |
| Der Auslöser ist eine Mischung aus Mensch, Ereignis und Uhrzeit, und der gesamte Ablauf soll sichtbar sein | Power Automate (separater Artikel) |
| Minutengenaues Polling, dauerhafte Überwachung, Warteschlangenverarbeitung | Windows-Dienst / dauerhaft laufender Prozess |
| Zustand soll zwischen Durchläufen erhalten bleiben, Wiederholungsversuche und Backoff sollen fein gesteuert werden | Windows-Dienst / dauerhaft laufender Prozess |
Beginnt man, mit einer „Aufgabe alle 5 Minuten“ ein Polling zu betreiben, fallen bei jedem Start Kosten für Prozesserzeugung und das Laden von Modulen an, zusätzlich braucht es einen Mechanismus, um den vorherigen Zustand etwa in einer Datei zu sichern — im Ergebnis implementiert man einen dauerhaft laufenden Prozess stückweise neu. Ist dieser Punkt erreicht, liegt es nahe, mit .NET’s Generic Host und BackgroundService einen dauerhaft laufenden Dienst zu bauen. Das Implementierungsmuster erläutern wir in „Generic Host und BackgroundService in einer Desktop-Anwendung verwenden“.
Umgekehrt ist es auch überzogen, eine monatliche oder tägliche Stapelverarbeitung eigens zu einem Dienst zu machen und die Zeitsteuerung selbst zu verwalten. Mit der Faustregel „Ausführungsintervall im Stundenbereich oder länger, die Verarbeitung ist unabhängig, kein Zustand wird gehalten → Aufgabenplanung; sobald das nicht mehr zutrifft → dauerhaft laufenden Dienst erwägen“ liegt man kaum je falsch.
9. Checkliste vor dem produktiven Einsatz
Vor der Registrierung empfiehlt es sich, folgende Punkte einmal durchzugehen.
- Wurde das Ausführungskonto festgelegt (nicht aus Gewohnheit SYSTEM gewählt; in einer Domäne gMSA geprüft)?
- Sind die Einschränkungen des Anmeldetyps verstanden (bei S4U kein Netzwerkzugriff; bei Password die Vorgehensweise bei einer Kennwortänderung festgelegt)?
- Wurde das Skript unter Bedingungen getestet, die dem Ausführungskonto entsprechen?
- Wird es in der Form
-NoProfile -NonInteractive -ExecutionPolicy Bypass -Fileaufgerufen? - Sind Pfade im Skript relativ zu
$PSScriptRoot/UNC (keine Abhängigkeit von zugeordneten Laufwerken oder relativen Pfaden)? - Wurden bei „Starten (optional)“ keine Anführungszeichen gesetzt?
- Wurden die Beendigungscodes entworfen (Erfolg 0 / Misserfolg ungleich 0; wurde die Beendigungscode-Konvention externer Befehle geprüft)?
- Wurde der Aufgabenverlauf aktiviert? Gibt es ein eigenes Protokoll des Skripts sowie eine Fehlschlag-Benachrichtigung?
- Wurden Energiebedingung, StartWhenAvailable, Mehrfachstart-Steuerung und Zeitlimit ausdrücklich gesetzt?
- Wurde die Aufgabendefinition als XML oder PowerShell-Skript im Repository gesichert?
10. Zusammenfassung
Die Aufgabenplanung ist nicht „ein Skript schreiben, und fertig“ — erst wenn die drei Grundlagen Ausführungskonto, Sitzung und Standardwerte durchdacht sind, läuft sie stabil im Betrieb. Anders gesagt: Sind die in diesem Artikel genannten Punkte — Wahl des Anmeldetyps, Aktivierung des Verlaufs, Entwurf von Beendigungscodes und Protokollierung, ausdrückliche Festlegung von Mehrfachstart-Steuerung und Zeitlimit — bei der Registrierung einmal erledigt, macht die Aufgabe danach erstaunlich wenig Arbeit.
„Manuell funktioniert es, aber bei geplanter Ausführung nicht“ hat so gut wie immer Unterschiede bei Sitzung und Umgebung zur Ursache. Bevor Sie planlos an Einstellungen herumschrauben, prüfen Sie im Verlauf-Tab, wie weit es gekommen ist, und arbeiten Sie das Eingrenzungsvorgehen dieses Artikels von oben nach unten durch.
Verwandte Artikel
- PowerShell-Skripte im Einsatz ── Protokolluntersuchung, Archivierung und Reporting sicher automatisieren
- PowerShell-Tests mit Pester einrichten ── Das praktische Muster, um Betriebsskripte robuster zu machen
- Mit Power Automate Geschäftsprozesse automatisieren ── Cloud-Flows und Desktop-Flows richtig einsetzen und Fehlerbehandlung entwerfen
- Wann eine Windows-Anwendung tatsächlich Administratorrechte benötigt
Verwandte Beratungsleistungen
Die KomuraSoft LLC übernimmt Design-Reviews zur Betriebsautomatisierung mit PowerShell und der Aufgabenplanung sowie die Sanierung periodischer Jobs, die „zwar laufen, aber niemand mehr reparieren kann“.
- Technische Beratung und Design-Review
- Windows-Anwendungsentwicklung
- Fehleruntersuchung und Ursachenanalyse
- Kontakt
Referenzlinks
-
Microsoft Learn, logonType Simple Type (Task Scheduler). Zur Definition der Anmeldetypen. Dazu, dass bei S4U zwar kein Kennwort gespeichert wird, dafür aber kein Zugriff auf Netzwerk- und verschlüsselte Dateien möglich ist. ↩ ↩2 ↩3
-
Microsoft Learn, Troubleshoot issues with scheduled tasks not running. Zum Eingrenzungsvorgehen isolierter Skripttest → Status-/Verlaufsprüfung → Änderung der Sicherheitsoptionen sowie zum Speicherort des Operational-Ereignisprotokolls der TaskScheduler. ↩ ↩2 ↩3
-
Microsoft Learn, Task Scheduler error and success constants. Zu den Status- und Fehlercode-Definitionen wie SCHED_S_TASK_READY (0x41300), SCHED_S_TASK_RUNNING (0x41301) und SCHED_S_TASK_HAS_NOT_RUN (0x41303). ↩ ↩2
-
Microsoft Learn, Manage group Managed Service Accounts. Dazu, dass das Kennwort eines gMSA vom Domänencontroller verwaltet und vom Host abgerufen wird, dass die Aufgabe der Aufgabenplanung gMSA unterstützt, dass die Funktionsebene von Domäne und Gesamtstruktur Windows Server 2012 oder neuer sein muss, dass ein KDS-Stammschlüssel erforderlich ist (nachprüfbar über Ereignis-ID 4004 im Operational-Protokoll von KdsSvc), dass der gMSA-Name gesamtstrukturweit eindeutig sein muss, sowie zu
-PrincipalsAllowedToRetrieveManagedPasswordvonNew-ADServiceAccountund zuInstall-ADServiceAccount/Test-ADServiceAccountauf jedem Host. ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, New-ScheduledTaskSettingsSet. Zu den Parametern des Einstellungsobjekts einer Aufgabe wie MultipleInstances (Parallel/Queue/IgnoreNew), StartWhenAvailable und ExecutionTimeLimit (Standard 3 Tage). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Security Contexts for Tasks. Zum Sicherheitskontext von Aufgaben und dazu, dass für die Ausführung einer mit Password/S4U registrierten Aufgabe das Recht „Anmelden als Batchauftrag“ erforderlich ist. ↩ ↩2
-
Microsoft Learn, New-ScheduledTaskPrincipal. Dazu, dass
-UserIddas Ausführungskonto und-LogonTypedie Anmeldeart (None/Password/S4U/Interactive/Group/ServiceAccount/InteractiveOrPassword) angibt, sowie dazu, dass-RunLeveldie WerteLimitedundHighestannimmt. ↩ -
Microsoft Learn (Archiv), General Task Registration. Zu den Meldungsdefinitionen der Ereignisse 106 (Registrierung der Aufgabe), 113 (registriert, aber ein Teil der Trigger startet nicht), 116 (Konfiguration gespeichert, aber Anmeldeinformationen konnten nicht gespeichert werden), 140 (Aktualisierung) und 141 (Löschung) von Microsoft-Windows-TaskScheduler. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn (Archiv), Task Monitoring and Control. Zu den Ereignissen 100 (Aufgabenstart), 102 (normaler Abschluss), 111 (Beendigung wegen Überschreitung der Ausführungszeit), 129 (Start mit Prozess-ID), 200/201 (Start/Abschluss der Aktion), 202/203 (Fehlschlag beim Abschluss/Start der Aktion) und 323 (Stopp wegen Start einer neuen Instanz) von Microsoft-Windows-TaskScheduler. Insbesondere dazu, dass 201 den Symbolnamen
ACTION_SUCCESSträgt und „Task Scheduler successfully completed task … and action …“ lautet, während 202 „Task Scheduler failed to complete the … instance of the … task with action … The error value is: …“ lautet und anzeigt, dass die Aufgabenplanung die Aktion nicht abschließen konnte. Dazu, dass das von aktuellem Windows ausgegebene 201 Version 2 ist, im Meldungstext „… with return code N“ enthält und in den Ereignisdaten einenResultCodemitführt (einschließlich des Umstands, dass dieser Wert nicht immer mit dem „Letzten Ausführungsergebnis“ der Aufgabe übereinstimmt), sowie zu den Meldungsdefinitionen von 327 (Stopp wegen Wechsel auf Akku), 328 (Stopp, weil nicht mehr im Leerlauf), 329 (Timeout) und 330 (Stopp auf Benutzeranforderung). ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 -
Microsoft Learn (Archiv), Event ID 322 — Task Properties. Dazu, dass Ereignis 322 (Symbolname
NEW_INSTANCE_IGNORED) anzeigt, dass „eine andere Instanz derselben Aufgabe bereits lief und deshalb kein Start erfolgte“, sowie zum Vorgehen bei der Überprüfung von Bedingungen und Einstellungen. ↩ -
Microsoft Learn, What’s New in Task Scheduler. Dazu, dass ab Windows 10 Trigger nicht interaktiver Aufgaben verzögert werden, solange der Akkusparmodus aktiv ist. ↩
-
Microsoft Learn, robocopy. Zur Tabelle der Beendigungscodes (0 = keine Kopierobjekte, 1 = alle Dateien erfolgreich kopiert, 2 und höher = Kombinationen aus zusätzlichen Dateien und Abweichungen) sowie dazu, dass 8 oder höher anzeigt, dass beim Kopiervorgang mindestens ein Fehlschlag aufgetreten ist. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Power Automate versus PowerShell + Aufgabenplanung — Jedes Automatisierungswerkzeug dort einsetzen, wo es passt, statt sie zu vermischen
Für IT-Mitarbeiter in kleinen und mittleren Unternehmen, bei denen nächtliche PowerShell-+-Aufgabenplanung-Batches und Power-Automate-Flo...
Ereignisprotokolle mit Get-WinEvent praxisnah untersuchen — Die Geschwindigkeit der Filterung entscheidet über die Dauer der Untersuchung
Wie Sie die Untersuchung von Windows-Ereignisprotokollen mit PowerShell effizient gestalten. Behandelt, warum die Filterung mit Where-Obj...
Externe EXEs aus PowerShell korrekt aufrufen — Die Fallstricke bei Argument-Quoting, Exitcodes und Zeichensalat
Rufen Sie robocopy oder eine hauseigene EXE aus PowerShell auf, und die Argumente zerbrechen, der Exitcode ist nicht verfügbar, und die A...
PowerShell-Fehlerbehandlung und Retry-Design — Von der try/catch-Falle bis zu Exit Codes und bewährten Retry-Verfahren
Ein praxisnaher Überblick über PowerShell-Fehlerbehandlung: der Unterschied zwischen abschließenden und nicht abschließenden Fehlern, das...
Datum, Uhrzeit und Zeitzonen in Business-Apps — Von den Fallstricken von DateTime über das UTC-Speicherprinzip bis zum Testdesign
Zeitstempel verschieben sich nach einer Servermigration um neun Stunden; nur beim Auslandsbüro springt das Datum auf den Vortag zurück – ...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Was bedeutet das 0x1, das bei „Letztes Ausführungsergebnis“ in der Aufgabenplanung erscheint?
- 0x1 ist kein Fehler der Aufgabenplanung selbst, sondern bedeutet, dass das gestartete Programm den Beendigungscode 1 zurückgegeben hat. Die Ursache liegt also auf Seiten des Skripts. Typische Gründe für Unterschiede zwischen manueller und geplanter Ausführung sind das aktuelle Verzeichnis, Umgebungsvariablen bzw. das Profil sowie die Ausführungsrichtlinie. Zu beachten ist außerdem die Beendigungscode-Konvention externer Befehle wie robocopy, das auch im Erfolgsfall 1 zurückgeben kann. Kennt man die Unterscheidung, dass Codes der Reihe 0x413xx Statuscodes der Aufgabenplanung sind und 0x8007xxxx Windows-Fehlercodes, sucht man nicht mehr an der falschen Stelle.
- Warum funktioniert ein Skript manuell, aber nicht über die Aufgabenplanung?
- So gut wie immer liegt die Ursache in Unterschieden bei Ausführungskonto, Sitzung und Umgebung. Wählt man „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“, läuft die Aufgabe in einer nicht interaktiven Sitzung, in der zugeordnete Netzlaufwerke und die Umgebungsvariablen des Benutzerprofils nicht existieren. Zusätzlich hat man bei „Kennwort nicht speichern“ (S4U) keinen Zugriff auf Ressourcen im Netzwerk. Zur Eingrenzung eignen sich: den Verlauf-Tab prüfen, das Skript isoliert testen und vorübergehend auf „Nur ausführen, falls Benutzer angemeldet ist“ umstellen. Der Aufgabenverlauf ist standardmäßig deaktiviert — aktivieren Sie ihn unbedingt, bevor Sie in den produktiven Betrieb gehen.
- Wie ruft man ein PowerShell-Skript korrekt aus der Aufgabenplanung auf?
- Als Programm geben Sie pwsh.exe (oder powershell.exe) an, als Argumente ist die Grundform -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "vollständiger Pfad". Verwendet man -File statt -Command, wird das exit n des Skripts direkt zum Beendigungscode des Prozesses, sodass sich Erfolg und Misserfolg unterscheiden lassen. Pfade im Skript sollten relativ zu $PSScriptRoot aufgebaut werden, und im Feld „Starten in (optional)“ dürfen keine Anführungszeichen stehen (sonst schlägt es mit 0x8007010B fehl). Im Skript selbst sollte man bei Erfolg explizit 0 und bei Misserfolg explizit einen Wert ungleich 0 zurückgeben.
- Wie verhindert man, dass eine Aufgabe nach einer Kennwortänderung unbemerkt stehen bleibt?
- Registriert man eine Aufgabe mit „Ausführen, unabhängig davon, ob der Benutzer angemeldet ist“, wird das Kennwort gespeichert, sodass die Aufgabe nach einer Kennwortänderung dauerhaft mit einem Anmeldefehler (0x8007052E) stehen bleibt. In einer Domänenumgebung ist ein gMSA (group Managed Service Account), dessen Kennwort der Domänencontroller automatisch verwaltet, die erste Wahl — damit entfällt dieses Problem grundsätzlich. Verwenden Sie stattdessen ein dediziertes Dienstkonto, sollten Sie Kennwortablauf und Aufgabenbestand gemeinsam verwalten. Ergänzend verhindert eine Benachrichtigung per E-Mail oder Teams bei einem Fehlschlag, dass ein monatelanger Stillstand unbemerkt bleibt.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.