PowerShell im Serverbetrieb

Ein eigenes PowerShell-Modul bauen, das im ganzen Team funktioniert

Der technische Teil ist an einem Vormittag erledigt. Der Unterschied zwischen einem Modul, das benutzt wird, und einer Datei, die jemand kopiert, liegt in den Namen, den Parametern und der Hilfe.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Das Skript funktioniert, und trotzdem benutzt es niemand außer dir

Der übliche Ausgangspunkt sieht so aus: Es gibt eine Datei, die eine Aufgabe zuverlässig erledigt, und sie liegt in einem Ordner, dessen Pfad drei Leute kennen. Oben stehen ein paar Variablen, die vor jedem Lauf angepasst werden müssen, weiter unten ein Servername, der einmal für einen Test eingetragen und nie wieder geändert wurde. Wer die Datei benutzen will, kopiert sie sich, ändert die Werte und hat damit die zweite Fassung erzeugt. Nach einem Jahr existieren sechs Fassungen, und keine davon ist erkennbar die richtige.

Der zweite Punkt ist die Bedienung. Ein Skript wird ausgeführt, eine Funktion wird aufgerufen. Das klingt nach einem Detail, ist aber der Unterschied zwischen etwas, das man in eine Pipeline stecken kann, und etwas, das man nur starten kann. Solange die Logik in einer Skriptdatei ohne Parameter steckt, lässt sie sich nicht mit anderen Befehlen kombinieren, nicht mit -WhatIf gefahrlos ausprobieren und nicht in ein größeres Werkzeug einbauen, ohne den Inhalt zu kopieren.

Und dann ist da die Frage, die im Betrieb regelmäßig aufkommt und für die es in einer losen Skriptsammlung keine Antwort gibt: Welche Fassung läuft eigentlich gerade auf welchem Server, und was hat sich seit dem letzten Mal geändert? Ein Modul beantwortet diese Frage mit einer Versionsnummer im Manifest und mit einer einzigen Stelle, an der es liegt. Das ist der eigentliche Gewinn, und er hat mit PowerShell weniger zu tun als mit Ordnung.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Schritt für Schritt

Der Ablauf in sechs Schritten

