Vom Skript zur Architektur – Warum Automations-Tools mehr als Code sind
Dieser Beitrag ist Teil 2 der Blogserie PowerShell Toolmaking in der Praxis. Während der erste Teil die architektonischen Grundlagen gelegt hat, beginnt hier der Übergang von Theorie zu Umsetzung. Der Fokus liegt bewusst auf einem scheinbar einfachen, in der Praxis jedoch entscheidenden Thema: dem sauberen Aufbau einer Verbindung zum Active Directory über PowerShell Remoting.
Der hier vorgestellte Ansatz bildet die Grundlage für alle weiteren Werkzeuge der Serie.
Remoting als infrastrukturelle Realität – Warum Verbindungslogik gekapselt werden muss
In modernen IT-Umgebungen ist lokale Administration die Ausnahme geworden. Administrative Workstations, eingeschränkte Rechte und Compliance-Vorgaben sorgen dafür, dass zentrale Dienste wie Active Directory ausschließlich remote verwaltet werden. PowerShell Remoting ist damit kein Spezialthema mehr, sondern seit Windows PowerShell 3.0 fester Bestandteil des administrativen Alltags.
Viele etablierte Verwaltungswerkzeuge setzen intern auf diese Technologie. Der Server-Manager, das Windows Admin Center und zahlreiche Automationslösungen arbeiten nicht lokal, sondern führen Befehle gezielt auf Zielsystemen aus. PowerShell Toolmaking muss diese Realität berücksichtigen und Remoting als gegebenen Rahmen akzeptieren.
Das Thema Remoting selbst wird im Beitrag PowerShell Remoting verstehen: Von Ad-hoc bis One-to-Many vertieft behandelt. Dort werden die unterschiedlichen Remoting-Varianten systematisch eingeordnet und praxisnah erläutert. Der vorliegende Beitrag setzt dieses Verständnis voraus und konzentriert sich auf die Frage, wie Remoting bewusst in ein wiederverwendbares Tool integriert wird.
Warum Remoting nicht in Fachfunktionen gehört
Ein häufiger Fehler in Skripten besteht darin, Remoting nebenbei einzubauen. Sitzungen werden innerhalb fachlicher Funktionen erstellt, Module ad hoc geladen und Verbindungen implizit vorausgesetzt. Diese Vermischung von Infrastruktur und Fachlogik führt zu schwer wartbarem Code und versteckten Abhängigkeiten.
Toolmaking verfolgt einen anderen Ansatz. Verbindungsaufbau, Sitzungsverwaltung und Modulbereitstellung sind infrastrukturelle Aufgaben. Sie sind Voraussetzung für fachliche Automatisierung, aber nicht Teil dieser Logik. Werden diese Verantwortlichkeiten getrennt, entsteht Klarheit – sowohl im Code als auch im Ablauf.
Eine Funktion, die Benutzer anlegt oder Gruppen verwaltet, sollte nicht wissen müssen, wie die Verbindung zum Active Directory hergestellt wurde. Sie sollte davon ausgehen können, dass die benötigte Umgebung bereits existiert.
Das Connect-Tool als stabiler Einstiegspunkt
Ein dediziertes Connect-Tool übernimmt genau diese Aufgabe. Es kapselt den Aufbau und die Wiederverwendung von PowerShell-Sitzungen, stellt benötigte Module bereit und definiert einen reproduzierbaren Ausgangszustand für alle nachfolgenden Schritte. Damit wird Remoting kontrollierbar und transparent.
Gerade implizites Remoting eignet sich für diesen Ansatz. Cmdlets stehen lokal zur Verfügung, ihre Ausführung erfolgt jedoch konsequent auf dem Zielsystem. Für nachgelagerte Tools ist dieser Unterschied unsichtbar, architektonisch jedoch entscheidend.
Das Connect-Tool wird damit zum ersten echten Baustein im Toolmaking-Prozess. Es schafft die Grundlage für Wiederholbarkeit, Idempotenz und saubere Orchestrierung – und markiert den Übergang von konzeptionellem Verständnis zu praktischer Umsetzung.
Implizites Remoting verstehen: Was lokal aussieht, aber remote ausgeführt wird
Unabhängig von der konkreten Ausprägung gilt für PowerShell Remoting ein zentrales Prinzip: Befehle werden nicht lokal, sondern auf dem Zielsystem ausgeführt. Dieses Verhalten unterscheidet Remoting grundlegend von lokalen Modulausführungen oder klassischen Skriptkopien.
Ob explizit über Invoke-Command oder implizit über importierte Cmdlets – die eigentliche Verarbeitung findet immer auf dem entfernten System statt. Parameter, Filter und Logik werden an das Zielsystem übergeben, dort ausgewertet und das Ergebnis anschließend an die lokale Sitzung zurückgegeben.
Diese Trennung ist für Toolmaking entscheidend. Automationen dürfen nicht davon ausgehen, dass lokale Voraussetzungen wie installierte Module, Registry-Zugriffe oder Dateipfade zur Verfügung stehen. Der Ausführungskontext ist stets das Zielsystem.
Explizites Remoting: Kontrolle auf Kosten der Lesbarkeit
Explizites Remoting erfolgt typischerweise über Invoke-Command oder direkte ScriptBlocks innerhalb einer Remote-Session. Dieser Ansatz macht die Remote-Ausführung sehr sichtbar. Jeder Aufruf signalisiert klar, dass Code nicht lokal läuft.
Für einmalige Aufgaben oder gezielte administrative Eingriffe ist dies sinnvoll. In Toolmaking-Szenarien führt explizites Remoting jedoch schnell zu unübersichtlichem Code. Fachlogik wird in ScriptBlocks eingebettet, Variablen müssen bewusst serialisiert werden und der Lesefluss leidet.
Controller-Skripte, die viele fachliche Schritte orchestrieren, werden auf diese Weise schwer wartbar. Die eigentliche Aufgabe eines Tools tritt hinter Remoting-Syntax zurück.
Implizites Remoting: Proxy-Cmdlets, Provider und das fehlende Laufwerk
Um implizites Remoting wirklich zu verstehen, reicht es nicht aus, nur auf die Verfügbarkeit von Cmdlets zu schauen. Entscheidend ist die Frage, woher diese Cmdlets ihre Daten beziehen und in welchem Kontext sie arbeiten.
PowerShell folgt hier einem Grundprinzip, das sich an der Unix-Philosophie orientiert: Everything is a drive. Datenquellen werden über PowerShell-Drives abstrahiert, die wiederum von PowerShell-Providern bereitgestellt werden. Möchte ein Cmdlet Daten lesen oder schreiben, benötigt es nicht nur Logik, sondern auch einen passenden Provider und ein dazugehöriges Drive.
Nach dem Import der Proxy-Cmdlets für das Active Directory stehen lokal zwar Befehle wie Get-ADUser oder New-ADUser zur Verfügung. Wird an dieser Stelle jedoch Get-PSDrive in der lokalen Sitzung ausgeführt, zeigt sich ein entscheidender Punkt: Ein PowerShell-Drive für das Active Directory existiert nicht.
Dieses Fehlen ist kein Fehler, sondern der Beweis dafür, dass lokal keine Datenquelle angebunden wurde. Die Cmdlets sind vorhanden, der Provider jedoch nicht.
Erst der Blick auf Get-PSSession zeigt den eigentlichen Kontext. Die geöffnete Remote-Sitzung zum Domänencontroller ist die Umgebung, in der das ActiveDirectory-Modul tatsächlich geladen wurde. Wird diese Sitzung mit Enter-PSSession betreten, lässt sich dort unmittelbar nachvollziehen, was lokal verborgen bleibt: Das Modul ActiveDirectory ist geladen und Get-PSDrive zeigt ein aktives Drive auf Basis des ActiveDirectory-Providers.
Ebenso lässt sich leicht verifizieren, dass eine neu aufgebaute Remote-Sitzung diesen Zustand nicht automatisch besitzt. Ohne expliziten Modulimport existieren weder das Modul noch das zugehörige Drive.
Genau hier liegt die Essenz impliziten Remotings: Die lokalen Proxy-Cmdlets sind lediglich Weiterleitungen. Die eigentliche Datenquelle, der Provider und das Drive existieren ausschließlich in der Remote-Sitzung. Die Ausführung erfolgt remote – auch wenn sie sich lokal anfühlt.
Serialisierung: Warum Ergebnisse Daten bleiben müssen
Das zuvor beschriebene Fehlen eines lokalen Active-Directory-Drives ist kein Nebeneffekt, sondern ein Hinweis auf den tatsächlichen Ausführungskontext. Die Arbeit findet vollständig in der Remote-Sitzung statt. Alles, was in die lokale PowerShell zurückkehrt, muss diesen Kontext verlassen – und wird dabei serialisiert.
Serialisierung bedeutet, dass PowerShell Objekte auf ihre reinen Daten reduziert. Eigenschaften werden übertragen, Methoden, ScriptProperties und direkte Objektverweise hingegen nicht. Das zurückgegebene Objekt ist damit keine aktive AD-Repräsentation mehr, sondern eine Momentaufnahme des Zustands zum Zeitpunkt der Abfrage.
In der Praxis zeigt sich dies besonders deutlich im Zusammenspiel mit Providern und Drives. Während in der Remote-Sitzung ein vollständiger ActiveDirectory-Provider mit zugehörigem Drive existiert, steht lokal lediglich ein deserialisiertes Ergebnisobjekt zur Verfügung. Dieses Objekt kennt seinen Ursprung, besitzt jedoch keine Verbindung mehr zur Datenquelle.
Für PowerShell Toolmaking ist dieser Umstand kein Nachteil, sondern ein Entwurfsleitfaden. Funktionen sollten nicht versuchen, mit Methoden oder impliziten Objektfähigkeiten zu arbeiten. Stattdessen verarbeiten sie Daten. Eigenschaften werden ausgewertet, verglichen und in neue strukturierte Objekte überführt.
Dieser Ansatz fördert robuste Automatisierung. Er verhindert implizite Abhängigkeiten vom Ausführungskontext und sorgt dafür, dass Funktionen auch bei wiederholter Ausführung stabil bleiben. Serialisierung zwingt damit zu sauberem Design – und macht Toolmaking verlässlicher.
Einordnung für das Connect-Tool: Remoting bewusst kapseln
Die zuvor beschriebenen Eigenschaften impliziten Remotings machen deutlich, warum eine saubere Kapselung unerlässlich ist. Proxy-Cmdlets, Provider und Drives existieren nicht lokal, sondern ausschließlich im Kontext einer spezifischen Remote-Sitzung. Auch die zurückgegebenen Objekte sind keine aktiven Repräsentationen, sondern serialisierte Daten.
Wird dieser Umstand nicht berücksichtigt, entstehen implizite Abhängigkeiten. Fachliche Funktionen setzen dann unbewusst voraus, dass eine bestimmte Sitzung existiert, dass Module geladen wurden oder dass ein Provider verfügbar ist. Solche Annahmen sind fragil und schwer zu diagnostizieren.
Das Connect-Tool übernimmt genau an dieser Stelle eine zentrale Rolle. Es definiert den Übergang zwischen lokaler PowerShell-Sitzung und Remote-Ausführung explizit. Sitzungsaufbau, Wiederverwendung, Modulimport und die damit verbundenen Proxy-Cmdlets werden an einer Stelle gebündelt und kontrolliert.
Nachgelagerte Tools profitieren davon unmittelbar. Sie arbeiten nicht mit Remoting-Details, sondern mit einem klar definierten Ausgangszustand. Die Frage, ob ein Active-Directory-Provider existiert oder ob Cmdlets lokal verfügbar sind, stellt sich für sie nicht mehr.
Dieses Prinzip ist mehr als eine organisatorische Vereinfachung. Es ist eine architektonische Voraussetzung für wartbares Toolmaking. Erst durch diese bewusste Kapselung wird implizites Remoting zu einem stabilen Fundament für alle weiteren Schritte der Serie.
Das Connect-Tool im Detail: Vom Remoting-Einzeiler zum wiederverwendbaren Tool
Der Einstieg in implizites Remoting wirkt im Alltag oft unspektakulär. Zwei Befehle reichen aus, um eine Remote-Sitzung zu öffnen und das ActiveDirectory-Modul lokal verfügbar zu machen. Genau diese Einfachheit ist didaktisch wertvoll, weil sie das Grundprinzip sichtbar macht: Die Ausführung erfolgt remote, die Cmdlets stehen lokal bereit.
New-PSSession -Name 'DC' -ComputerName 'dc.firma.de' -Credential 'FIRMA\Administrator' Import-Module -Name 'ActiveDirectory' -PSSession (Get-PSSession -Name 'DC') -Global
Diese Form eignet sich für den spontanen Einsatz in der Konsole. Für Toolmaking ist sie jedoch nur der Startpunkt. Sie enthält mehrere implizite Annahmen: Session-Name, Zielsystem und Anmeldeinformationen sind fest verdrahtet, und Wiederverwendung ist nicht vorgesehen.
Damit ist der nächste Schritt naheliegend: Die Verbindung wird zunächst greifbar gemacht, bevor sie parametrisiert und später kapsuliert wird.
Erst entkoppeln, dann abstrahieren: die Session als Variable
Der erste Schritt Richtung Wartbarkeit ist nicht sofort eine Funktion, sondern eine kleine Entkopplung. Die Session wird in einer Variablen gehalten. Dadurch wird sichtbar, dass der Modulimport an eine konkrete Sitzung gebunden ist. Gleichzeitig entsteht eine Grundlage für Prüfungen und Wiederverwendung.
$PSSession = New-PSSession -Name 'DC' -ComputerName 'dc.firma.de' -Credential 'FIRMA\Administrator' Import-Module -Name 'ActiveDirectory' -PSSession $PSSession -Global
Auch diese Variante bleibt bewusst einfach. Sie zeigt jedoch bereits ein zentrales Muster, das im Tool später entscheidend wird: Die Session ist ein Objekt, das den Ausführungskontext repräsentiert. Der Modulimport erfolgt nicht irgendwie, sondern gezielt über diese Session.
An diesem Punkt kann bereits geprüft werden, dass lokal zwar die Proxy-Cmdlets verfügbar sind, jedoch kein Active-Directory-Drive existiert. Der Provider bleibt Teil des Remote-Kontextes. Genau diese Einsicht verhindert später typische Fehlannahmen.
Parametrisierung als Brücke: aus dem Muster wird ein wiederholbares Skript
Sobald ein Muster mehrfach genutzt wird, entsteht Bedarf an Parametern. Der Sprung von der Konsole zum Skript ist dabei nicht nur technisch, sondern konzeptionell relevant: Parameter machen Abhängigkeiten sichtbar und schaffen Wiederholbarkeit.
Param ( [Parameter()] [ValidateNotNullOrEmpty()] [string] $DC = 'dc.firma.de', [Parameter()] [ValidateNotNullOrEmpty()] [string] $Admin = 'FIRMA\Administrator' ) $Module = 'ActiveDirectory' $Credential = Get-Credential -Message "Anmeldeinformationen für $DC" -UserName $Admin $DCName = ($DC.Split('.')[0]).ToUpperInvariant() $PSSession = New-PSSession -Name $DCName -ComputerName $DC -Credential $Credential Import-Module -Name $Module -PSSession $PSSession -Global
Vom Skript zur Funktion: warum Toolmaking hier beginnt
An dieser Stelle beginnt der Übergang zum eigentlichen Toolmaking. Ein Skript kann die Verbindung herstellen, aber es ist als Baustein in größeren Workflows ungeeignet. Ein Controller benötigt ein wiederverwendbares Tool, das:
- eine vorhandene Session erkennt und wiederverwendet
- optional einen Neuaufbau erzwingt
- den Modulzustand verlässlich herstellt
- ein strukturiertes Ergebnis zurückliefert, das orchestrierbar ist
Damit wird aus einem wiederholbaren Skript ein belastbarer Baustein. Genau diesen Schritt bildet die Funktion Connect-ADDomain ab.
Session-Wiederverwendung: Idempotenz beginnt bei der Verbindung
Das bisherige Skript erstellt bei jedem Aufruf eine neue Remote-Sitzung. Für interaktive Tests ist das unproblematisch, in produktiven Automationen jedoch nicht ideal. Wiederholte Verbindungsaufbauten erhöhen Komplexität und erschweren die Nachvollziehbarkeit.
Der nächste Evolutionsschritt besteht daher darin, zunächst zu prüfen, ob bereits eine geeignete Sitzung existiert.
$PSSession = Get-PSSession -ErrorAction 'SilentlyContinue' | ` Where-Object -FilterScript {$PSItem.ComputerName -ieq $DC -and $PSItem.State -eq 'Opened'} | ` Select-Object -First 1 if (-not $PSSession) { $PSSession = New-PSSession -Name $DCName -ComputerName $DC -Credential $Credential }
Diese Erweiterung wirkt zunächst unscheinbar. Tatsächlich markiert sie jedoch einen wichtigen architektonischen Schritt. Das Verhalten wird idempotent: Ein erneuter Aufruf erzeugt keine zusätzliche Session, sofern bereits eine passende Verbindung existiert.
Damit wird die Verbindung selbst zum kontrollierten Zustand – nicht zu einem Seiteneffekt.
Kontrollierter Modulimport: Proxy-Cmdlets nur bei Bedarf
Ein ähnliches Muster gilt für den Modulimport. Wird Import-Module bei jedem Aufruf blind ausgeführt, entstehen zwar keine Fehler, jedoch unnötige Wiederholungen. Toolmaking vermeidet solche Redundanzen bewusst.
if (-not (Get-Module -Name $Module)) {Import-Module -Name $Module -PSSession $PSSession -Global}
Durch diese Prüfung bleibt die lokale Sitzung stabil. Proxy-Cmdlets werden nur dann erzeugt, wenn sie noch nicht vorhanden sind. Der Zustand wird nicht implizit verändert, sondern explizit hergestellt.
Gerade im Zusammenspiel mit implizitem Remoting ist dieses Vorgehen wichtig. Die Proxy-Cmdlets sind lediglich lokale Repräsentationen. Der tatsächliche Provider und das zugehörige Drive existieren ausschließlich im Remote-Kontext. Ein bewusster Import verhindert widersprüchliche Annahmen über die Umgebung.
Vom Skript zur Funktion: Kapselung und Rückgabevertrag
An dieser Stelle ist der Übergang zur Funktion konsequent. Ein Skript kann die Verbindung herstellen, doch ein Tool im Sinne des Toolmakings benötigt zusätzlich:
- eine klar definierte Schnittstelle
- eine kontrollierte Fehlerstrategie
- einen strukturierten Rückgabevertrag
Die Funktion Connect-ADDomain kapselt genau diese Aspekte. Sie übernimmt Parameterprüfung, Session-Strategie, Modulimport und Statusrückgabe in einem klar definierten Rahmen.
Ein zentrales Element ist dabei der Rückgabevertrag:
$Result = [PSCustomObject]@{ DomainController = $DomainController SessionId = $PSSession.Id SessionName = $PSSession.Name SessionState = $PSSession.State ModuleName = $Module ModuleLoaded = [bool](Get-Module -Name $Module) RemotingMode = 'Implicit' Timestamp = Get-Date Success = $true ErrorMessage = $null }
Dieses Objekt ersetzt klassische Konsolenausgaben. Es transportiert Zustandsinformationen strukturiert und maschinenlesbar. Ein Controller-Skript kann diese Daten auswerten, protokollieren oder in JSON und HTML überführen.
Damit wird das Connect-Tool nicht nur funktional korrekt, sondern orchestrierbar.
Fehlerstrategie: Daten statt Ausgaben
Auch im Fehlerfall bleibt die Funktion konsistent. Statt ausschließlich Konsolentext zu erzeugen, wird ein strukturiertes Ergebnisobjekt mit Success = $false zurückgegeben. Optional kann über -PassThru das Objekt direkt an aufrufende Komponenten übergeben werden.
Dieser Ansatz trennt Diagnose von Steuerlogik. Write-Verbose -Message dient der optionalen Transparenz während der Ausführung, beeinflusst jedoch nicht den Rückgabewert.
So entsteht ein Tool, das sowohl interaktiv als auch automatisiert zuverlässig eingesetzt werden kann. Mit dem fertigen Connect-Tool steht nun ein stabiler Einstiegspunkt zur Verfügung. Im nächsten Kapitel zeige ich, wie dieses Tool in ein erstes Controller-Skript eingebunden wird und wie vorbereitete Verbindungen als Voraussetzung für fachliche Automatisierung dienen.
Aktueller Stand des Connect-Tools
Die folgenden Schritte haben die Verbindung vom einfachen Remoting-Aufruf über ein parametrisiertes Skript bis hin zu einer strukturierten Funktion weiterentwickelt.
Zur besseren Übersicht steht nachfolgend der aktuelle konsolidierte Stand der Funktion Connect-ADDomain zur Verfügung. Dieser bildet die Grundlage für alle weiteren Schritte in dieser Serie.
function Connect-ADDomain { [CmdletBinding()] Param ( [Parameter()] [ValidateNotNullOrEmpty()] [string]$DomainController = 'dc.firma.de', [Parameter()] [PSCredential] $Credential, [Parameter()] [ValidateNotNullOrEmpty()] [string]$ModuleName = 'ActiveDirectory', [Parameter()] [switch]$ForceReconnect, [Parameter()] [switch]$PassThru ) $SessionName = ($DomainController.Split('.')[0]).ToUpperInvariant() $PreviousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Stop' try { if (-not $Credential) { $Credential = Get-Credential -Message "Anmeldeinformationen für $DomainController" } $PSSession = Get-PSSession -ErrorAction 'SilentlyContinue' | ` Where-Object -FilterScript {$PSItem.ComputerName -ieq $DomainController -and $PSItem.State -eq 'Opened'} | Select-Object -First 1 if ($ForceReconnect -and $PSSession) { Write-Verbose -Message "Bestehende PSSession wird verworfen (ForceReconnect aktiviert)." Remove-PSSession -Session $PSSession -ErrorAction 'SilentlyContinue' $PSSession = $null } if (-not $PSSession) { Write-Verbose -Message "Erstelle neue PSSession zu $DomainController (Name: $SessionName)." $PSSession = New-PSSession -Name $SessionName -ComputerName $DomainController ` -Credential $Credential } else { Write-Verbose -Message "Vorhandene PSSession wird wiederverwendet (Id: $($PSSession.Id))." } if (-not (Get-Module -Name $ModuleName)) { Write-Verbose -Message "Importiere Modul $ModuleName per implizitem Remoting." Import-Module -Name $ModuleName -PSSession $PSSession -Global } else { Write-Verbose -Message "Modul $ModuleName ist bereits geladen." } $Result = [PSCustomObject]@{ DomainController = $DomainController SessionId = $PSSession.Id SessionName = $PSSession.Name SessionState = $PSSession.State ModuleName = $ModuleName ModuleLoaded = [bool](Get-Module -Name $ModuleName) RemotingMode = 'Implicit' Timestamp = Get-Date Success = $true ErrorMessage = $null } if ($PassThru) { return $Result } Set-Variable -Name 'AdSession' -Scope Script -Value $Result -Force } catch { $Result = [PSCustomObject]@{ DomainController = $DomainController SessionId = $null SessionName = $SessionName SessionState = $null ModuleName = $ModuleName ModuleLoaded = $false RemotingMode = 'Implicit' Timestamp = Get-Date Success = $false ErrorMessage = $PSItem.Exception.Message } if ($PassThru) { return $Result } Write-Warning -Message "Verbindung mit $DomainController fehlgeschlagen: $($PSItem.Exception.Message)" } finally { $ErrorActionPreference = $PreviousErrorActionPreference } }

Exkurs: Von der Funktion zum Tool – Module richtig bereitstellen
Warum eine Funktion allein noch kein Tool ist
Eine Funktion in einer .ps1-Datei ist ausführbar. Sie kann getestet, angepasst und interaktiv genutzt werden. Dennoch handelt es sich dabei noch nicht um ein bereitgestelltes Tool im Sinne professionellen Toolmakings.
Ein Tool zeichnet sich dadurch aus, dass es:
- strukturiert bereitgestellt wird
- versionierbar ist
- wiederverwendbar importiert werden kann
- eindeutig identifizierbar ist
Diese Anforderungen erfüllt PowerShell über das Modulsystem. Erst durch die Überführung einer Funktion in ein Modul wird aus lokalem Code ein stabiler Baustein.
Von der .ps1 zur .psm1-Datei
Der erste Schritt besteht darin, die Funktion aus einer Skriptdatei in eine Skriptmoduldatei zu überführen. Technisch bedeutet das: Die Datei erhält die Endung .psm1.
Beispiel:
ConnectAD.psm1
Inhaltlich ändert sich zunächst nichts. Die Funktion bleibt identisch. Der Unterschied liegt in der Art der Bereitstellung. Während eine .ps1-Datei ausgeführt wird, wird eine .psm1-Datei importiert.
Der Import erfolgt über:
Import-Module -Name ConnectAD
Damit wird die Funktion Bestandteil der aktuellen Sitzung – nicht durch Ausführung, sondern durch Registrierung im Modulkontext.
Speicherorte für Module ermitteln
PowerShell erkennt Module nur dann automatisch, wenn sie sich in einem der definierten Modulpfade befinden. Diese Pfade lassen sich wie folgt anzeigen:
Get-ChildItem -Path Env:\PSModulePath | Format-List
Typische Speicherorte sind:
- Benutzerprofil: Documents\PowerShell\Modules
- Systemweit: Program Files\PowerShell\Modules
- Windows PowerShell: WindowsPowerShell\Modules
Nur wenn ein Modul in einem dieser Verzeichnisse liegt, kann es über Autoloading automatisch gefunden werden.
Die korrekte Verzeichnisstruktur
Ein Modul besteht nicht nur aus einer Datei. Entscheidend ist die Ordnerstruktur.
Das Prinzip lautet:
\Modules
\ConnectAD
| ConnectAD.psd1
| ConnectAD.psm1
Der Ordnername entspricht exakt dem Modulnamen. Innerhalb dieses Ordners liegt die Skriptmoduldatei sowie optional – und in professionellen Szenarien verpflichtend – eine Manifestdatei.
Diese Struktur ist kein kosmetisches Detail. Sie ermöglicht Versionierung, sauberen Import und klare Identifikation.
Die Rolle der Modulmanifestdatei (.psd1)
Eine Skriptmoduldatei allein ist technisch funktionsfähig. Sie kann importiert und genutzt werden. In der Grundlagenausbildung wird daher häufig ausschließlich mit .psm1-Dateien gearbeitet, um das Prinzip eines Moduls zu demonstrieren.
Für produktionsnahe Toolmaking-Szenarien reicht das jedoch nicht aus.
Die Manifestdatei (.psd1) beschreibt das Modul formal. Sie definiert unter anderem:
- Modulname
- Version
- Autor
- exportierte Funktionen
- Abhängigkeiten
Sie wird komfortabel erzeugt über:
New-ModuleManifest
Anschließend muss die Datei bewusst gepflegt werden. Insbesondere das Feld FunctionsToExport sollte nicht dem Zufall überlassen bleiben.
Die Manifestdatei ist damit nicht optionales Beiwerk, sondern Bestandteil einer sauberen Bereitstellung. Sie macht aus einer Sammlung von Funktionen ein strukturiertes Modul.
Warum dieser Schritt für Toolmaking entscheidend ist
Der Übergang von der Funktion zur Modulbereitstellung markiert einen qualitativen Sprung. Code wird nicht mehr nur ausgeführt, sondern bereitgestellt. Er erhält eine Identität, eine Version und eine klare Importlogik.
Spätestens an dieser Stelle beginnt professionelles Toolmaking. Mit einer korrekt bereitgestellten Modulstruktur steht das Connect-Tool nun nicht mehr als isolierte Funktion, sondern als importierbares Modul zur Verfügung. Darauf aufbauend kann ein Controller-Skript entstehen, das dieses Modul gezielt nutzt.
Vom Connect-Tool zum ersten Controller-Skript
Mit dem fertigen Connect-Tool steht ein stabiler Einstiegspunkt zur Verfügung. Dennoch ist damit noch kein Workflow entstanden. Eine Funktion bereitet die Umgebung vor, führt jedoch keine fachliche Aktion aus. Hier kommt das Controller-Skript ins Spiel.
Bevor der Controller jedoch arbeiten kann, muss das Modul ConnectAD verfügbar sein. PowerShell bietet dafür mehrere Wege. Befindet sich das Modul in einem der bekannten Modulpfade, kann Autoloading genutzt werden. Alternativ erfolgt ein expliziter Import mit Import-Module. Ebenso kann mit #requires -Module ConnectAD eine verbindliche Abhängigkeit deklariert werden.
Autoloading ist komfortabel, da das Modul beim ersten Aufruf automatisch geladen wird. Für produktive Automationsszenarien ist jedoch der kontrollierte Import vorzuziehen. Professionelle Controller-Skripte dokumentieren ihre Abhängigkeiten bewusst und vermeiden implizites Verhalten.
Ein möglicher Einstieg lautet daher:
#requires -Module ConnectADDamit ist sichergestellt, dass die Funktion Connect-ADDomain verfügbar ist, bevor die Ausführung beginnt.
Ein Controller ist mehr als ein Skript
Ein Controller ist kein weiteres Skript unter vielen. Er übernimmt die Rolle des Orchestrators. Er steuert Abläufe, ruft spezialisierte Funktionen auf, bewertet deren Rückgaben und entscheidet über den weiteren Verlauf. Während Worker-Funktionen isolierte Aufgaben erfüllen, koordiniert der Controller deren Zusammenspiel.
Diese Trennung ist im Toolmaking entscheidend. Fachlogik bleibt modular, Infrastruktur bleibt gekapselt und die Steuerung erfolgt zentral. Das Ergebnis ist kein Monolith, sondern ein strukturierter Workflow.
Der erste Orchestrierungsschritt: Verbindung sicherstellen
Ein Controller beginnt nicht mit der Fachlogik, sondern mit der Sicherstellung der Voraussetzungen. In unserem Szenario bedeutet das: Die Verbindung zum Active Directory muss hergestellt und validiert sein.
Ein einfaches Controller-Grundgerüst könnte folgendermaßen aussehen:
[CmdletBinding()] Param ( [Parameter()] [ValidateNotNullOrEmpty()] [string] $DomainController = 'dc.firma.de' ) Write-Verbose -Message "Starte Controller-Skript." $ConnectionResult = Connect-ADDomain -DomainController $DomainController -PassThru -Verbose if (-not $ConnectionResult.Success) { Write-Warning -Message "Controller-Abbruch: Verbindung konnte nicht hergestellt werden." return } Write-Verbose -Message "Verbindung erfolgreich. SessionId: $($ConnectionResult.SessionId)" #requires -Module ConnectAD
Bemerkenswert ist in diesem Zusammenhang die Position der #requires-Anweisung am Ende des Skripts.
#requires -Module ConnectAD
Obwohl diese Direktive syntaktisch am Ende steht, wird sie nicht sequentiell verarbeitet. PowerShell parst #requires-Anweisungen bereits vor der eigentlichen Skriptausführung. Das bedeutet:
- Das Vorhandensein des Moduls wird geprüft, bevor der erste Codeblock ausgeführt wird.
- Fehlt das Modul, bricht PowerShell die Ausführung unmittelbar ab.
- Die Position innerhalb des Skripts ist für die Wirksamkeit unerheblich.
Damit unterscheidet sich #requires grundlegend von regulären Befehlen wie Import-Module, die im normalen Ausführungsfluss verarbeitet werden.
Dieses Verhalten unterstreicht die Rolle von #requires als deklarative Vorbedingung. Es handelt sich nicht um eine Aktion, sondern um eine Anforderung an die Laufzeitumgebung.
Der Controller bleibt damit klar strukturiert:
- Infrastrukturabhängigkeiten werden deklarativ definiert.
- Fachlogik wird ausschließlich bei erfüllten Voraussetzungen ausgeführt.
- Steuerentscheidungen basieren auf strukturierten Statusobjekten.
Gerade in professionellen Automationsumgebungen ist diese Trennung entscheidend. Abhängigkeiten gehören nicht in den Kontrollfluss, sondern in die Vorbedingungen des Skripts.
Statusobjekte als Steuerinstrument
Der Rückgabevertrag des Connect-Tools entfaltet seine Stärke erst im Controller-Kontext. Das Objekt $ConnectionResult enthält alle relevanten Metadaten. Der Controller kann damit:
- erfolgreiche Verbindungen protokollieren
- Fehler strukturiert behandeln
- Statusinformationen aggregieren
- Ergebnisse in JSON, XML oder HTML überführen
Damit entsteht ein klarer Kontrollfluss. Fehler werden nicht implizit über Konsolentext signalisiert, sondern explizit über das Feld Success. Diese Form der Rückmeldung ist maschinenlesbar und damit orchestrierbar.
Gerade in größeren Automationsketten – etwa im Zusammenspiel mit Scheduler, CI/CD-Pipelines oder Runbooks – ist diese Konsistenz unverzichtbar.
Vorbereitung auf fachliche Automatisierung
Mit einem funktionierenden Controller, der die Verbindung sicherstellt, ist die Grundlage für die eigentliche Fachlogik gelegt. An dieser Stelle könnten nun Funktionen wie New-ADUser, Get-ADGroup oder Add-ADGroupMember folgen – jedoch nicht direkt, sondern ebenfalls gekapselt.
Der Controller wird in den kommenden Teilen der Serie:
- Eingangsdaten entgegennehmen
- Worker-Funktionen gezielt aufrufen
- deren Statusobjekte sammeln
- Ergebnisse aggregieren
Die Verbindung selbst bleibt dabei ein vorbereitender Schritt, der sauber isoliert ist.

Exkurs: #requires als deklarative Vorbedingung im Toolmaking
Warum #requires mehr ist als ein Modul-Import
In klassischen Skripten werden Abhängigkeiten häufig mit Import-Module am Anfang eingebunden. Dieser Ansatz funktioniert, bleibt jedoch Teil des regulären Ausführungsflusses. Das Skript startet, führt Befehle aus und importiert Module sequentiell.
Die Direktive #requires verfolgt einen anderen Ansatz. Sie definiert keine Aktion, sondern eine zwingende Vorbedingung für die Ausführung.
Beispiel:
#requires -Module ConnectAD
Bevor PowerShell die erste Codezeile ausführt, wird geprüft, ob das angegebene Modul verfügbar ist. Fehlt es, wird das Skript nicht gestartet.
Damit verschiebt sich die Verantwortung:
- Abhängigkeiten werden deklariert.
- Voraussetzungen werden erzwungen.
- Fehler treten vor der eigentlichen Logik auf.
Parsing vor Ausführung
Ein zentraler Unterschied besteht im Zeitpunkt der Verarbeitung. #requires-Anweisungen werden bereits während der Parse-Phase ausgewertet – also bevor der Ausführungsfluss beginnt.
Das bedeutet:
- Die Position im Skript ist funktional unerheblich.
- Auch wenn #requires am Ende steht, wird sie vorab geprüft.
- Der reguläre Kontrollfluss wird nicht erreicht, wenn Anforderungen nicht erfüllt sind.
Dieses Verhalten unterscheidet #requires deutlich von Import-Module, das erst zur Laufzeit ausgeführt wird.
Im Kontext von Toolmaking ist das entscheidend. Abhängigkeiten werden nicht dynamisch behandelt, sondern als strukturelle Voraussetzung definiert.
Mögliche #requires-Varianten
#requires beschränkt sich nicht auf Module. Es können unter anderem folgende Anforderungen definiert werden:
#requires -Version 7.0 #requires -Module ConnectAD #requires -RunAsAdministrator #requires -PSEdition Core
Damit lassen sich Laufzeitbedingungen explizit festlegen. Ein Skript dokumentiert nicht nur, was es tut, sondern auch unter welchen Bedingungen es lauffähig ist.
Gerade in Umgebungen mit unterschiedlichen PowerShell-Versionen oder Mischbetrieb aus Windows PowerShell und PowerShell Core wird diese Klarheit relevant.
Strategische Bedeutung für die Serie
Im weiteren Verlauf dieser Beitragsreihe werden mehrere Module entstehen:
- ConnectAD für die Verbindungslogik
- weitere Tool-Module für Benutzeranlage
- Module für Gruppenlogik
- Module für Reporting
Ein Controller-Skript kann diese Module gezielt deklarieren:
#requires -Module ConnectAD #requires -Module CompanyADUser #requires -Module CompanyADGroups
Damit entsteht eine saubere Modularchitektur. Der Controller beschreibt seine Abhängigkeiten explizit. Worker-Funktionen bleiben in ihren jeweiligen Modulen gekapselt.
Diese Vorgehensweise stärkt:
- Wartbarkeit
- Lesbarkeit
- Reproduzierbarkeit
- strukturelle Klarheit
Abgrenzung zu Autoloading und Import-Module
Zusammenfassend lassen sich die Mechanismen wie folgt einordnen:
- Autoloading: komfortabel, aber implizit
- Import-Module: kontrolliert, aber Teil des Ausführungsflusses
- #requires: deklarativ, strukturell, vor Ausführung geprüft
Im professionellen Toolmaking ist #requires kein Ersatz für Modulstruktur, sondern deren logische Ergänzung.
Erste fachliche Automatisierung: Ein Benutzerkonto strukturiert erstellen
Mit dem Modul ConnectAD und einem funktionierenden Controller ist die technische Grundlage gelegt. Nun beginnt die eigentliche Fachautomatisierung. In unserem Szenario bedeutet das: Auf Basis von HR-Daten soll ein neues Benutzerkonto im Active Directory entstehen.
Auch hier gilt das etablierte Prinzip: Der Controller steuert, die Worker-Funktion arbeitet. Die Benutzeranlage gehört nicht in den Controller, sondern in eine dedizierte Funktion. Dadurch bleibt der Workflow modular, testbar und erweiterbar.
Eine erste Version dieser Worker-Funktion könnte New-CompanyADUser heißen und sich ausschließlich auf die Kontoerstellung konzentrieren.
Klare Eingaben statt impliziter Annahmen
Die Funktion definiert ihre Eingaben explizit. Für den aktuellen Stand genügen drei Parameter:
- Vorname
- Nachname
- Abteilung
function New-CompanyADUser { [CmdletBinding()] Param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$GivenName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Surname, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Department ) Write-Verbose -Message "Starte Kontenerstellung für $GivenName $Surname." }
Die Funktion enthält keine Remoting-Logik. Sie geht davon aus, dass die Verbindung bereits durch den Controller sichergestellt wurde.
Identitäten bewusst generieren
Ein professionelles Benutzerkonto benötigt mehr als nur einen Anzeigenamen. Insbesondere SamAccountName und UserPrincipalName müssen kontrolliert erzeugt werden.
Ein einfaches, nachvollziehbares Namensschema kann lauten:
- erster Buchstabe des Vornamens
- vollständiger Nachname
- alles in Kleinbuchstaben
$SamAccountName = ($GivenName.Substring(0,1) + $Surname).ToLowerInvariant() $UserPrincipalName = "$SamAccountName@firma.de"
Diese Ableitung ist bewusst transparent. Die Logik ist nachvollziehbar und kann bei Bedarf angepasst werden. Identitäten entstehen nicht zufällig, sondern folgen einer klaren Regel.
Gerade im Kontext von Toolmaking ist diese Transparenz entscheidend. Namensmuster sind Teil der Fachlogik und sollten explizit definiert werden.
Idempotente Grundprüfung
Bevor ein neues Konto angelegt wird, sollte geprüft werden, ob ein entsprechendes Objekt bereits existiert. Für diesen zweiten Teil der Serie beschränken wir uns auf eine einfache Prüfung anhand des SamAccountName.
$ExistingUser = Get-ADUser -Filter "SamAccountName -eq '$SamAccountName'" -ErrorAction 'SilentlyContinue'
Wird ein Benutzerobjekt gefunden, liefert die Funktion ein strukturiertes Statusobjekt zurück:
if ($ExistingUser) { return [PSCustomObject]@{ Action = 'Skipped' Identity = $SamAccountName Success = $true Message = 'Benutzer existiert bereits.' Timestamp = Get-Date } }
Damit ist eine grundlegende Idempotenz erreicht. Mehrfache Ausführung führt nicht zu Duplikaten, sondern zu einem stabilen Ergebnis.
Benutzerkonto erstellen und Status zurückgeben
Existiert kein Benutzer mit dem erzeugten SamAccountName, erfolgt die Anlage:
New-ADUser -GivenName $GivenName -Surname $Surname -Name "$GivenName $Surname" ` -SamAccountName $SamAccountName -UserPrincipalName $UserPrincipalName ` -Enabled $true -Department $Department
Auch hier endet die Funktion nicht mit einer Konsolenausgabe, sondern mit einem strukturierten Ergebnis:
return [PSCustomObject]@{ Action = 'Created' Identity = $SamAccountName Success = $true Message = 'Benutzer erfolgreich erstellt.' Timestamp = Get-Date }
Die Worker-Funktion liefert Daten, keine Texte. Diese Daten kann der Controller sammeln, auswerten und später aggregieren.
Zwischenfazit: Kontoerstellung ist nur der Anfang
Mit der strukturierten Generierung von SamAccountName und UserPrincipalName endet dieser zweite Teil bewusst an einem klar definierten Punkt: Ein Benutzerkonto kann reproduzierbar, nachvollziehbar und kontrolliert angelegt werden.
Doch ein Onboarding-Prozess umfasst deutlich mehr als die reine Kontoerstellung. Gruppenmitgliedschaften, Konfliktstrategien, Passwortkonzepte, SupportsShouldProcess, Reporting und Workflow-Aggregation sind integrale Bestandteile eines belastbaren Automationsdesigns.
- Die Verbindung ist gekapselt.
- Das Modul ist bereitgestellt.
- Der Controller orchestriert.
- Die erste Worker-Funktion arbeitet strukturiert.
Im nächsten Teil vertiefe und erweitere ich diesen Ansatz. Dann geht es um robuste Identitätsstrategien, Konfliktbehandlung und die Integration von Gruppenlogik in einen echten Onboarding-Workflow.
Aktueller Stand der Worker-Funktion
Die Benutzeranlage wurde im vorherigen Kapitel schrittweise entwickelt. Zur besseren Übersicht steht nachfolgend der aktuelle konsolidierte Stand der Funktion New-CompanyADUser zur Verfügung.
Diese Version umfasst:
- explizites Parameterdesign
- saubere SamAccountName- und UPN-Generierung
- grundlegende idempotente Existenzprüfung
- strukturierte Statusrückgabe
function New-CompanyADUser { [CmdletBinding()] Param ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$GivenName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Surname, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Department ) Write-Verbose -Message "Starte Kontenerstellung für $GivenName $Surname." # SamAccountName und UPN generieren $SamAccountName = ($GivenName.Substring(0,1) + $Surname).ToLowerInvariant() $UserPrincipalName = "$SamAccountName@firma.de" # Idempotente Prüfung $ExistingUser = Get-ADUser -Filter "SamAccountName -eq '$SamAccountName'" -ErrorAction 'SilentlyContinue' if ($ExistingUser) { return [PSCustomObject]@{ Action = 'Skipped' Identity = $SamAccountName Success = $true Message = 'Benutzer existiert bereits.' Timestamp = Get-Date } } # Benutzer erstellen New-ADUser -GivenName $GivenName -Surname $Surname -Name "$GivenName $Surname" ` -SamAccountName $SamAccountName -UserPrincipalName $UserPrincipalName ` -Enabled $true -Department $Department return [PSCustomObject]@{ Action = 'Created' Identity = $SamAccountName Success = $true Message = 'Benutzer erfolgreich erstellt.' Timestamp = Get-Date } }
Status Quo und Ausblick: Das Fundament steht
Der zweite Teil dieser Reihe hat bewusst nicht versucht, den gesamten Onboarding-Prozess abzubilden. Stattdessen wurde ein stabiler technischer Rahmen geschaffen, auf dem weitere Automatisierung aufbauen kann.
Konkret wurde:
- Remoting als infrastrukturelle Voraussetzung gekapselt
- das Modul ConnectAD strukturiert bereitgestellt
- die Funktion Connect-ADDomain als wiederverwendbares Tool entwickelt
- ein Controller-Skript als Orchestrator etabliert
- eine erste Worker-Funktion zur Benutzeranlage implementiert
- eine nachvollziehbare Generierung von SamAccountName und UserPrincipalName eingeführt
- eine grundlegende idempotente Prüfung integriert
Damit ist die technische Basis geschaffen, um Active-Directory-Automatisierung nicht nur auszuführen, sondern strukturiert zu gestalten.
Architektonische Prinzipien im Überblick
Neben der reinen Funktionalität standen in diesem Beitrag bewusst architektonische Leitlinien im Fokus:
- Infrastruktur und Fachlogik wurden getrennt
- Remoting wurde zentral gekapselt
- Statusinformationen wurden strukturiert zurückgegeben
- Module wurden sauber bereitgestellt
- implizite Abhängigkeiten wurden vermieden
Diese Prinzipien sorgen dafür, dass das Tool nicht nur funktioniert, sondern erweiterbar bleibt. Genau an dieser Stelle unterscheidet sich Toolmaking von ad-hoc-Scripting.
Bewusst noch nicht behandelt
Der aktuelle Stand bildet lediglich die erste Stufe eines professionellen Onboarding-Prozesses ab. Mehrere zentrale Aspekte wurden bewusst ausgeklammert und werden im nächsten Teil vertieft:
- Konfliktbehandlung bei doppelten Identitäten
- robuste UPN- und Namensstrategien
- SupportsShouldProcess und kontrolliertes -WhatIf-Verhalten
- sichere Initialkennwörter
- Abteilungsgruppen und Berechtigungslogik
- Aggregation mehrerer Statusobjekte
- erste Reporting-Strukturen
Die reine Erstellung eines Benutzerkontos ist nur der Einstieg. Ein belastbarer Onboarding-Prozess ist deutlich breiter angelegt.
Der nächste Schritt
Mit dem aktuellen Stand existiert:
- ein gekapselter Verbindungsmechanismus
- ein importierbares Modul
- ein steuernder Controller
- eine funktionierende Benutzeranlage
Damit ist das Fundament gelegt.
Im nächsten Teil wird dieses Fundament erweitert. Die Benutzeranlage wird robuster gestaltet, Identitätskonflikte werden systematisch behandelt und der Workflow um weitere Onboarding-Schritte ergänzt.
- Die Verbindung steht.
- Das Modul ist etabliert.
- Der erste Benutzer wird erstellt.
Ab jetzt beginnt die eigentliche Komplexität.
Quellenangaben
(Abgerufen am 14.02.2026)
Remoting und implizites Remoting
- Adam Bertram (4sysops): Using PowerShell implicit remoting
- Adam Bertram (Computer Things): PowerShell implicit remoting
- Microsoft Learn: About Remote Requirements
- Microsoft Learn: About Windows PowerShell Compatibility
- Microsoft Learn: ImplicitRemotingCommandBase Class
- Microsoft Learn: PowerShell Remoting
- Microsoft Scripting Blog: An Introduction to PowerShell Remoting – Part Four: Sessions and Implicit Remoting
- Microsoft Scripting Blog: Remoting the implicit way
Module, Manifest und Toolmaking
- Adam Bertram (Progress Blog): Understanding PowerShell Toolmaking
- Microsoft Learn: About Module Manifests
- Microsoft Learn: About Modules
- Microsoft Learn: How to Create a Windows PowerShell Snap-in
- Microsoft Learn: How to Import Cmdlets Using Modules
- Microsoft Learn: Import-Module
- Microsoft Learn: Introducing Scripting and Toolmaking
- Microsoft Learn: Modules and Snap-Ins
- Packt Publishing: PowerShell Toolmaking Repository
Active Directory Modul
Microsoft Learn: ActiveDirectory Module for Windows PowerShell
Idempotenz und Automationsprinzipien
- Berk Anasmaz: Every Programmer Should Know #1: Idempotency
- Florian Klima (LinkedIn): IT Häppchen: Idempotenz
- Vance Lim (ShadeCoder): An Idempotent Operation – A Comprehensive Guide for 2025
Weiterlesen hier im Blog
- PowerShell – Parallelisierung in der Praxis
- PowerShell Remoting verstehen: Von Ad-hoc bis One-to-Many
- PowerShell verstehen – Execution Policy, Codesignatur und Zone.Identifier richtig einsetzen
- PowerShell-Konstrukte im Überblick – Schleifen, Bedingungen und Fehlerbehandlung professionell eingesetzt
- Toolmaking-Grundlagen in PowerShell – Warum nachhaltige Automatisierung mit Architektur beginnt
