Skip to the main content.

ScriptRunner Blog

Erste Schritte mit PowerShell-Funktionen

Table of contents

Post Featured Image

PowerShell-Funktionen sind wichtige Tools, mit denen Du Code zur Wiederverwendung kapseln kannst, wodurch Ihre Skripts effizienter und verwaltbarer werden.

Einführung

PowerShell ist eine großartige Sprache. Der Einstieg in die Sprache ist für einen Anfänger wirklich einfach. Die Sprachstruktur selbst, die berühmte "Verb-Nomen"-Struktur der Cmdlets (und vieles mehr) war ein echter Wegbereiter. Sie ermöglichte es so vielen (mich eingeschlossen), eine Reise in die Programmierung zu beginnen, die nie endet.

Um PowerShell (oder eigentlich jede andere Programmiersprache) zu lernen, ist es eine gute Idee, mit etwas Grundlegendem zu beginnen. Mit etwas sehr Einfachem, und darauf aufbauend. Am besten ist es, ein aktuelles Problem zu finden, und zu versuchen, es zu lösen.

Du kannst damit beginnen, Codezeilen, die bei dir in einer PowerShell-Konsole funktioniert haben, in eine Datei einzufügen. Wenn du die Konsole immer wieder verwendest, wirst Du schnell feststellen, welche Dinge behoben oder verbessert werden müssen. Je mehr du es verwenden, desto mehr Dinge wirst du optimieren und verbessern.

Aber sehr schnell wird aus den ursprünglich separaten Codezeilen etwas Stabileres, weniger fehleranfälliges, benutzerfreundlicheres und vor allem: Einfach wiederzuverwenden!

Auf das Thema Wiederverwendbarkeit möchte ich heute die Aufmerksamkeit lenken, denn in diesem Artikel dreht sich alles genau darum.

Wir werden in die Welt der Funktionen eintauchen, indem wir zeigen, wie wir eine Protokollierungsfunktion von einer einzigen Codezeile zu einer vollwertigen Funktion aufbauen können. Neben der Syntax werden wir auch den Gedankenprozess zur Erstellung von PowerShell-Funktionen durchgehen.

 

Wo soll man anfangen?

Die Protokollierung kann an verschiedenen Stellen erfolgen, aber heute werden wir uns nur auf die Protokollierung in eine Datei konzentrieren.

Auch wenn dies sehr einleuchtend erscheint, sollten wir zunächst definieren , was eine Protokolldatei eigentlich ist.

Eine Protokolldatei ist eine Textdatei, die Ereignisse, Prozesse oder Meldungen aufzeichnet
die von Softwareanwendungen, Betriebssystemen oder Geräten erzeugt werden
zur Nachverfolgung und Fehlersuche.

Um Protokolle zu schreiben, müssen wir Text in eine Datei schreiben. PowerShell macht dies sehr einfach, indem es das Cmdlet " Add-Content" bereitstellt, um genau dies zu tun. Mit der folgenden Zeile können wir das Wort "woop" in eine Datei namens C:\temp\plop.txt schreiben


Add-Content -Path "C:\Temp\Plop.txt" -Value "woop"

Dabei wird davon ausgegangen, dass der Ordner C:\Temp existiert.

Wir können leicht erkennen, dass diese Codezeile nicht für die Wiederverwendbarkeit geeignet ist, da sie immer wieder denselben Wert woop in dieselbe Datei C:\Temp\Plop.txt schreiben würde.

Beheben wir dies, indem wir den Pfad und die Nachricht leicht änderbar machen.


$Path = "C:\Temp\Plop.txt"
$Message = "woop"
Add-Content -Path $Path -Value $Message

In der obigen Zeile sind wir von fest kodierten Werten zur Verwendung von Variablen übergegangen. Aber das ermöglicht immer noch keine wirkliche Wiederverwendbarkeit.
Idealerweise würden wir gerne Nachrichten aus unseren Skripten in eine Datei schreiben können. Wenn wir also dieses Skript für die Nacht einplanen, wissen wir am Morgen, ob es erfolgreich gelaufen ist oder nicht.

An dieser Stelle kommen Funktionen ins Spiel. Da wir mehr als eine Nachricht in ein einziges Skript schreiben wollen, und um zu vermeiden, dass wir diese drei Zeilen immer wieder kopieren und einfügen müssen, werden wir eine Funktion verwenden.