Jeder Schritt endet mit einem Zustand, in dem das Werkzeug benutzbar ist. Das ist Absicht: Ihr könnt nach jedem Schritt aufhören, etwas anderes dazwischenschieben und später weitermachen, ohne einen halbfertigen Umbau zu verwalten.

  1. 1

    Aus dem Skript benannte Funktionen schneiden

    Geh das vorhandene Skript einmal durch und markier die Stellen, an denen es etwas Abgeschlossenes tut: einen Wert holen, etwas prüfen, etwas ändern, einen Bericht ausgeben. Aus jeder dieser Stellen wird eine Funktion mit einem Namen aus Verb und Substantiv. Das Verb nimmst du aus der Liste, die Get-Verb ausgibt, denn PowerShell warnt beim Import eines Moduls, wenn ein Befehl ein Verb benutzt, das nicht dazugehört. Halt dich an die vorgegebene Form, also Get und nicht Gets oder Getting, und vermeide Synonyme wie Delete für Remove oder Create für New. Ein Unterschied lohnt die Aufmerksamkeit besonders: New legt etwas Neues an, Add hängt etwas an etwas Vorhandenes an, Invoke führt etwas aus und wartet darauf, Start stößt etwas an und wartet nicht.

    Geschafft, wenn: Das Skript besteht aus benannten Funktionen und einem kurzen Aufrufteil am Ende, es läuft unverändert weiter.

  2. 2

    Aus Variablen im Kopf werden Parameter mit Typ und Prüfung

    Jetzt wandert alles, was vor einem Lauf angepasst wurde, in einen param-Block. Gib jedem Parameter einen Typ, denn eine als [datetime] deklarierte Angabe nimmt dir die Umwandlung ab und lehnt Unsinn ab, bevor die Funktion überhaupt anläuft. Setz [CmdletBinding()] über den param-Block, damit die Funktion die gemeinsamen Parameter bekommt und Write-Verbose tatsächlich etwas ausgibt, sobald jemand -Verbose angibt. Was zwingend gebraucht wird, bekommt [Parameter(Mandatory)], was aus der Pipeline kommen soll, bekommt ValueFromPipeline. Für alles, was etwas verändert, kommt zusätzlich die Unterstützung für ShouldProcess dazu, weil damit -WhatIf und -Confirm funktionieren.

    Geschafft, wenn: Die Funktionen lassen sich mit Argumenten aufrufen, in eine Pipeline stecken und mit -WhatIf gefahrlos ausprobieren.

  3. 3

    Hilfe und Fehlerbehandlung ergänzen

    Über jede öffentliche Funktion kommt ein Kommentarblock mit .SYNOPSIS, .DESCRIPTION, einem .PARAMETER je Parameter und mindestens zwei .EXAMPLE. PowerShell liest diesen Block und gibt ihn bei Get-Help aus, und ein Beispiel mit echten Werten ist im Alltag mehr wert als drei Absätze Beschreibung. Parallel dazu gehört die Fehlerbehandlung an ihren Platz: Schreib Zwischenschritte mit Write-Verbose statt mit Write-Host, weil sich Write-Verbose abschalten und weiterleiten lässt. Fang Fehler dort ab, wo du sie beantworten kannst, und lass sie sonst durch. Wenn du einen Fehler eines eingebauten Cmdlets abfangen willst, brauchst du -ErrorAction Stop, sonst läuft der Ablauf trotz try und catch weiter.

    Geschafft, wenn: Get-Help liefert zu jeder Funktion eine brauchbare Beschreibung, und Fehler kommen dort an, wo jemand sie sieht.

  4. 4

    Die Funktionen zu einem Modul zusammenfassen

    Speicher die Datei jetzt mit der Endung .psm1 und leg sie in einen Ordner, der genauso heißt: Aus CorpServerTools.psm1 wird ein Ordner CorpServerTools. Diese Namensgleichheit ist keine Empfehlung, sondern die Regel, nach der PowerShell das Modul findet. Der Ordner gehört in einen der Pfade, die in $env:PSModulePath stehen. Ist er dort, brauchst du seit PowerShell 3.0 kein Import-Module mehr, denn das Modul wird geladen, sobald jemand eine seiner Funktionen aufruft. Am Ende der Datei zählst du mit Export-ModuleMember auf, welche Funktionen öffentlich sein sollen. Was du nicht aufzählst, bleibt intern, und genau das brauchst du für Hilfsfunktionen, die niemanden außerhalb interessieren.

    Geschafft, wenn: Die Funktionen stehen nach dem Öffnen einer neuen Sitzung zur Verfügung, ohne dass jemand eine Datei einbinden muss.

  5. 5

    Ein Manifest anlegen und die Abhängigkeiten benennen

    New-ModuleManifest erzeugt die .psd1 mit allen möglichen Angaben, von denen du zunächst nur wenige brauchst. Wichtig sind RootModule mit dem Verweis auf die .psm1, ModuleVersion, eine Beschreibung, der Autor und FunctionsToExport mit der ausdrücklichen Liste statt eines Platzhalters. Dazu kommen RequiredModules für Module, ohne die eures nicht läuft, und PowerShellVersion, wenn ihr Sprachmittel benutzt, die es in älteren Fassungen nicht gibt. Ab hier ist der Ordner ein Paket mit einer Versionsnummer, und die Frage, welcher Stand irgendwo liegt, ist mit Get-Module beantwortbar. Denk daran, dass eine spätere Signatur nicht nur die .psm1, sondern auch die .psd1 erfasst, denn PowerShell prüft beide.

    Geschafft, wenn: Das Modul hat eine Version, eine benannte Oberfläche und dokumentierte Abhängigkeiten.

  6. 6

    Verteilen, testen und weiterpflegen

    Zum Schluss legt ihr fest, wo das Modul liegt und wie es auf die Server kommt. Ein internes Repository, aus dem mit Install-Module bezogen wird, ist der sauberste Weg, weil damit auch das Aktualisieren geregelt ist. Eine verteilte Freigabe im Modulpfad tut es für den Anfang ebenfalls, solange es wirklich nur eine gibt. Ergänzt Tests mit Pester für die Funktionen, an denen etwas hängt, und lasst PSScriptAnalyzer über den Code laufen, weil er genau die Kleinigkeiten findet, die später jemanden aufhalten. Und schreibt bei jeder Änderung die Version hoch, sonst könnt ihr am Manifest nicht mehr ablesen, was auf einem Server steht.

    Geschafft, wenn: Das Modul kommt aus einer Quelle auf alle Server, hat eine nachvollziehbare Version und ist geprüft.

