PowerShell Toolmaking in der Praxis: Active Directory per Remoting anbinden

14. Februar 2026

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
Dieses Skript ist weiterhin kein Tool im Sinne des Toolmakings, aber es erfüllt eine wichtige Aufgabe: Es macht die Struktur sichtbar, die später in einer Funktion zuverlässig kapsuliert wird. Die folgenden Schritte erweitern nun genau dieses Grundgerüst um Session-Reuse, kontrollierten Modulimport, Statusobjekte und eine klare Fehlerstrategie.

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 ConnectAD

Damit 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
Hier wird deutlich: Der Controller interessiert sich nicht für die Details des Remotings. Er verlässt sich vollständig auf den Rückgabevertrag des Connect-Tools. Die Entscheidung, ob weitergearbeitet wird, basiert ausschließlich auf strukturierten Statusdaten.

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.

  1. Die Verbindung ist gekapselt.
  2. Das Modul ist bereitgestellt.
  3. Der Controller orchestriert.
  4. 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.

  1. Die Verbindung steht.
  2. Das Modul ist etabliert.
  3. Der erste Benutzer wird erstellt.

Ab jetzt beginnt die eigentliche Komplexität.

Quellenangaben

(Abgerufen am 14.02.2026)

Remoting und implizites Remoting

Module, Manifest und Toolmaking

Active Directory Modul

Microsoft Learn: ActiveDirectory Module for Windows PowerShell

Idempotenz und Automationsprinzipien

Weiterlesen hier im Blog