Kurz gesagt, eine Funktion ist ein Abschnitt, der durch seinen Namen eindeutig identifiziert werden kann. Er kapselt einen Codeschnipsel ein, der beim Aufruf ausgeführt wird.

Auf diese Weise können die Kerninhalte an einem zentralen Ort aufbewahrt werden, und es macht das Ändern von Dingen wirklich einfach, weil es in der Funktion zentralisiert ist. Du kannst dort eine Änderung vornehmen, die dann überall, wo die Funktion aufgerufen wird, übernommen wird.


function Write-Log ($Path,$Message) {
Add-Content -Path $Message
}

Lass uns das schnell aufschlüsseln, damit jeder mitreden kann. Ich bin ein großer Anhänger des Sprichworts "Ein Bild sagt mehr als tausend Worte", also schauen wir uns dies anhand eines Bildes genauer an:

anatomy of a simple function


Function:
ist das Schlüsselwort, das PowerShell mitteilt, dass das folgende Konstrukt eine "wiederverwendbare Funktion" ist. Es handelt sich um ein automatisches Schlüsselwort, das Teil der PowerShell-Sprache ist.

Write-Log: Dies ist der Name unserer Funktion. Mit anderen Worten, es ist das Wort, mit dem wir unseren Code aufrufen werden.

Wir haben ein Klammerpaar, das zwei Variablen enthält: "$Path" und "$Message". Diese werden als Parameter bezeichnet.

Beachten Sie, dass sie durch ein Komma getrennt sind.

Als Nächstes folgt ein Paar geschweifter Klammern '{}', die den Hauptteil unserer Funktion enthalten, der jedes Mal ausgeführt wird , wenn wir unsere Funktion über den Funktionsnamen aufrufen. Der Anfang der Funktion wird durch die erste öffnende geschweifte Klammer '{' definiert, und das Ende der Funktion endet mit der letzten schließenden geschweiften Klammer '}'.

Tipp: Wusstes du, dass alles, was zwischen geschweiften Klammern '{ }' steht, ein ScriptBlock genannt wird?

Alles, was zwischen der ersten öffnenden geschweiften Klammer und der letzten schließenden geschweiften Klammer steht, ist der Inhalt unserer Funktion. Dies ist der Kern unserer Funktion. Hier passiert die ganze Magie.

In unserem Beispiel ist es die folgende Zeile:


Add-Content -Path $Path -Value $Message

 

Verwenden der ersten Funktion:
Einbindung der Protokollierungsfunktion in ein Skript

Nehmen wir an, du habst einen Kollegen, der ein Skript geschrieben hat, um einen Server nach einem Backup von temporären Dateien zu befreien. Du bist nicht 100%ig sicher, dass das Skript tatsächlich funktioniert. Du bist dir noch nicht mal sicher, ob es überhaupt startet!

Das Skript heißt "nightScript.ps1" und sieht wie folgt aus:


#nightScript.ps1
$AllFilesToRemove = Get-ChildItem -Path "C:\Temp\Backup\"
-Recurse foreach($file in $AllFilesToRemove) {
Remove-Item -Path $file.FullName -Force
}
Restart-Service -Name "BackupService"

Wir sehen hier, dass nichts auf dem Bildschirm ausgegeben wird und dass auch keine Informationen auf die Festplatte geschrieben werden, um zu verfolgen, was das Skript tatsächlich getan oder nicht getan hat.

Dies ist die perfekte Gelegenheit, um eine erste (grundlegende) Protokollierungsfunktion zu verwenden.

Um eine Funktion verwenden zu können, muss sie vom Skript gelesen werden. Technisch gesehen kann die Funktion an einer beliebigen Stelle in der Skriptdatei stehen, aber es empfiehlt sich, sie ganz am Anfang des Skripts zu platzieren, da sie dann leichter zu lesen ist.

Wenn du ein PowerShell-Skript ausführst, sucht die PowerShell-Engine als Erstes nach vorhandenen Funktionen im Skript und lädt sie in den Speicher. Wenn du eine Funktion änderst, muss die Funktion neu geladen werden, damit PowerShell die neueste Version verwenden kann.

Info: Das Laden der Funktion bedeutet, dass das Skript erneut ausgeführt wird.