Aus dem Skript wird in fünf Schnitten ein Modul

  1. 01 Jeder Ablauf bekommt einen Namen aus Verb und Substantiv, das Verb aus Get-Verb.
  2. 02 Alles, was vorher angepasst wurde, wird zum Parameter mit Typ und Prüfung.
  3. 03 Über jede Funktion kommt ein Hilfeblock, den Get-Help später ausgibt.
  4. 04 Die Datei wird zu einer .psm1 im gleichnamigen Ordner unter $env:PSModulePath.
  5. 05 Das Manifest führt Version und Abhängigkeiten, die Ablage verteilt beides.
Was du mitnimmst

Was du dabei festlegst, und zwar einmal für alle Module

Sieben Entscheidungen, die du am besten vor der ersten Zeile triffst, weil sie sich später nur mit Umbenennungen korrigieren lassen. Alle sieben kosten zusammen weniger Zeit als die erste Diskussion darüber, warum eine Funktion anders heißt als die anderen.

Verb und Substantiv nach den Vorgaben von PowerShell wählen

Jeder Befehl heißt Verb-Substantiv, und das Verb kommt aus der Liste, die Get-Verb ausgibt. Nimm keine Synonyme: Es heißt Remove und nicht Delete, Get und nicht Read, Test und nicht Ping. Das Substantiv steht in der Einzahl, auch wenn die Funktion mehrere Objekte zurückgibt. Wer sich daran hält, bekommt Befehle, die sich anfühlen wie die eingebauten, und das ist der Grund, warum andere sie überhaupt ausprobieren.

Ein Präfix im Substantiv festlegen

Setz vor das Substantiv ein kurzes Kürzel für euer Haus oder euren Bereich, damit aus Get-Server ein Get-CorpServer wird. Damit kollidiert ihr nicht mit Befehlen aus Modulen, die später dazukommen, und in einer Ausgabe von Get-Command ist sofort erkennbar, was von euch stammt. Diese Festlegung gilt für alle Module und nicht für dieses eine.

Erst die Parameter, dann die Logik

Alles, was ihr vor einem Lauf anpassen würdet, wird ein Parameter. Nichts davon bleibt als Variable im Kopf der Datei stehen. Mit [CmdletBinding()] bekommt die Funktion die gemeinsamen Parameter dazu, also unter anderem -Verbose und -ErrorAction, und mit einer Angabe zu ShouldProcess kommen -WhatIf und -Confirm hinzu. Gerade -WhatIf entscheidet darüber, ob jemand ein Werkzeug zum ersten Mal auf einem Produktivsystem ausprobiert.

Hilfe schreiben, bevor jemand danach fragt

Ein Kommentarblock über der Funktion mit .SYNOPSIS, .DESCRIPTION, je einem .PARAMETER pro Parameter und mindestens zwei .EXAMPLE reicht vollkommen aus. PowerShell liest ihn und gibt ihn bei Get-Help aus. Der Aufwand liegt bei zehn Minuten je Funktion, und er ist der Unterschied zwischen einem Werkzeug, das jemand benutzt, und einem, bei dem er lieber nachfragt.

Festlegen, was nach außen sichtbar ist

Standardmäßig sind nach dem Import alle Funktionen einer .psm1 verfügbar, Variablen dagegen nicht. Verlass dich nicht darauf, sondern zähl mit Export-ModuleMember und im Manifest unter FunctionsToExport ausdrücklich auf, was öffentlich sein soll. Alles andere sind interne Hilfsfunktionen, die ihr jederzeit umbauen könnt, ohne jemandem den Aufruf zu zerlegen.

Die Version im Manifest führen, nicht im Dateinamen

Ein Manifest mit der Endung .psd1 hält Version, Autor, Beschreibung, benötigte Module und die erforderliche PowerShell-Fassung fest. Damit lässt sich beantworten, welcher Stand irgendwo läuft. Dateinamen mit angehängtem Datum leisten das nicht, weil sie nach dem Kopieren nicht mehr stimmen.

Eine Ablage bestimmen, aus der alle beziehen

Ein Modul, das per Mail verteilt wird, ist wieder eine Skriptsammlung. Legt eine Quelle fest, aus der die Module kommen, sei es eine interne Ablage, ein Repository oder ein Verzeichnis, das über Gruppenrichtlinie verteilt wird, und schreibt in die Anleitung, dass es keine zweite gibt.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Warum die Namen wichtiger sind als die Technik

