<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Automation on kenji.blog</title><link>http://kenji.blog/de/categories/automation/</link><description>Recent content in Automation on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>de</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 06:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/de/categories/automation/index.xml" rel="self" type="application/rss+xml"/><item><title>Vollständige Automatisierung täglicher Routineaufgaben mit PowerShell-Skripten - Ein Praxisbeispiel</title><link>http://kenji.blog/de/p/powershell-daily-task-automation-scripts/</link><pubDate>Sun, 13 Sep 2026 06:00:00 +0900</pubDate><guid>http://kenji.blog/de/p/powershell-daily-task-automation-scripts/</guid><description>&lt;img src="http://kenji.blog/p/powershell-daily-task-automation-scripts/img/eyecatch.jpg" alt="Featured image of post Vollständige Automatisierung täglicher Routineaufgaben mit PowerShell-Skripten - Ein Praxisbeispiel" />&lt;h2 id="einführung-warum-powershell-zur-automatisierung-von-aufgaben-nutzen">Einführung: Warum PowerShell zur Automatisierung von Aufgaben nutzen?
&lt;/h2>&lt;p>In modernen IT-Infrastrukturen und Entwicklungsumgebungen, in denen Windows OS als Plattform genutzt wird, sind &amp;ldquo;tägliche Routineaufgaben&amp;rdquo; eine unvermeidliche Herausforderung für Nutzer. Die manuelle Durchführung von Aufgaben wie Dateisicherungen, Überwachung von Systemprotokollen und Aktualisierung sowie Erstellung von Entwicklungsressourcen (Git-Repositories) ist eine Brutstätte für menschliche Fehler und führt zu wertvoller Zeitverschwendung.&lt;/p>
&lt;p>Früher wurden Batch-Dateien (&lt;code>.bat&lt;/code> und &lt;code>.cmd&lt;/code>) oder VBScript verwendet, aber heutzutage ist die optimale Lösung zweifellos &lt;strong>PowerShell&lt;/strong>. PowerShell ist nicht nur eine textbasierte Shell, sondern baut auf der leistungsstarken objektorientierten Basis des .NET Frameworks (und .NET Core) auf. Da die über die Pipeline übergebenen Daten keine &amp;ldquo;Strings&amp;rdquo; (Zeichenketten), sondern &amp;ldquo;Objekte&amp;rdquo; sind, ist es nicht notwendig, eine komplexe Textanalyse (wie mit grep, awk, sed) selbst zu implementieren, und Sie können leicht auf Daten zugreifen, indem Sie einfach Eigenschaften angeben.&lt;/p>
&lt;p>In diesem Artikel stellen wir drei praktische Beispiele für vollständige Automatisierungsskripte mit PowerShell vor, die direkt mit der praktischen Arbeit verknüpft sind (Backup auf ein NAS und Log-Rotation, Ereignisprotokollüberwachung und Slack-Benachrichtigungen, Batch-Update und Build mehrerer Git-Repositories). Vorab werden wir zudem grundlegende Technologien, die dafür erforderlich sind, wie PowerShell-Ausführungsrichtlinien, Modularisierung und die Integration in die Aufgabenplanung (Task Scheduler), eingehend erläutern.&lt;/p>
&lt;hr>
&lt;h2 id="die-grundlagen-für-die-powershell-automatisierung-schaffen">Die Grundlagen für die PowerShell-Automatisierung schaffen
&lt;/h2>&lt;p>Um Automatisierungsskripte sicher und zuverlässig in einer Produktionsumgebung auszuführen, sind einige Vorbereitungen erforderlich. Hier erläutern wir ausführlich das Verständnis von Ausführungsrichtlinien, die Modularisierung zur Verbesserung der Wiederverwendbarkeit und eine robuste Fehlerbehandlung.&lt;/p>
&lt;h3 id="1-powershell-ausführungsrichtlinien-execution-policy">1. PowerShell-Ausführungsrichtlinien (Execution Policy)
&lt;/h3>&lt;p>In Windows ist standardmäßig eine &amp;ldquo;Ausführungsrichtlinie&amp;rdquo; eingerichtet, um die versehentliche Ausführung schädlicher Skripte zu verhindern. Im Ausgangszustand (&lt;code>Restricted&lt;/code>) können überhaupt keine Skripte (&lt;code>.ps1&lt;/code>-Dateien) ausgeführt werden. Um eine Automatisierung durchzuführen, muss dies auf ein geeignetes Level geändert werden.&lt;/p>
&lt;p>Es gibt folgende Arten von Ausführungsrichtlinien:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Restricted&lt;/strong>: Erlaubt die Ausführung von Skripten nicht. (Standard)&lt;/li>
&lt;li>&lt;strong>AllSigned&lt;/strong>: Erlaubt nur die Ausführung von Skripten, die von einem vertrauenswürdigen Herausgeber signiert wurden.&lt;/li>
&lt;li>&lt;strong>RemoteSigned&lt;/strong>: Lokal erstellte Skripte können wie gewohnt ausgeführt werden, aber Skripte, die aus dem Internet heruntergeladen wurden, erfordern eine Signatur.&lt;/li>
&lt;li>&lt;strong>Unrestricted&lt;/strong>: Alle Skripte können ausgeführt werden, jedoch wird bei der Ausführung von aus dem Internet heruntergeladenen Skripten eine Warnung angezeigt.&lt;/li>
&lt;li>&lt;strong>Bypass&lt;/strong>: Nichts wird blockiert und es werden keine Warnungen angezeigt. Wird häufig für temporäre Skriptausführungen (wie CI/CD-Pipelines) verwendet.&lt;/li>
&lt;/ul>
&lt;p>Wenn Sie selbst erstellte Skripte über die Aufgabenplanung in der lokalen Umgebung eines Unternehmens ausführen, ist die praktischste und sicherste Einstellung &lt;code>RemoteSigned&lt;/code>. Starten Sie PowerShell mit Administratorrechten und führen Sie den folgenden Befehl aus.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Set-ExecutionPolicy&lt;/span> &lt;span class="n">RemoteSigned&lt;/span> &lt;span class="n">-Scope&lt;/span> &lt;span class="n">LocalMachine&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dadurch werden lokal erstellte Backup-Skripte und ähnliches ausgeführt, ohne blockiert zu werden.&lt;/p>
&lt;h3 id="2-wiederverwendbarkeit-von-code-durch-modularisierung-psm1--psd1">2. Wiederverwendbarkeit von Code durch Modularisierung (.psm1 / .psd1)
&lt;/h3>&lt;p>Bei der Durchführung komplexer Automatisierungsprozesse ist es aus Sicht der Wartbarkeit nicht empfehlenswert, alle Prozesse in eine einzige riesige &lt;code>.ps1&lt;/code>-Datei zu schreiben. Häufig verwendete Funktionen (z. B. Protokollausgabe, Senden von Webhooks an Slack, Fehlerbehandlung usw.) sollten als &amp;ldquo;Module&amp;rdquo; aufgeteilt werden.&lt;/p>
&lt;p>Ein PowerShell-Modul besteht hauptsächlich aus einer Skriptmodul-Datei (&lt;code>.psm1&lt;/code>) und einem Modulmanifest (&lt;code>.psd1&lt;/code>).&lt;/p>
&lt;p>Beispiel für &lt;strong>CommonUtils.psm1&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="kd">function&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nb">Write-CustomLog&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nb">CmdletBinding&lt;/span>&lt;span class="p">()]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">param&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nb">Parameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="na">Mandatory&lt;/span>&lt;span class="p">=&lt;/span>&lt;span class="vm">$true&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="no">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nv">$Message&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="nb">ValidateSet&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;INFO&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;WARNING&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;ERROR&amp;#39;&lt;/span>&lt;span class="p">)]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="no">string&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="nv">$Level&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;INFO&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$timestamp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-Date&lt;/span> &lt;span class="n">-Format&lt;/span> &lt;span class="s2">&amp;#34;yyyy-MM-dd HH:mm:ss&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$logLine&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;[&lt;/span>&lt;span class="nv">$timestamp&lt;/span>&lt;span class="s2">] [&lt;/span>&lt;span class="nv">$Level&lt;/span>&lt;span class="s2">] &lt;/span>&lt;span class="nv">$Message&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Sowohl Bildschirmausgabe als auch Dateiausgabe durchführen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="nv">$logLine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Add-Content&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;C:\logs\automation.log&amp;#34;&lt;/span> &lt;span class="n">-Value&lt;/span> &lt;span class="nv">$logLine&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Export-ModuleMember&lt;/span> &lt;span class="n">-Function&lt;/span> &lt;span class="nb">Write-CustomLog&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Um dieses Modul aus einem anderen Skript aufzurufen, verwenden Sie &lt;code>Import-Module&lt;/code> am Anfang des Skripts.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Import-Module&lt;/span> &lt;span class="s2">&amp;#34;C:\Scripts\Modules\CommonUtils.psm1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Write-CustomLog&lt;/span> &lt;span class="n">-Message&lt;/span> &lt;span class="s2">&amp;#34;Backup-Vorgang wird gestartet.&amp;#34;&lt;/span> &lt;span class="n">-Level&lt;/span> &lt;span class="s1">&amp;#39;INFO&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="3-robuste-fehlerbehandlung-try--catch">3. Robuste Fehlerbehandlung (try / catch)
&lt;/h3>&lt;p>Das Wichtigste bei der Automatisierung ist, &amp;ldquo;wie man sich verhält, wenn etwas schief geht&amp;rdquo;. In PowerShell können Sie das Standardverhalten beim Fehlschlagen eines Befehls steuern, indem Sie die integrierte Variable &lt;code>$ErrorActionPreference&lt;/code> setzen. Der Standardwert ist &lt;code>Continue&lt;/code> (Fehler anzeigen und mit der Verarbeitung fortfahren), aber für Automatisierungsskripte ist es die beste Vorgehensweise, ihn auf &lt;code>Stop&lt;/code> zu setzen und Ausnahmen explizit mit einem &lt;code>try / catch&lt;/code>-Block abzufangen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ErrorActionPreference&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;Stop&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Ein Vorgang, der fehlschlagen könnte&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$content&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-Content&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;C:\NonExistentFile.txt&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="no">System.Management.Automation.ItemNotFoundException&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Einen spezifischen Fehler abfangen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Datei nicht gefunden: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Red&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Alle anderen Fehler abfangen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Ein unerwarteter Fehler ist aufgetreten: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Red&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">finally&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Bereinigungs-Code, der unabhängig von Erfolg oder Fehlschlag immer ausgeführt wird&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Verarbeitung wird beendet.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Durch Nutzung dieser Basis können Sie Skripte erstellen, die sicher und nachvollziehbar sind, auch wenn sie nachts unbeaufsichtigt laufen.&lt;/p>
&lt;hr>
&lt;h2 id="integration-in-die-aufgabenplanung-task-scheduler-register-scheduledtask">Integration in die Aufgabenplanung (Task Scheduler) (Register-ScheduledTask)
&lt;/h2>&lt;p>Sobald das Skript fertig ist, benötigen Sie einen Mechanismus, um es regelmäßig auszuführen. In Windows ist die &amp;ldquo;Aufgabenplanung&amp;rdquo; (Task Scheduler) am zuverlässigsten. Es ist zwar möglich, dies über die GUI (&lt;code>taskschd.msc&lt;/code>) einzurichten, aber aus Sicht von Infrastructure as Code erklären wir hier, wie Aufgaben mithilfe von PowerShell-Cmdlets registriert werden.&lt;/p>
&lt;p>PowerShell bietet das Modul &lt;code>ScheduledTasks&lt;/code>, mit dem Sie Trigger (wann es ausgeführt werden soll), Aktionen (was ausgeführt werden soll) und Prinzipale (unter welchen Benutzerrechten es ausgeführt werden soll) im Detail definieren können.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 1. Aktion definieren (PowerShell ausgeblendet ausführen und das angegebene Skript übergeben)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$action&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-ScheduledTaskAction&lt;/span> &lt;span class="n">-Execute&lt;/span> &lt;span class="s2">&amp;#34;powershell.exe&amp;#34;&lt;/span> &lt;span class="n">-Argument&lt;/span> &lt;span class="s2">&amp;#34;-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\Scripts\DailyTasks.ps1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 2. Trigger definieren (Täglich um 03:00 Uhr ausführen)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$trigger&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-ScheduledTaskTrigger&lt;/span> &lt;span class="n">-Daily&lt;/span> &lt;span class="n">-At&lt;/span> &lt;span class="s2">&amp;#34;3:00AM&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 3. Prinzipal (Ausführungsbenutzerrechte) definieren (Mit SYSTEM-Rechten ausführen)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$principal&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-ScheduledTaskPrincipal&lt;/span> &lt;span class="n">-UserId&lt;/span> &lt;span class="s2">&amp;#34;NT AUTHORITY\SYSTEM&amp;#34;&lt;/span> &lt;span class="n">-LogonType&lt;/span> &lt;span class="n">ServiceAccount&lt;/span> &lt;span class="n">-RunLevel&lt;/span> &lt;span class="n">Highest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 4. Aufgabeneinstellungen konfigurieren&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$settings&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">New-ScheduledTaskSettingsSet&lt;/span> &lt;span class="n">-AllowStartIfOnBatteries&lt;/span> &lt;span class="n">-DontStopIfGoingOnBatteries&lt;/span> &lt;span class="n">-StartWhenAvailable&lt;/span> &lt;span class="n">-DontStopOnIdleEnd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 5. Aufgabe registrieren&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$taskName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;MyDailyAutomationTask&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Register-ScheduledTask&lt;/span> &lt;span class="n">-TaskName&lt;/span> &lt;span class="nv">$taskName&lt;/span> &lt;span class="n">-Action&lt;/span> &lt;span class="nv">$action&lt;/span> &lt;span class="n">-Trigger&lt;/span> &lt;span class="nv">$trigger&lt;/span> &lt;span class="n">-Principal&lt;/span> &lt;span class="nv">$principal&lt;/span> &lt;span class="n">-Settings&lt;/span> &lt;span class="nv">$settings&lt;/span> &lt;span class="n">-Description&lt;/span> &lt;span class="s2">&amp;#34;Aufgabe zur automatischen Ausführung täglicher Routineaufgaben&amp;#34;&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Durch einfaches Ausführen dieses Skripts wird ein Job in der Aufgabenplanung registriert, und das Skript wird täglich zur festgelegten Zeit mit SYSTEM-Rechten (den höchsten Rechten, die im Hintergrund ausgeführt werden, ohne ein Fenster anzuzeigen) ausgeführt.&lt;/p>
&lt;hr>
&lt;h2 id="praxisbeispiel-1-backup-auf-externes-nas-und-log-rotation">Praxisbeispiel 1: Backup auf externes NAS und Log-Rotation
&lt;/h2>&lt;p>Die tägliche Sicherung von Geschäftsdaten ist unerlässlich, aber ein manuelles Kopieren ist keine Option. Hier erstellen wir ein Skript, das &lt;code>Robocopy&lt;/code>, den stärksten Standard-Kopierbefehl in Windows, über PowerShell aufruft, die Ausführungsprotokolle ausgibt und alte Protokolle automatisch löscht (rotiert).&lt;/p>
&lt;h3 id="theoretischer-wert-der-ausführungszeit-bei-netzwerkübertragungen-math">Theoretischer Wert der Ausführungszeit bei Netzwerkübertragungen (Math)
&lt;/h3>&lt;p>Beim Entwerfen von Backup-Skripten ist es für den Betrieb wichtig abzuschätzen, wie lange der Vorgang dauern wird. Die geschätzte benötigte Zeit $T_{backup}$ für das Sichern auf einem NAS über das Netzwerk lässt sich durch die folgende Formel annähern.&lt;/p>
$$ T_{backup} = \frac{S_{total}}{B \times (1 - \alpha)} + C \times L $$&lt;p>Hierbei sind die Variablen wie folgt definiert:&lt;/p>
&lt;ul>
&lt;li>$S_{total}$ : Gesamtmenge der zu sichernden Daten (Bit)&lt;/li>
&lt;li>$B$ : Netzwerkbandbreite (bps, z. B. 1 Gbps = $10^9$ bps)&lt;/li>
&lt;li>$\alpha$ : Overhead des Netzwerks oder Protokolls (normalerweise 0,1 bis 0,2 bei TCP/IP oder SMB-Protokoll)&lt;/li>
&lt;li>$C$ : Gesamtzahl der Dateien&lt;/li>
&lt;li>$L$ : Verarbeitungs-Latenz pro Datei (Sekunden)&lt;/li>
&lt;/ul>
&lt;p>Insbesondere bei der Sicherung vieler kleiner Dateien (wie z. B. Quellcode) ist der Term für die Verzögerung aufgrund der Dateianzahl $C$ ($C \times L$) dominant. Aus diesem Grund ist es bei Backup-Vorgängen optimal, &lt;code>Robocopy&lt;/code> zu verwenden, das Multithreading-Übertragungen ermöglicht, anstatt ein einfaches Datei-Kopiertool zu nutzen.&lt;/p>
&lt;h3 id="ablaufdiagramm-des-backup-skripts">Ablaufdiagramm des Backup-Skripts
&lt;/h3>&lt;pre class="mermaid">
flowchart TD
A[&amp;#34;Backup-Skript starten&amp;#34;] --&amp;gt; B{&amp;#34;Ist NAS erreichbar?&amp;#34;}
B -- Ja --&amp;gt; C[&amp;#34;Robocopy ausführen (Multi-threaded)&amp;#34;]
B -- Nein --&amp;gt; D[&amp;#34;Fehlerprotokoll schreiben &amp;amp; Beenden&amp;#34;]
C --&amp;gt; E{&amp;#34;War Robocopy erfolgreich?&amp;#34;}
E -- Ja (ExitCode &amp;lt; 8) --&amp;gt; F[&amp;#34;Protokolle rotieren (Letzte 30 Tage behalten)&amp;#34;]
E -- Nein (ExitCode &amp;gt;= 8) --&amp;gt; D
F --&amp;gt; G[&amp;#34;Backup-Skript beenden&amp;#34;]
D --&amp;gt; G
&lt;/pre>
&lt;h3 id="powershell-skript-implementierungsbeispiel-backup-tonasps1">PowerShell-Skript Implementierungsbeispiel (&lt;code>Backup-ToNas.ps1&lt;/code>)
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ErrorActionPreference&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;Stop&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Einstellungen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SourceDir&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;C:\WorkData&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$TargetNasDir&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;\\NAS01\Backup\WorkData&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$LogDir&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;C:\Scripts\Logs&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$DateStr&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-Date&lt;/span> &lt;span class="n">-Format&lt;/span> &lt;span class="s2">&amp;#34;yyyyMMdd&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$LogFile&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Join-Path&lt;/span> &lt;span class="nv">$LogDir&lt;/span> &lt;span class="s2">&amp;#34;Backup_&lt;/span>&lt;span class="nv">$DateStr&lt;/span>&lt;span class="s2">.log&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$RetainDays&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">30&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># 1. Vorabprüfung: Kann auf das NAS zugegriffen werden?&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">-not&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Test-Path&lt;/span> &lt;span class="nv">$TargetNasDir&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="s2">&amp;#34;Auf den NAS-Zielpfad kann nicht zugegriffen werden: &lt;/span>&lt;span class="nv">$TargetNasDir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Backup wird gestartet: &lt;/span>&lt;span class="nv">$SourceDir&lt;/span>&lt;span class="s2"> -&amp;gt; &lt;/span>&lt;span class="nv">$TargetNasDir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># 2. Robocopy ausführen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># /MIR : Spiegeln (Dateien löschen, die in der Quelle nicht mehr vorhanden sind)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># /MT:16 : Multithread-Kopieren mit 16 Threads&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># /NP : Fortschritt (%) nicht ausgeben (um das Protokoll nicht unübersichtlich zu machen)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># /R:2 /W:2 : 2 Wiederholungsversuche bei Fehler, 2 Sekunden Wartezeit&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$roboArgs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$SourceDir&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$TargetNasDir&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/MIR&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/MT:16&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/NP&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/R:2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/W:2&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;/LOG+:&lt;/span>&lt;span class="nv">$LogFile&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Bei Aufruf externer Befehle aus PowerShell heraus ist Start-Process die sicherste Methode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$process&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Start-Process&lt;/span> &lt;span class="n">-FilePath&lt;/span> &lt;span class="s2">&amp;#34;robocopy.exe&amp;#34;&lt;/span> &lt;span class="n">-ArgumentList&lt;/span> &lt;span class="nv">$roboArgs&lt;/span> &lt;span class="n">-Wait&lt;/span> &lt;span class="n">-NoNewWindow&lt;/span> &lt;span class="n">-PassThru&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$exitCode&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ExitCode&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Robocopy Exit-Code-Spezifikation: 0-7 bedeuten Erfolg oder spezifikationsgemäßes Verhalten. 8 und höher sind Fehler&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$exitCode&lt;/span> &lt;span class="o">-ge&lt;/span> &lt;span class="mf">8&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">throw&lt;/span> &lt;span class="s2">&amp;#34;Robocopy mit Fehler beendet. ExitCode: &lt;/span>&lt;span class="nv">$exitCode&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Backup erfolgreich abgeschlossen. ExitCode: &lt;/span>&lt;span class="nv">$exitCode&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># 3. Log-Rotation&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Alte Protokolldateien werden gelöscht (Aufbewahrungsdauer: &lt;/span>&lt;span class="nv">${RetainDays}&lt;/span>&lt;span class="s2"> Tage)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$limitDate&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Get-Date&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="py">AddDays&lt;/span>&lt;span class="p">(-&lt;/span>&lt;span class="nv">$RetainDays&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="nv">$LogDir&lt;/span> &lt;span class="n">-Filter&lt;/span> &lt;span class="s2">&amp;#34;Backup_*.log&amp;#34;&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Where-Object&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">LastWriteTime&lt;/span> &lt;span class="o">-lt&lt;/span> &lt;span class="nv">$limitDate&lt;/span> &lt;span class="p">}&lt;/span> &lt;span class="p">|&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Remove-Item&lt;/span> &lt;span class="n">-Force&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Log-Bereinigung abgeschlossen.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$errorMessage&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;Während des Backup-Vorgangs ist ein Fehler aufgetreten: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Error&lt;/span> &lt;span class="nv">$errorMessage&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># In die eigentliche Fehlerprotokolldatei schreiben&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Add-Content&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Join-Path&lt;/span> &lt;span class="nv">$LogDir&lt;/span> &lt;span class="s2">&amp;#34;Error.log&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="n">-Value&lt;/span> &lt;span class="s2">&amp;#34;[(Get-Date -Format &amp;#39;yyyy-MM-dd HH:mm:ss&amp;#39;)] &lt;/span>&lt;span class="nv">$errorMessage&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Mit Code 1 beenden (ungleich null), um die Aufgabenplanung über den Fehler zu benachrichtigen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exit&lt;/span> &lt;span class="mf">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dieses Skript in Kombination mit der Aufgabenplanung realisiert ein vollautomatisches, tägliches Backup. Insbesondere die Behandlung der Exit-Codes von &lt;code>Robocopy&lt;/code> ist sehr wichtig. Beachten Sie, dass Robocopy auch bei Erfolg eine 1 zurückgeben kann (wenn &amp;ldquo;neue Dateien kopiert wurden&amp;rdquo;) oder eine 2 (wenn &amp;ldquo;zusätzliche Dateien gelöscht wurden&amp;rdquo;), sodass eine einfache Überprüfung wie &lt;code>$LASTEXITCODE -eq 0&lt;/code> nicht korrekt funktionieren würde.&lt;/p>
&lt;hr>
&lt;h2 id="praxisbeispiel-2-überwachung-des-systemereignisprotokolls-und-slack-benachrichtigung-webhook">Praxisbeispiel 2: Überwachung des Systemereignisprotokolls und Slack-Benachrichtigung (Webhook)
&lt;/h2>&lt;p>Bei Windows-Servern oder Workstations für Kreative ist es äußerst wichtig, frühzeitig Festplattenfehler zu erkennen, die Vorboten eines Bluescreens (BSoD) sein könnten, oder Anwendungscrashs (Application Errors) festzustellen.
Hier erstellen wir ein Skript, das Protokolle der Stufen &amp;ldquo;Fehler&amp;rdquo; (Error) und &amp;ldquo;Kritisch&amp;rdquo; (Critical) der letzten Stunde aus den Ereignisprotokollen &lt;code>System&lt;/code> und &lt;code>Application&lt;/code> extrahiert und beim Auffinden eine Benachrichtigung an Slack sendet.&lt;/p>
&lt;h3 id="sequenzdiagramm-des-benachrichtigungsprozesses">Sequenzdiagramm des Benachrichtigungsprozesses
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant S as &amp;#34;Scheduled Task&amp;#34;
participant P as &amp;#34;PowerShell Script&amp;#34;
participant W as &amp;#34;Windows Event Log&amp;#34;
participant Sl as &amp;#34;Slack API&amp;#34;
S-&amp;gt;&amp;gt;P: &amp;#34;Skript auslösen (Jede Stunde)&amp;#34;
P-&amp;gt;&amp;gt;W: &amp;#34;Get-WinEvent (Critical/Error letzte 1h)&amp;#34;
W--&amp;gt;&amp;gt;P: &amp;#34;Log-Objekte zurückgeben&amp;#34;
alt &amp;#34;Fehler gefunden&amp;#34;
P-&amp;gt;&amp;gt;P: &amp;#34;Nachricht formatieren (JSON)&amp;#34;
P-&amp;gt;&amp;gt;Sl: &amp;#34;Invoke-RestMethod (POST webhook_url)&amp;#34;
Sl--&amp;gt;&amp;gt;P: &amp;#34;200 OK&amp;#34;
else &amp;#34;Keine Fehler&amp;#34;
P-&amp;gt;&amp;gt;P: &amp;#34;Ordentlich beenden&amp;#34;
end
&lt;/pre>
&lt;h3 id="powershell-skript-implementierungsbeispiel-monitor-eventlogps1">PowerShell-Skript Implementierungsbeispiel (&lt;code>Monitor-EventLog.ps1&lt;/code>)
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ErrorActionPreference&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;Stop&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Slack Webhook URL (vorab über die Slack Incoming Webhooks Integration beziehen)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SlackWebhookUrl&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;https://hooks.slack.com/services/TXXXXX/BXXXXX/XXXXXXXXXXXXX&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Zeitbereich für die Suche (vergangene 1 Stunde)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$startTime&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Get-Date&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="py">AddHours&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mf">-1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Ereignisprotokolle schnell mithilfe von XPath-Filtern durchsuchen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Level 1: Kritisch (Critical), 2: Fehler (Error)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$xmlFilter&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="sh">@&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh">&amp;lt;QueryList&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;Query Id=&amp;#34;0&amp;#34; Path=&amp;#34;System&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;Select Path=&amp;#34;System&amp;#34;&amp;gt;*[System[(Level=1 or Level=2) and TimeCreated[@SystemTime&amp;amp;gt;=&amp;#39;&lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$startTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ToUniversalTime&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="py">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;o&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="sh">&amp;#39;]]]&amp;lt;/Select&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;/Query&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;Query Id=&amp;#34;1&amp;#34; Path=&amp;#34;Application&amp;#34;&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;Select Path=&amp;#34;Application&amp;#34;&amp;gt;*[System[(Level=1 or Level=2) and TimeCreated[@SystemTime&amp;amp;gt;=&amp;#39;&lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$startTime&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">ToUniversalTime&lt;/span>&lt;span class="p">().&lt;/span>&lt;span class="py">ToString&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;o&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="sh">&amp;#39;]]]&amp;lt;/Select&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh"> &amp;lt;/Query&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh">&amp;lt;/QueryList&amp;gt;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="sh">&amp;#34;@&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Protokolle mit Get-WinEvent abrufen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># -ErrorAction SilentlyContinue ignoriert Fehler, wenn keine Protokolle gefunden werden&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$events&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-WinEvent&lt;/span> &lt;span class="n">-FilterXml&lt;/span> &lt;span class="nv">$xmlFilter&lt;/span> &lt;span class="n">-ErrorAction&lt;/span> &lt;span class="n">SilentlyContinue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$events&lt;/span> &lt;span class="o">-and&lt;/span> &lt;span class="nv">$events&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Count&lt;/span> &lt;span class="o">-gt&lt;/span> &lt;span class="mf">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$eventCount&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$events&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Count&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;In der vergangenen Stunde wurden &lt;/span>&lt;span class="nv">$eventCount&lt;/span>&lt;span class="s2"> Fehler/kritische Protokolle gefunden.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Text für die Benachrichtigung zusammensetzen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$messageBody&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;*Windows System-Alarm* :rotating_light:&lt;/span>&lt;span class="se">`n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$messageBody&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="s2">&amp;#34;In der vergangenen Stunde wurden &lt;/span>&lt;span class="nv">$eventCount&lt;/span>&lt;span class="s2"> Fehler erkannt.&lt;/span>&lt;span class="se">`n`n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Nur Details zu den letzten 3 Einträgen anzeigen (unter Berücksichtigung von Zeichenbeschränkungen usw.)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$events&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">Select-Object&lt;/span> &lt;span class="n">-First&lt;/span> &lt;span class="mf">3&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ForEach-Object&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$messageBody&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="s2">&amp;#34;*&lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">LogName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">* - &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">ProviderName&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2"> (EventID: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Id&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">)&lt;/span>&lt;span class="se">`n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$messageBody&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt; &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Message&lt;/span> &lt;span class="o">-replace&lt;/span> &lt;span class="s1">&amp;#39;`n&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39; &amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="se">`n`n&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$eventCount&lt;/span> &lt;span class="o">-gt&lt;/span> &lt;span class="mf">3&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$messageBody&lt;/span> &lt;span class="p">+=&lt;/span> &lt;span class="s2">&amp;#34;※Es gibt &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$eventCount&lt;/span> &lt;span class="p">-&lt;/span> &lt;span class="mf">3&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2"> weitere Fehler. Bitte überprüfen Sie die Ereignisanzeige (Event Viewer).&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># JSON-Nutzlast (Payload) zum POSTen in Slack erstellen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$payload&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">text&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$messageBody&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">username&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;SystemMonitor&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">icon_emoji&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;:desktop_computer:&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$jsonPayload&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$payload&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ConvertTo-Json&lt;/span> &lt;span class="n">-Depth&lt;/span> &lt;span class="mf">3&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># REST API aufrufen und an Slack senden&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Invoke-RestMethod&lt;/span> &lt;span class="n">-Uri&lt;/span> &lt;span class="nv">$SlackWebhookUrl&lt;/span> &lt;span class="n">-Method&lt;/span> &lt;span class="n">Post&lt;/span> &lt;span class="n">-Body&lt;/span> &lt;span class="nv">$jsonPayload&lt;/span> &lt;span class="n">-ContentType&lt;/span> &lt;span class="s2">&amp;#34;application/json; charset=utf-8&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Benachrichtigung an Slack wurde gesendet.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Keine Fehler oder kritischen Protokolle gefunden. Das System funktioniert normal.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Error&lt;/span> &lt;span class="s2">&amp;#34;Ein Fehler ist im Ereignisprotokoll-Überwachungsskript aufgetreten: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">exit&lt;/span> &lt;span class="mf">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Der technische Kern dieses Skripts ist die Verwendung von &lt;code>Get-WinEvent -FilterXml&lt;/code>. Herkömmliche Cmdlets wie &lt;code>Get-EventLog&lt;/code> oder die Filterung mit &lt;code>Where-Object&lt;/code> über Pipelines sind extrem langsam, da sie alle Ereignisobjekte zuerst in den Arbeitsspeicher laden und dann verarbeiten. Durch die Verwendung von XML-Filtern wird die Filterung auf Seiten des Windows-Ereignisprotokolldienstes durchgeführt. Dies führt zu einer drastischen Leistungssteigerung, bei der die Ausführungszeit meist im Bereich von wenigen Sekunden liegt.&lt;/p>
&lt;hr>
&lt;h2 id="praxisbeispiel-3-batch-aktualisierung-mehrerer-git-repositories-und-build-automatisierung">Praxisbeispiel 3: Batch-Aktualisierung mehrerer Git-Repositories und Build-Automatisierung
&lt;/h2>&lt;p>Für Entwickler ist es sehr mühsam, am frühen Morgen als Erstes mehrere Git-Repositories (z.B. Frontend, Backend, Infrastruktur-Repositories) auf dem eigenen Arbeits-PC mit dem neuesten &lt;code>main&lt;/code>-Branch zu synchronisieren und bei Bedarf die Installation von Paketen (wie &lt;code>npm install&lt;/code>) oder Builds durchzuführen.
Wir erstellen ein Tool, das dies mithilfe eines PowerShell-Skripts auf einmal erledigt.&lt;/p>
&lt;p>Dieses Skript erkennt automatisch alle Git-Repositories unterhalb eines bestimmten übergeordneten Verzeichnisses und führt ein &lt;code>git pull&lt;/code> aus, falls es keine nicht-festgeschriebenen (uncommitted) Änderungen gibt. Wenn neue Änderungen gezogen (gepullt) werden, wird außerdem automatisch ein Build-Befehl ausgeführt.&lt;/p>
&lt;h3 id="skript-zur-automatischen-aktualisierung-mehrerer-repositories-update-gitreposps1">Skript zur automatischen Aktualisierung mehrerer Repositories (&lt;code>Update-GitRepos.ps1&lt;/code>)
&lt;/h3>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;span class="lnt">64
&lt;/span>&lt;span class="lnt">65
&lt;/span>&lt;span class="lnt">66
&lt;/span>&lt;span class="lnt">67
&lt;/span>&lt;span class="lnt">68
&lt;/span>&lt;span class="lnt">69
&lt;/span>&lt;span class="lnt">70
&lt;/span>&lt;span class="lnt">71
&lt;/span>&lt;span class="lnt">72
&lt;/span>&lt;span class="lnt">73
&lt;/span>&lt;span class="lnt">74
&lt;/span>&lt;span class="lnt">75
&lt;/span>&lt;span class="lnt">76
&lt;/span>&lt;span class="lnt">77
&lt;/span>&lt;span class="lnt">78
&lt;/span>&lt;span class="lnt">79
&lt;/span>&lt;span class="lnt">80
&lt;/span>&lt;span class="lnt">81
&lt;/span>&lt;span class="lnt">82
&lt;/span>&lt;span class="lnt">83
&lt;/span>&lt;span class="lnt">84
&lt;/span>&lt;span class="lnt">85
&lt;/span>&lt;span class="lnt">86
&lt;/span>&lt;span class="lnt">87
&lt;/span>&lt;span class="lnt">88
&lt;/span>&lt;span class="lnt">89
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$ErrorActionPreference&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;Stop&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Liste der übergeordneten Verzeichnisse, in denen sich die Repositories befinden&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$TargetDirectories&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">@&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;C:\Dev\FrontendProjects&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;C:\Dev\BackendProjects&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Jedes Verzeichnis durchsuchen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$parentDir&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nv">$TargetDirectories&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">-not&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Test-Path&lt;/span> &lt;span class="nv">$parentDir&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Warning&lt;/span> &lt;span class="s2">&amp;#34;Verzeichnis nicht gefunden: &lt;/span>&lt;span class="nv">$parentDir&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Liste der Unterverzeichnisse abrufen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$subDirs&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="nv">$parentDir&lt;/span> &lt;span class="n">-Directory&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$repoDir&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nv">$subDirs&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$repoPath&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nv">$repoDir&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">FullName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$gitDir&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Join-Path&lt;/span> &lt;span class="nv">$repoPath&lt;/span> &lt;span class="s2">&amp;#34;.git&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Prüfen, ob der Ordner .git existiert (ob es ein Git-Repository ist)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Test-Path&lt;/span> &lt;span class="nv">$gitDir&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;---------------------------------&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Verarbeite Repository: &lt;/span>&lt;span class="nv">$repoPath&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Cyan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Das aktuelle Arbeitsverzeichnis in PowerShell ändern&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Set-Location&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="nv">$repoPath&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">try&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Auf uncommitted Änderungen prüfen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$status&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">git&lt;/span> &lt;span class="n">status&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-porcelain&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$status&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Übersprungen, da nicht-festgeschriebene Änderungen vorhanden sind.&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Yellow&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Den aktuellen Branch (Zweig) ermitteln&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$branch&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">git&lt;/span> &lt;span class="nb">rev-parse&lt;/span> &lt;span class="p">-&lt;/span>&lt;span class="n">-abbrev-ref&lt;/span> &lt;span class="n">HEAD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$branch&lt;/span> &lt;span class="o">-ne&lt;/span> &lt;span class="s2">&amp;#34;main&amp;#34;&lt;/span> &lt;span class="o">-and&lt;/span> &lt;span class="nv">$branch&lt;/span> &lt;span class="o">-ne&lt;/span> &lt;span class="s2">&amp;#34;master&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Übersprungen, da der aktuelle Branch &lt;/span>&lt;span class="nv">$branch&lt;/span>&lt;span class="s2"> ist (nur main/master wird unterstützt).&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Yellow&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Pull ausführen und das Ergebnis in einer Variablen speichern&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Die neuesten Änderungen vom Remote-Repository werden abgerufen (git pull)...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$pullResult&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="n">git&lt;/span> &lt;span class="n">pull&lt;/span> &lt;span class="n">origin&lt;/span> &lt;span class="nv">$branch&lt;/span> &lt;span class="mf">2&lt;/span>&lt;span class="p">&amp;gt;&amp;amp;&lt;/span>&lt;span class="mf">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Auch auf der Konsole ausgeben&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$pullResult&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="nb">ForEach-Object&lt;/span> &lt;span class="p">{&lt;/span> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34; &lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Wenn eine andere Zeichenfolge als &amp;#34;Already up to date.&amp;#34; enthalten ist, wird davon ausgegangen, dass es ein Update gab&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$isUpdated&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">$false&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">foreach&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$line&lt;/span> &lt;span class="k">in&lt;/span> &lt;span class="nv">$pullResult&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$line&lt;/span> &lt;span class="o">-notmatch&lt;/span> &lt;span class="s2">&amp;#34;Already up to date&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$isUpdated&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="vm">$true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$isUpdated&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Repository wurde aktualisiert. Build-Aufgabe wird gestartet...&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Green&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Wenn package.json vorhanden ist, npm install und npm run build ausführen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">Test-Path&lt;/span> &lt;span class="s2">&amp;#34;package.json&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Führe npm install aus...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">npm&lt;/span> &lt;span class="n">install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Führe npm run build aus...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">npm&lt;/span> &lt;span class="n">run&lt;/span> &lt;span class="n">build&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c"># Wenn .sln (Visual Studio Solution) vorhanden ist, msbuild oder dotnet build ausführen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">$slnFiles&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Get-ChildItem&lt;/span> &lt;span class="n">-Filter&lt;/span> &lt;span class="s2">&amp;#34;*.sln&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">$slnFiles&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Count&lt;/span> &lt;span class="o">-gt&lt;/span> &lt;span class="mf">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Erstelle .NET-Anwendung...&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dotnet&lt;/span> &lt;span class="n">build&lt;/span> &lt;span class="nv">$slnFiles&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mf">0&lt;/span>&lt;span class="p">].&lt;/span>&lt;span class="py">FullName&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">catch&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">Write-Error&lt;/span> &lt;span class="s2">&amp;#34;Ein Fehler ist bei der Verarbeitung des Repositories &lt;/span>&lt;span class="nv">$repoPath&lt;/span>&lt;span class="s2"> aufgetreten: &lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nv">$_&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="py">Exception&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">Message&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;---------------------------------&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Write-Host&lt;/span> &lt;span class="s2">&amp;#34;Aktualisierung aller Repositories abgeschlossen.&amp;#34;&lt;/span> &lt;span class="n">-ForegroundColor&lt;/span> &lt;span class="n">Green&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dieses Skript ist so konzipiert, dass auch bei einem Fehler in einem Repository dank &lt;code>try / catch&lt;/code> und der &lt;code>foreach&lt;/code>-Schleife die Verarbeitung der nächsten Repositories problemlos fortgesetzt wird. Darüber hinaus wird die Option &lt;code>git status --porcelain&lt;/code>, die speziell für die Skriptverarbeitung gedacht ist, genutzt, um den &amp;ldquo;Sauberkeitszustand&amp;rdquo; (Cleanliness) des Arbeitsbaums (Working Tree) zuverlässig zu bestimmen. Wenn Sie dieses Skript im Autostart-Ordner ablegen oder es im Task-Scheduler beim Anmelden des Benutzers registrieren, sind all Ihre Entwicklungsumgebungen auf dem neuesten Stand, während Sie sich nach dem Booten des PCs einen Kaffee holen.&lt;/p>
&lt;hr>
&lt;h2 id="hinweise-zum-betrieb-und-fortgeschrittene-techniken">Hinweise zum Betrieb und fortgeschrittene Techniken
&lt;/h2>&lt;p>Beim langfristigen Betrieb von Automatisierungsskripten mit PowerShell gibt es einige bewährte Methoden (Best Practices), die Sie beachten sollten.&lt;/p>
&lt;h3 id="1-sichere-verwaltung-von-zugangsdaten">1. Sichere Verwaltung von Zugangsdaten
&lt;/h3>&lt;p>Passwörter oder API-Schlüssel (z. B. Slack Webhook URL, Datenbank-Verbindungszeichenfolgen) als Klartext im Skript fest zu codieren (Hardcoding), stellt ein großes Sicherheitsrisiko dar. PowerShell verfügt über Funktionen wie &lt;code>Export-Clixml&lt;/code> und &lt;code>ConvertFrom-SecureString&lt;/code>, um Authentifizierungsinformationen verschlüsselt zu speichern.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Nur beim ersten Mal manuell ausführen (Ein Dialogfeld zur Passworteingabe wird angezeigt)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Get-Credential | Export-Clixml -Path &amp;#34;C:\Scripts\Creds\admin.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Einlesen innerhalb des Automatisierungsskripts&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$cred&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nb">Import-Clixml&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;C:\Scripts\Creds\admin.xml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Nutzen Sie $cred, um z. B. eine Verbindung zum Remote-Server herzustellen&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dies ermöglicht einen sicheren Umgang mit Authentifizierungsinformationen, die nur im Profil des Benutzers entschlüsselt werden können, der das Skript ausführt.&lt;/p>
&lt;h3 id="2-vollständige-aufzeichnung-von-ausführungsprotokollen-mit-transcript">2. Vollständige Aufzeichnung von Ausführungsprotokollen mit Transcript
&lt;/h3>&lt;p>In den vorherigen Beispielen wurden Protokolle einzeln mit Befehlen wie &lt;code>Add-Content&lt;/code> ausgegeben. PowerShell verfügt jedoch über eine Transcript-Funktion, die automatisch alle auf dem Bildschirm ausgegebenen Informationen (einschließlich Fehlermeldungen und Standardausgabe) in eine Datei schreibt.&lt;/p>
&lt;p>Fügen Sie einfach Folgendes am Anfang und am Ende des Skripts ein, um ein robustes Audit-Protokoll (Audit Log) zu erstellen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Start-Transcript&lt;/span> &lt;span class="n">-Path&lt;/span> &lt;span class="s2">&amp;#34;C:\Scripts\Logs\Execution_&lt;/span>&lt;span class="p">$(&lt;/span>&lt;span class="nb">Get-Date&lt;/span> &lt;span class="n">-Format&lt;/span> &lt;span class="s1">&amp;#39;yyyyMMdd_HHmmss&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s2">.log&amp;#34;&lt;/span> &lt;span class="n">-Append&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># (Hier steht die eigentliche Skriptlogik)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Stop-Transcript&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="3-mathematischer-ansatz-zur-überwachung-und-anomalieerkennung-math">3. Mathematischer Ansatz zur Überwachung und Anomalieerkennung (Math)
&lt;/h3>&lt;p>Bei groß angelegten Automatisierungen ist es effektiv, nicht nur Fehler zu erkennen, sondern auch &amp;ldquo;Ungewöhnliches&amp;rdquo; statistisch zu erfassen. Wenn beispielsweise die tägliche Backup-Zeit extrem vom Durchschnitt abweicht, könnte dies ein Vorbote für ein Netzwerkproblem oder einen Festplattenausfall sein.&lt;/p>
&lt;p>Angenommen, die täglichen Backup-Zeiten sind $x_1, x_2, \dots, x_n$. Dann lassen sich der Stichprobenmittelwert (Sample Mean) $\mu$ und die Standardabweichung $\sigma$ wie folgt berechnen:&lt;/p>
$$ \mu = \frac{1}{n} \sum_{i=1}^n x_i $$$$ \sigma = \sqrt{ \frac{1}{n-1} \sum_{i=1}^n (x_i - \mu)^2 } $$&lt;p>Wenn die heutige Ausführungszeit $x_{today}$ den Wert von $\mu + 3\sigma$ überschreitet (Drei-Sigma-Regel), kann das System dies als &amp;ldquo;statistische Anomalie&amp;rdquo; betrachten und eine Logik implementieren, die eine Warnmeldung ausgibt. Mit dem PowerShell-Cmdlet &lt;code>Measure-Object&lt;/code> lassen sich derartige statistische Vorgänge in nur wenigen Zeilen umsetzen.&lt;/p>
&lt;h2 id="zusammenfassung">Zusammenfassung
&lt;/h2>&lt;p>In diesem Artikel haben wir anhand von Praxisbeispielen erläutert, wie Sie Routineaufgaben in Windows-Umgebungen mithilfe von PowerShell vollständig automatisieren können.
Beginnend mit grundlegenden Schritten wie der Verwaltung von Ausführungsrichtlinien und der Modularisierung haben wir Skripte vorgestellt, die in der Praxis direkt einsetzbar sind, wie z.B. Backups und Log-Rotation, Überwachung von Ereignisprotokollen mit Slack-Benachrichtigungen und den automatisierten Build für mehrere Git-Repositories.&lt;/p>
&lt;p>PowerShell ist äußerst tiefgreifend; es ist eine leistungsstarke Automatisierungs-Engine, die, obwohl sie ein Kommandozeilenwerkzeug ist, auf fast alle Funktionen von .NET zugreifen kann. Basierend auf den hier vorgestellten Skripten können Sie Pfade und Logiken an Ihre eigene Arbeitsumgebung anpassen und so kreative Zeit gewinnen, befreit von mühsamer manueller Arbeit.&lt;/p>
&lt;p>Der Erfolg der Automatisierung hängt davon ab, &amp;ldquo;mit kleinen Skripten anzufangen und nach und nach deren Robustheit wie Fehlerbehandlung und Protokollierung zu verbessern&amp;rdquo;. Wie wäre es, Ihre Reise in die PowerShell-Automatisierung zu starten, indem Sie einfach zunächst einen einzigen Ordner auf Ihrem eigenen PC sichern?&lt;/p></description></item></channel></rss>