Bereiten wir unsere Testumgebung vor und verbessern wir das Skript ein wenig, indem wir eine Fehlerbehandlung hinzufügen.

Um dies zu simulieren, habe ich eine Funktion namens "New-FakeFile" verwendet, die ich für psconfEU 2020 geschrieben habe. Sie ist recht praktisch, da sie es ermöglicht, auf einfache Weise gefälschte Dateien mit aussagekräftigen Namen zu erstellen, die die zu sichernden Dateien repräsentieren. 

Ich habe den folgenden Ausschnitt verwendet, um 10 Dateien zu erstellen:


Install-module FakeFile
New-FakeFile -NumberOfFiles 10 -TotalSize 10MB -FolderPath C:\Temp\backup

Aktualisieren wir nun das Skript mit unserer neuen Protokollierungsfunktion und machen wir es etwas robuster, indem wir eine grundlegende Fehlerbehandlung hinzufügen.


#nightScript_v2.ps1
Function Write-Log ($Message,$Path) {
Add-Content -Path $Path -Value $Message
}
try{
$LogFilePath = $MyInvocation.MyCommand.Source.Replace(".ps1",".log")
Write-log -Message "Starting NightScript" -Path $LogFilePath
$AllFilesToRemove = Get-ChildItem -Path "C:\Temp\Backup\" -Recurse
foreach($file in $AllFilesToRemove)
{
#Sie können gefälschte Dateien hinzufügen:
#install-module FakeFile
#New-FakeFile -NumberOfFiles 10 -TotalSize 10MB -FolderPath C:\Temp\backup
Write-log -Message "Removing file: $($File.FullName)" -Path $LogFilePath
Remove-Item -Path $file.FullName -Force
}
Write-log -Message "Restarting service 'BackUpService'" -Path $LogFilePath
Restart-Service -Name "BackupService" -ErrorAction Stop
}catch{
write-Log -Message "Error Occured: $_" -Path $LogFilePath
}finally{
Write-log -Message "End of script" -Path $LogFilePath
}

Beachte, dass ich alles in einen try catch finally-Block verschoben habe. Damit werden zwei wichtige Dinge erreicht:

  • Zum einen wird die Fehlerbehandlung hinzugefügt. Wenn irgendwo im Catch-Block ein Fehler auftritt, wird die Ausführung des Codes automatisch angehalten und in den Catch-Block verschoben, der dann ausgeführt wird. In diesem Fall schreiben wir einfach eine Nachricht, die besagt, dass ein Fehler aufgetreten ist.
  • Der zweite Vorteil ist, dass unser Skript dadurch eine gewisse Struktur erhält. Der Hauptcode kommt in den Try-Block, alle auftretenden Fehler werden in den Catch-Block geschrieben, wobei dieselbe Codezeile verwendet wird, und wir schreiben eine abschließende Meldung, unabhängig davon, ob das Skript fehlgeschlagen ist oder nicht.

Wenn wir das Skript so ausführen, wie es ist, wird eine neue Protokolldatei mit dem Namen "NightScript_v2.log" erstellt, und zwar genau dort, wo sich unser Skript befindet, mit der Ausführung unseres Skripts. Sie wird etwa so aussehen:

screenshot of a log file

Wenn wir die Funktion nun mehrere Male hintereinander ausführen (und damit simulieren, dass das Skript über einen Zeitraum von mehreren Tagen, z. B. über das Wochenende, ausgeführt wird), werden wir sehen, dass unsere Protokolldatei mit weiteren Informationen gefüllt wird. Wir können sehen, dass am Ende unseres Skripts ein Fehler aufgetreten ist, dank unserer 'Error Occurred:' write-log message.

Wir können auch sehen, dass es schwierig ist, zu erkennen, wann die einzelnen Aufgaben stattgefunden haben. Es ist auch etwas schwierig zu erkennen, ob ein Fehler im Skript aufgetreten ist oder nicht, wie Sie aus dem Protokollausschnitt unten ersehen können.

Was wir sehen können, ist, dass das Skript mehrmals gestartet und gestoppt wurde, dank unserer Meldungen "Starting NightScript" und "End of Script".

Ich persönlich bevorzuge es, in der Protokolldatei immer anzugeben, wann das Skript startet und stoppt. Eine Sache, die fehlt, ist die Zeit, zu der das Skript gestartet/gestoppt wurde. Das wollen wir als nächstes beheben.

 