PowerShell setzt Befehlsnamen aus einem Verb und einem Substantiv zusammen, und die Liste der Verben ist nicht beliebig. Sie ist in mehrere Gruppen geteilt, unter anderem für allgemeine Aktionen, für Kommunikation, für Daten, für Diagnose, für den Lebenszyklus und für Sicherheit, und Get-Verb gibt sie vollständig aus. Zu jedem Verb steht dabei, welche Synonyme ausdrücklich nicht verwendet werden sollen. Das wirkt beim ersten Lesen kleinlich und zahlt sich in dem Moment aus, in dem jemand eine Funktion von euch sucht, ohne ihren Namen zu kennen: Wer weiß, dass alles, was etwas holt, mit Get beginnt, findet sie.

Ein paar Unterscheidungen kommen im Alltag immer wieder vor. New legt etwas an, das es vorher nicht gab, Add hängt etwas an eine vorhandene Struktur an. Set ändert etwas Vorhandenes und darf es unter Umständen auch anlegen. Invoke führt etwas aus und wartet, bis es fertig ist, Start stößt etwas an und kehrt sofort zurück. Find sucht ein Objekt, Search legt einen Verweis auf ein Objekt in einer Ablage an. Und für alles, was prüft, gibt es Test, während Ping ausdrücklich nicht mehr verwendet werden soll.

Das Substantiv steht in der Einzahl, auch wenn die Funktion mehrere Objekte zurückgibt. Das ist bei den eingebauten Befehlen genauso, es heißt Get-Service und nicht Get-Services. Und davor gehört ein kurzes Präfix, das euer Haus oder euren Bereich kennzeichnet. Das kostet vier Zeichen und verhindert, dass eine eurer Funktionen irgendwann denselben Namen trägt wie eine aus einem Modul, das jemand später installiert. Wenn zwei Module denselben Befehlsnamen mitbringen, gewinnt das zuletzt geladene, und diese Art Fehler sucht man lange.

Was ein Modul von einer Datei unterscheidet, die man einbindet

Technisch ist ein Skriptmodul jede gültige PowerShell-Datei, die mit der Endung .psm1 gespeichert wird. Damit greifen die Modul-Cmdlets, allen voran Import-Module und Remove-Module. Der Unterschied zur Alternative ist praktisch: Ohne Modul bleibt nur, den Inhalt einer Datei in die laufende Sitzung einzulesen, und das lässt sich weder sinnvoll verteilen noch versionieren noch wieder entladen. Ein Modul dagegen ist ein Paket, das auf anderen Systemen ankommt und das dort seinen Namen behält.

Der Ordner ist Teil dieser Regel. Die .psm1 und der Ordner, in dem sie liegt, müssen denselben Namen tragen, und dieser Ordner muss in einem der Pfade aus $env:PSModulePath stehen. Ist beides erfüllt, greift seit PowerShell 3.0 das automatische Laden: Ruft jemand eine Funktion aus dem Modul auf, wird es im Hintergrund geladen. Liegt der Ordner woanders, funktioniert alles genauso, nur muss der Pfad dann bei jedem Import-Module angegeben werden, und dieser Umstand reicht aus, damit ein Werkzeug nicht benutzt wird.

Beim Import gilt eine Voreinstellung, die man kennen sollte: Alle Funktionen aus der .psm1 sind danach verfügbar, Variablen dagegen nicht. Darauf zu bauen ist trotzdem keine gute Idee, weil damit jede Hilfsfunktion Teil eurer öffentlichen Oberfläche wird und sich später nicht mehr umbenennen lässt, ohne dass jemand darüber stolpert. Export-ModuleMember am Ende der Datei und FunctionsToExport im Manifest ziehen diese Grenze ausdrücklich. Und noch ein Detail für den Alltag: Remove-Module entfernt ein Modul aus der laufenden Sitzung, es deinstalliert nichts und löscht keine Dateien.

Das Manifest und die Fragen, die es beantwortet

Ein Manifest ist eine Datei mit der Endung .psd1, die neben dem Modul liegt und es beschreibt. New-ModuleManifest legt sie an und erzeugt dabei eine Vorlage mit allen möglichen Feldern. Die meisten davon könnt ihr leer lassen. Wichtig sind fünf: RootModule zeigt auf die .psm1, ModuleVersion trägt die Version, Description und Author beantworten die Frage nach Zweck und Zuständigkeit, und FunctionsToExport bestimmt die öffentliche Oberfläche.

Zwei weitere Felder lohnen sich, sobald das Modul auf mehreren Systemen liegt. RequiredModules nennt die Module, ohne die eures nicht läuft, und PowerShell weigert sich zu laden, wenn sie fehlen. Das ist angenehmer als ein Fehler mitten in einem Ablauf. PowerShellVersion legt fest, ab welcher Fassung das Modul unterstützt wird, und verhindert, dass jemand es auf einem älteren Server einbindet und dort auf Sprachmittel trifft, die es noch nicht gibt.

Der eigentliche Gewinn ist die Version. Sie steht im Manifest, sie wandert mit dem Ordner, und Get-Module gibt sie aus. Damit lässt sich die Frage, welcher Stand auf welchem Server liegt, in einem Aufruf beantworten, und zwar auch über mehrere Server hinweg. Voraussetzung dafür ist die Disziplin, sie bei jeder Änderung hochzusetzen. Eine Version, die seit zwei Jahren auf 1.0.0 steht, während sich der Inhalt viermal geändert hat, ist schlechter als keine, weil sie eine Sicherheit vortäuscht.

Verteilen, prüfen und in Betrieb halten

Für die Verteilung gibt es zwei Wege, die sich beide bewährt haben. Der erste ist ein internes Repository: Ihr veröffentlicht das Modul dorthin und bezieht es auf den Servern mit Install-Module. Der Vorteil ist, dass Aktualisieren und Deinstallieren mitgeliefert sind und dass jeder Server sagen kann, welche Fassung er hat. Der zweite Weg ist eine Freigabe oder ein verteiltes Verzeichnis, das in $env:PSModulePath steht. Das ist schneller aufgesetzt und reicht für eine überschaubare Zahl von Servern, verlangt aber die Zusage, dass es wirklich nur eine solche Stelle gibt.

Geprüft wird auf zwei Ebenen. PSScriptAnalyzer sieht sich den Code an und meldet die Muster, die später Ärger machen, von unbenutzten Variablen über nicht zugelassene Verben bis zu Aufrufen, die auf einer anderen Fassung nicht funktionieren. Pester prüft das Verhalten: Ihr beschreibt, was eine Funktion mit bestimmten Eingaben tun soll, und bekommt beim nächsten Umbau gesagt, wenn sie es nicht mehr tut. Für Werkzeuge, die etwas ändern statt nur abzufragen, ist das der Unterschied zwischen einer Änderung, die man vertritt, und einer, die man hofft.

Wenn eure Umgebung Skripte signiert, gehört das Modul in denselben Ablauf. Die Signaturprüfung erfasst nicht nur .ps1, sondern auch .psm1 und .psd1, und unter der Einstellung AllSigned fällt ein Modul mit unsigniertem Manifest genauso durch wie ein unsigniertes Skript. Signiert wird deshalb der ganze Ordner und nicht die Hauptdatei allein, und zwar als Schritt der Veröffentlichung, nicht als Erinnerung in einer Anleitung.

Die Fehler, die ein zweites Mal Zeit kosten

Der häufigste ist ein Modul, das intern noch weiß, auf welchem Server es entstanden ist. Ein hart eingetragener Pfad, ein Domänenname, ein Verzeichnis auf Laufwerk D: Solange das Modul nur bei euch läuft, fällt es nicht auf. Sobald es jemand anders einsetzt, bricht es an einer Stelle ab, die im Aufruf gar nicht vorkommt. Die Gegenmaßnahme ist unspektakulär: alles, was von der Umgebung abhängt, wird ein Parameter mit einem sinnvollen Standardwert.

Der zweite ist die Ausgabe. Write-Host schreibt direkt auf den Bildschirm und gibt nichts weiter, was sich verarbeiten ließe. Wer damit Ergebnisse ausgibt, baut ein Werkzeug, das man ansehen, aber nicht weiterverwenden kann. Gib stattdessen Objekte zurück und benutz Write-Verbose für Zwischenstände. Der Unterschied zeigt sich, sobald jemand die Ausgabe eurer Funktion in ein Where-Object oder ein Export-Csv steckt, und das passiert früher, als man denkt.