Ein Wort zu den Konventionen

In einer Protokolldatei können wir herausfinden , was passiert ist , während ein Skript um 3 Uhr morgens lief. Wir müssen in der Lage sein, die Informationen schnell zu finden und genaue Informationen daraus zu erhalten. Wenn wir das Skript erneut ausführen müssen, um zu sehen, was tatsächlich auf dem System passiert ist, bedeutet das, dass unsere Protokollfunktion (oder die Dinge, die wir tatsächlich protokollieren) nicht hilfreich genug sind, um schnell herauszufinden, was wann passiert ist.

Deshalb ist es wichtig, dass eine Protokollierungsfunktion die folgenden 3 Dinge beachtet:

  • Konsistenz
  • Standardisierung
  • Klare Konventionen

Diese 3 Regeln lassen sich sehr gut auf jede Funktion anwenden, die Sie tatsächlich schreiben.

Wir wollen, dass unsere Funktion immer eine Logmeldung schreibt. Das heißt, auch wenn sie fehlschlägt. Die Meldung muss standardisiert sein. Eine Aktion pro Zeile, einschließlich Fehlern, und die Meldungen sollten einen Zeitstempel haben, damit man zurückverfolgen kann, wann die Aufgabe tatsächlich passiert ist.

Und wir wollen klare (und einfache!) Konventionen, auf denen wir aufbauen können.

Jede Zeile sollte die gleiche Struktur haben, die in 3 Hauptteile unterteilt ist.

  • 1. Zeitstempel
    Wann ist diese Meldung/Fehler aufgetreten?
    Wir können dies mit dem folgenden Schnipsel herausfinden: get-date -uformat '%Y%m%d-%T'.
  • 2. Schweregrad
    Wie hoch war der Schweregrad der Meldung, der sein kann:
    Dies ist eine vordefinierte Zeichenfolge, die einen der folgenden Werte annehmen kann: Info,Error,Ok
  • 3. Nachricht
    Die Nachricht, die wir zurücksenden möchten.

Um die Ausgabe leichter lesbar zu machen, fügen wir jeder Meldung, die die Log-Funktion ausgibt, einen Zeitstempel und den Schweregrad hinzu.


function Write-Log ($Message,$Path,$Severity = "INFO") {
$TimeStamp = get-date -uformat '%Y%m%d-%T'
$FullMessage = "$TimeStamp;$Severity;$Message"
Add-Content -Path $Path -Value $FullMessage
}

Der Code ist ziemlich einfach, aber beachte das Element '$Severity = "Info", das als Parameter hinzugefügt wurde. Der Parameter hat einen Standardwert von "INFO". Das heißt, wenn der Parameter nicht verwendet wird, wird der Wert "INFO" verwendet. Es steht dem Benutzer frei, diesen Wert jederzeit zu überschreiben und einen beliebigen Wert zu verwenden.

Wie in Zeile 28, wo wir jetzt den Schalter "severity" verwenden, um anzuzeigen, dass ein Fehler aufgetreten ist:


write-Log -Message "Error Occured: $_" -Path $LogFilePath -Severity "ERROR"

Ich habe alle Änderungen in einer neuen Datei namens "NightScript_v3.ps1" gespeichert. Wenn wir das Skript ausführen, sehen wir folgendes:

screenshot of a script called NightScript_v3.ps1

Wir können sofort sehen, wann das Skript gestartet und beendet wurde und ob ein Fehler aufgetreten ist.

Ein weiterer Vorteil ist, dass wir das ';' als Trennzeichen verwendet haben, so dass es in Zukunft sehr einfach sein wird, diese Protokolldatei zu analysieren!

 

Bonus-Funktion: Provider

Wussten Sie, dass alle Funktionen, die Ihnen derzeit zur Verfügung stehen, über die Funktion: provider zugänglich sind?

Get-ChildItem-Funktion: 

Diese Funktion listet alle vorhandenen Funktionen auf, die in der aktuellen Sitzung geladen sind.

Hier sehen wir einige der Funktionen, die derzeit in meiner Sitzung geladen sind. Wir können sehen, dass einige Funktionen von PSHTL zusammen mit unserer Write-Log-Funktion derzeit geladen und in meiner Sitzung verfügbar sind.

screenshot Get-ChildItem function