Der dritte betrifft Änderungen an der Oberfläche. Sobald andere eure Funktionen aufrufen, ist jeder Parametername eine Zusage. Einen Parameter umzubenennen, weil der neue Name schöner ist, zerlegt fremde Skripte an einer Stelle, die ihr nicht seht. Wenn es sein muss, geht es mit einem Alias auf den alten Namen und einer Ankündigung, sonst gar nicht. Genau deshalb lohnt es sich, die öffentliche Oberfläche von Anfang an klein zu halten und alles andere intern zu lassen.

Dazu passende Kurse

Wenn aus euren Skripten Werkzeuge werden sollen, die das ganze Team benutzt, sind Kurse rund um eigene Automatisierungswerkzeuge der passende Einstieg.

Weil ein Modul erst im täglichen Betrieb zeigt, was es taugt, gehören Windows-Server-Kurse für den automatisierten Betrieb zur selben Vorbereitung.

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Danke an Trainer Sven, welcher ein sehr detailliertes und fachliches KnowHow besitzt.
Windows Server - Remote Desktop Services (Advanced 01)
Ich beziehe mich hier nur auf die Nachschlung durch Herrn Marco Jachmann. Herr Jachmann ist überaus kompetent und hat alle Inhalte professionell vermittelt.
Hyper-V und System Center VMM - Alles für Administratoren
Sehr sehr gut aufbereitete Nachschulung mit genau dem notwendigen Engagement und der erforderlichen Vorbereitung innerhalb von 48 Stunden.
Hyper-V und System Center VMM - Alles für Administratoren

Häufige Fragen

Brauchen wir wirklich ein Manifest, oder reicht die .psm1?
Für ein Modul, das nur auf einem Rechner liegt, reicht die .psm1. Sobald es auf mehreren Systemen läuft, beantwortet erst das Manifest die Frage, welcher Stand wo liegt und wovon das Modul abhängt. Der Aufwand ist einmalig, denn New-ModuleManifest erzeugt die Datei, und ihr füllt fünf Felder aus.
Wohin gehört das Modul auf einem Server?
In einen der Ordner, die in $env:PSModulePath stehen, und dort in ein Verzeichnis, das genauso heißt wie die .psm1. Für Module, die allen zur Verfügung stehen sollen, ist der Programmpfad die richtige Stelle, für persönliche Werkzeuge der Pfad im Profil. Wichtig ist vor allem, dass ihr euch auf eine Variante festlegt, sonst existiert das Modul irgendwann an drei Orten in drei Fassungen.
Wie kommen Kollegen an eine neue Version?
Über die Stelle, die ihr als Quelle festgelegt habt. Bei einem internen Repository holen sie sich die neue Fassung mit den Modul-Cmdlets und sehen an der Version, dass sich etwas geändert hat. Bei einer verteilten Freigabe bekommen sie sie automatisch, sobald sie eine neue Sitzung öffnen. Was auf keinen Fall funktioniert, ist die Verteilung per Mail, denn danach hat jeder eine eigene Kopie, die nie wieder aktualisiert wird.
Muss jede Funktion in eine eigene Datei?
Müssen nicht, aber ab etwa einem Dutzend Funktionen lohnt es sich. Ihr legt dann je Funktion eine .ps1 in einem Unterordner ab und lasst die .psm1 diese Dateien beim Laden einlesen. Das hält die Änderungen klein und macht sichtbar, was sich in einer Fassung geändert hat. Nach außen ändert sich dadurch nichts, denn das Modul verhält sich weiterhin wie eines.
Was ist mit Modulen, die auf Windows PowerShell 5.1 und auf PowerShell 7 laufen sollen?
Das geht, verlangt aber, dass ihr beides testet, denn eine Reihe von Cmdlets aus Windows PowerShell ist in PowerShell 7 nicht mehr enthalten. Trag die unterstützte Fassung im Manifest unter PowerShellVersion ein und lasst eure Tests gegen beide Fassungen laufen. Wo ein Modul zwingend die ältere Umgebung braucht, gibt es bei Import-Module den Schalter -UseWindowsPowerShell, der es in einem eigenen Prozess bereitstellt.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Windows Server-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Eigene Werkzeuge bauen, die auch andere benutzen

Im Aufbaukurs bei cmt schneidest du ein gewachsenes Skript in Funktionen, gibst ihnen Parameter und Hilfetexte und packst sie in ein Modul mit Manifest, das du am Ende auf einem zweiten System einsetzt.