Um nur die Informationen unserer Write-Log-Funktion zu erhalten, verwenden wir:

Get-ChildItem function:write-log 

screenshot Get-ChildItem function:write-log

Sie gibt die Daten der Funktion write-log zurück (wenn diese Funktion mindestens einmal in Ihre Sitzung geladen wurde).

Nicht sehr interessant, wirst Du vielleicht sagen.

Aber PowerShell verbirgt eine Menge Eigenschaften vor uns. Man kann den folgenden Befehl verwenden, um alle gewünschten Eigenschaften anzuzeigen:

Get-ChildItem function:write-log | select -Property *

Dieser Befehl gibt auf meinem Computer Folgendes zurück:

Es gibt eine ganze Reihe von Eigenschaften, aber eine, die wirklich hervorstechen sollte, ist die Eigenschaft Definition.

(Get-ChildItem function:write-log).Definition

Sie gibt die Definition (oder den Inhalt) Ihrer Funktion zurück, was besonders bei der Entwicklung Ihrer Funktion nützlich sein kann.

Ich lade Dich ein, es auszuprobieren. Schreiben deine Funktion, lade sie und liste sie auf mit:

(Get-ChildItem function:write-log).Definition

Aktualisiere dann die Funktion, lade sie nicht und versuche dasselbe Snippet noch einmal. Du wirst sehen, dass immer noch die alte Version der Definition verwendet wird.

Du musst die Funktion neu laden (erneut aufrufen), um die neueste Version in den Speicher zu bekommen und sie zu verwenden.

All dies soll verdeutlichen, dass dies ein häufiger Fehler ist, der passieren kann: Du aktualisierst eine Funktion und siehst nicht, dass die Änderungen wirksam werden. Das kann einen in den Wahnsinn treiben. Ich weiß, dass mir das passiert ist, als ich vor 15 Jahren mit dem Schreiben meiner ersten PowerShell-Skripts begonnen habe! (Um ganz ehrlich zu sein, passiert das auch heute noch ab und zu).

Wenn das passiert, stelle sicher, dass die Definition korrekt ist, und wenn nicht, dass die Funktion vom richtigen Speicherort laden ist.

 

Letzter Hinweis

Zusammenfassend lässt sich sagen, dass wir in diesem Artikel gelernt haben, was eine einfache Funktion ist, wie sie funktioniert und wann wir unseren Code umgestalten sollten, um sie zu verwenden.

Wir haben auch gesehen, dass wir Parameter an eine Funktion übergeben und sogar einen Standardwert für einen bestimmten Parameter festlegen können, wenn er nicht aufgerufen wird. Wir haben auch gelernt, dass eine Funktion nur in der Sitzung existiert, in der sie aufgerufen wird, und dass sie leicht überschrieben/aktualisiert werden kann.

Im nächsten Artikel werden wir die Dinge etwas beschleunigen, indem wir uns mit fortgeschrittenen Funktionen befassen und unsere Protokollierung durch Hinzufügen einer Parametervalidierung noch weiter verbessern.

 

Good2know

Ihr ultimativer PowerShell-Spickzettel

Schöpfen Sie das volle Potenzial von PowerShell mit unserem praktischen Poster aus. Egal, ob Sie ein Anfänger oder ein erfahrener Profi sind, dieser Spickzettel ist so konzipiert, dass er Ihnen die wichtigsten und am häufigsten verwendeten Cmdlets liefert.

Das Poster ist als Download und in Papierform erhältlich.

PowerShell Poster 2023

Holen Sie sich Ihr Poster hier!

 

 

Verwandte Links

 

Zusammenhängende Posts

12 min read

Erste Schritte mit PowerShell-Funktionen

PowerShell-Funktionen sind wichtige Tools, mit denen Du Code zur Wiederverwendung kapseln kannst, wodurch Ihre Skripts...

6 min read

Neue ScriptRunner Version verbessert die IT-Automatisierung in Unternehmen durch mehr Sicherheit, Transparenz und Effizienz

Die neueste ScriptRunner Version bringt drei interessante Neuerungen, die die IT-Automatisierung in Unternehmen...

14 min read

Der Schlüssel zu produktiver Softwareverwaltung: winget & PowerShell

Steigere die IT-Effizienz mit Winget und PowerShell! Lies, wie du Installationen, Updates und die Verwaltung von...

Über den Autor: