Zum Inhalt

Benutzerdefinierte Sealing-Skripte

PVS Forge kann Ihre eigenen Skripte auf dem Master Target rund um den Imaging-Lauf ausführen — ein Pre-Sealing-Skript vor der Image-Aufnahme (Dienste stoppen, maschinenspezifische Rückstände bereinigen, ein Hersteller-Cleanup-Tool laufen lassen) und ein symmetrisches Post-Sealing-Skript danach (alles wieder aktivieren, was Sie deaktiviert haben). Diese Seite erklärt, wie die Skripte aufgebaut sein müssen, damit sie sauber laufen, und liefert fertige Vorlagen.

Wann und wie sie laufen

Die beiden Skripte rahmen den Imaging-Lauf:

Pre-Sealing-Skript  ->  Sealing  ->  Imaging  ->  Post-Sealing-Skript  ->  Reaktivierung
  • Das Pre-Sealing-Skript läuft nach der Pending-Reboot-Prüfung und bevor die Auto-Updates deaktiviert werden — also bevor das Image erstellt wird. Alles, was es ändert, landet in der vDisk.
  • Das Post-Sealing-Skript läuft nach dem Imaging und bevor die Auto-Updates wieder aktiviert werden. Nutzen Sie es, um rückgängig zu machen, was das Pre-Sealing-Skript getan hat — z. B. die vom Pre-Sealing-Skript deaktivierten Dienste und Update-Tasks wieder aktivieren, damit das Master Target in einen lauffähigen Zustand zurückkehrt.
  • Beide laufen erhöht unter dem Imaging-Konto, nicht-interaktiv (headless), ohne interaktives Benutzerprofil oder Desktop.
  • Unterstützte Dateitypen (für beide Skripte identisch):
    • .ps1powershell.exe -NonInteractive -ExecutionPolicy Bypass -File "<Pfad>"
    • .cmdcmd.exe /c "<Pfad>"
  • Jeder Lauf wird im Seal/Unseal-Log (<vDisk>.seal-unseal.log im Store) mit Startzeit, Befehl, stdout, stderr, Exit-Code und Dauer protokolliert — sodass Sie genau sehen, was Ihr Skript getan hat.

Pfade, Timeouts und das Fehlerverhalten konfigurieren Sie unter Einstellungen → vDisk & Imaging.

Pfade und Umgebungsvariablen

Beide Skript-Pfade dürfen auf einen lokalen Pfad auf dem Master Target oder eine vom Master Target erreichbare UNC-Freigabe zeigen — z. B. ein zentrales Skript auf NETLOGON. PVS Forge holt ein UNC-Skript zunächst in eine temporäre lokale Kopie (mit den Imaging-Anmeldedaten) und führt diese aus — so funktioniert es, obwohl das Imaging-Konto keine interaktive Netzwerksitzung hat.

Umgebungsvariablen im Pfad werden auf dem Master Target aufgelöst:

Unterstützt Beispiele
Maschinen-Variablen %SystemRoot%, %windir%, %SystemDrive%, %ProgramFiles%, %ProgramFiles(x86)%, %ProgramData%, %ALLUSERSPROFILE%, %ComputerName%, %Public%
Domäne / Anmeldung (von PVS Forge aufgelöst) %LOGONSERVER%, %USERDNSDOMAIN%, %USERDOMAIN%
Nicht unterstützt — nicht verwenden Warum
%USERPROFILE%, %APPDATA%, %LOCALAPPDATA%, %HOMEPATH%, %USERNAME% Sie zeigen auf das Imaging-Dienstkonto, nicht auf einen echten Nutzer — nicht das, was Sie erwarten

Zentrale Skripte auf NETLOGON

\\%LOGONSERVER%\NETLOGON\seal.cmd funktioniert, aber \\<ihre-domaene>\NETLOGON\seal.cmd (z. B. \\contoso.com\NETLOGON\seal.cmd) ist robuster — es trifft immer einen lebenden Domänencontroller. UNC-Zugriff braucht Domänen-Anmeldedaten: das Imaging-Konto muss die Freigabe lesen können.

MT-spezifische Skripte mit %MT%

Bei mehreren Master Targets können Sie eine Skript-Konfiguration behalten und trotzdem zielspezifische Skripte ausführen — über den Platzhalter %MT% im Pfad:

Token Gilt für Beispiel (3 Targets)
%MT% alle Master Targets (kein Positions-Filter) läuft für 1, 2 und 3
%MT2% nur Position 2 läuft für 2
%MT13% Position 1 und 3 — jede Ziffer ist eine Position (nicht „dreizehn"), max. Position 9 läuft für 1 und 3

Die Position ist die laufende Nummer, die in der Master-Target-Liste vor jedem Eintrag angezeigt wird (oberster = 1). %MT% wird durch den Kurznamen (NetBIOS) des aktuellen Targets ersetzt und kann als Ordner oder als Dateiname-Bestandteil dienen:

  • \\%LOGONSERVER%\NETLOGON\%MT%\pre.ps1\\...\NETLOGON\SRV04\pre.ps1
  • \\%LOGONSERVER%\NETLOGON\scripts\pre-%MT13%.ps1 (Position 1) → ...\pre-SRV04.ps1

Verhalten:

  • Position nicht durch %MT<ziffern>% ausgewählt → das Skript wird für dieses Target schlicht nicht ausgeführt (kein Fehler, kein Abbruch).
  • Skript für dieses Target gemeint (%MT% für alle Targets oder eine getroffene %MT<ziffern>%-Position), aber nicht vorhanden → der Lauf wird vor dem Imaging abgebrochen. Ist ein Target für den Hook vorgesehen, muss sein Skript existieren — sonst gilt es als Konfigurationsfehler.
  • Ein fester Pfad (ohne %MT%-Token), der nicht existiert → ebenfalls Abbruch vor dem Imaging wie bisher.

Positionen sind dauerhaft

Die Position gehört fest zum Master Target und ändert sich nie, solange das Target in der Liste bleibt — weder durch Imaging-Läufe noch durch einen Neustart von PVS Forge noch durch das Hinzufügen weiterer Targets (Neue kommen ans Ende bzw. füllen eine Lücke, siehe unten). Ihre %MT<ziffern>%-Filter bleiben damit stabil.

Ein Target, das zu einem laufenden Imaging-Lauf gehört, kann nicht gelöscht werden — brechen Sie den Lauf zuerst ab (Stopp-Symbol an der Zeile), und entfernen Sie das Target danach.

Beim Löschen eines Targets behalten alle übrigen ihre Nummer — die Liste zeigt dann eine sichtbare Lücke (z. B. 1, 3). Das nächste neu hinzugefügte Target füllt die kleinste Lücke (im Beispiel: Position 2). Prüfen Sie nach Löschen/Neuanlegen, ob ein %MT<ziffern>%-Filter die neu besetzte Position meint. Bei mehr als 9 Targets sind Positionen ab 10 per Ziffern-Filter nicht adressierbar — dort den namensbasierten Weg (%MT% mit Hostnamen) verwenden, der ohnehin der empfohlene Standard ist.

Was ein Exit-Code ungleich null bewirkt

Ein Skript-Fehler bricht den Imaging-Lauf nie ab

  1. Ein fehlerhaftes Skript stoppt das Imaging nicht. Ein Exit-Code ungleich null, ein Timeout oder eine Exception wird ins Seal/Unseal-Log geschrieben (inklusive stdout/stderr) und im Live-Log angezeigt — der Lauf läuft aber weiter und die Reaktivierung erfolgt immer, sodass das Master Target nie im gesealten Zustand zurückbleibt.
  2. Ein Skript-Fehler wird immer gemeldet. Ein fehlerhaftes Skript markiert den Lauf mit einem Hinweis in Status und Benachrichtigungs-E-Mail — so sehen Sie es immer und halten ein stillschweigend fehlgeschlagenes Skript nicht für „alles in Ordnung". Das Imaging wird trotzdem abgeschlossen und die vDisk importiert.
  3. Ein fehlendes oder nicht unterstütztes Skript bricht vor dem Imaging ab. Ist ein Pfad konfiguriert, die Datei aber nicht vorhanden, oder ist die Endung nicht .ps1 oder .cmd, stoppt die Vorabprüfung den Lauf vor dem Imaging — es wurde noch nichts gesealed. Beide Skripte werden gemeinsam geprüft.
Situation Ergebnis
Exit-Code 0 Skript erfolgreich, kein Fehler protokolliert
Exit ungleich null / Timeout / Exception In Seal/Unseal- + Live-Log protokolliert, Lauf läuft weiter, und der Job wird in Status und E-Mail markiert
Pfad fehlt oder nicht unterstützte Endung Von der Vorabprüfung erkannt — Imaging bricht ab, bevor es startet

Beenden Sie jedes erfolgreiche Skript explizit mit exit 0 (PowerShell) bzw. exit /b 0 (CMD), damit das Log ein sauberes Ergebnis zeigt.

Die Regeln

Bauen Sie Ihre Skripte für den Dienst-Kontext

  1. Unbeaufsichtigt laufen. Kein pause, kein Read-Host, keine GUI — alles, was auf Eingabe wartet, blockiert bis zum Timeout.
  2. Timeout beachten. Läuft ein Skript länger als der konfigurierte Timeout (ein gemeinsamer Wert für beide Skripte, Standard 90 s), wird es beendet (und der Fehler protokolliert).
  3. Nur absolute Pfade. Das Arbeitsverzeichnis ist undefiniert.
  4. Kein Netzwerkzugriff zur Laufzeit. Das Imaging-Konto hat während der Skriptausführung keine interaktive Netzwerksitzung. Der Skript-Pfad darf eine UNC-Freigabe sein (PVS Forge holt ihn), aber im Skript selbst net use, Netzlaufwerke und das Lesen anderer Freigaben vermeiden — benötigte Dateien vorab lokal bereitstellen.

Fallen im Dienst-Kontext

Falle Problem Lösung
pause / Read-Host Blockiert bis zum Timeout Entfernen
net stop auf bereits gestoppten Dienst Exit-Code 2 (als Fehler protokolliert) sc stop verwenden (immer Exit 0)
net use / Freigaben im Skript lesen Keine Netzwerksitzung zur Laufzeit Vermeiden; Dateien vorab lokal bereitstellen
$env:APPDATA / $env:LOCALAPPDATA Zeigen auf das Dienstkonto-Profil Absolute Pfade verwenden
GUI-Aufrufe (notepad, mmc, …) Blockieren ohne Desktop Entfernen
Start-Process ohne -Wait Skript endet, während das Kind bis zum Timeout weiterläuft Immer -Wait
Relative Pfade Arbeitsverzeichnis unbekannt Absolute Pfade verwenden

sc statt net stop / net start bevorzugen

sc stop <Dienst> liefert immer Exit-Code 0 — auch wenn der Dienst bereits gestoppt ist oder nicht existiert. net stop gibt bei einem bereits gestoppten Dienst Exit-Code 2 zurück, was als Skript-Fehler protokolliert wird.

Dienste deaktivieren — nicht nur stoppen

Ein gestoppter, aber weiterhin aktivierter Dienst startet beim Boot des Ziels wieder — Stoppen allein hält einen Updater also nicht von der laufenden Maschine fern. Setzen Sie im Pre-Sealing-Skript den Starttyp auf Disabled und im Post-Sealing-Skript zurück auf Automatic (oder Manual). Dasselbe gilt für Update-Scheduled-Tasks — viele Hersteller starten Updates über den Taskplaner: im Pre deaktivieren, im Post wieder aktivieren.

Vorlagen

Pre-Sealing (seal.ps1)

# seal.ps1 - Pre-Sealing-Vorlage fuer PVS Forge
# Laeuft headless unter dem Imaging-Konto, BEVOR das Image aufgenommen wird.
# Regeln: keine Eingabe-Prompts, absolute Pfade, bei Erfolg mit exit 0 enden.

$ErrorActionPreference = 'Stop'

try {
    # --- Updater/Dienste DEAKTIVIEREN, damit sie auf dem gebooteten Ziel nicht starten ---
    # Deaktivieren, nicht nur stoppen: ein gestoppter, aber aktivierter Dienst startet beim Boot.
    foreach ($svc in 'MyUpdater', 'AnotherSvc') {
        Set-Service  -Name $svc -StartupType Disabled -ErrorAction SilentlyContinue
        Stop-Service -Name $svc -Force                -ErrorAction SilentlyContinue
    }

    # --- Hersteller-Update-Scheduled-Tasks deaktivieren (viele Updater laufen ueber den Taskplaner) ---
    Disable-ScheduledTask -TaskName 'MyVendorUpdate' -ErrorAction SilentlyContinue | Out-Null

    # --- Maschinenspezifische Rueckstaende entfernen ---
    Remove-ItemProperty -Path 'HKLM:\SOFTWARE\MyApp' -Name 'LastUser' -ErrorAction SilentlyContinue
    Remove-Item -Path 'C:\ProgramData\MyApp\cache\*' -Recurse -Force -ErrorAction SilentlyContinue

    # --- Externes Cleanup-Tool starten und DARAUF WARTEN ---
    # Ohne -Wait wuerde das Skript enden, bevor das Tool fertig ist.
    Start-Process -FilePath 'C:\Tools\cleanup.exe' -ArgumentList '/silent' -Wait

    Write-Output 'Pre-Sealing abgeschlossen.'
    exit 0
}
catch {
    # Die Meldung landet im Seal/Unseal-Log; der Lauf laeuft trotzdem weiter.
    Write-Error "Sealing-Skript fehlgeschlagen: $($_.Exception.Message)"
    exit 1
}

Post-Sealing (unseal.ps1)

# unseal.ps1 - Post-Sealing-Vorlage fuer PVS Forge
# Laeuft headless unter dem Imaging-Konto, NACH dem Imaging, VOR der Reaktivierung.
# Aktiviert GENAU das wieder, was seal.ps1 deaktiviert hat (lauffaehiger Boot-Zustand).

$ErrorActionPreference = 'Stop'

try {
    foreach ($svc in 'MyUpdater', 'AnotherSvc') {
        Set-Service   -Name $svc -StartupType Automatic -ErrorAction SilentlyContinue
        Start-Service -Name $svc                        -ErrorAction SilentlyContinue
    }

    Enable-ScheduledTask -TaskName 'MyVendorUpdate' -ErrorAction SilentlyContinue | Out-Null

    Write-Output 'Post-Sealing abgeschlossen.'
    exit 0
}
catch {
    Write-Error "Post-Sealing-Skript fehlgeschlagen: $($_.Exception.Message)"
    exit 1
}

CMD (seal.cmd)

@echo off
REM seal.cmd - Pre-Sealing-Vorlage fuer PVS Forge
REM Laeuft headless unter dem Imaging-Konto. sc verwenden (immer Exit 0), nie "net stop".

REM Deaktivieren (nicht nur stoppen), damit der Updater beim Boot des Ziels aus bleibt.
sc config MyUpdater start= disabled
sc stop MyUpdater
sc config AnotherSvc start= disabled
sc stop AnotherSvc

REM Hersteller-Update-Scheduled-Task deaktivieren
schtasks /Change /TN "MyVendorUpdate" /DISABLE

del /q "C:\ProgramData\MyApp\cache\*"

REM Bei Erfolg immer mit exit 0 beenden
exit /b 0

Vor dem Produktiveinsatz testen

Führen Sie das Skript manuell auf dem Master Target in einem nicht-interaktiven, erhöhten Kontext aus, um die echte Umgebung anzunähern — z. B. mit PsExec (-s läuft als SYSTEM, nah genug, um Headless-/Profil-Probleme zu finden):

psexec -s -i powershell.exe -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\seal.ps1"
echo $LASTEXITCODE   # 0 = sauber; ungleich null wird protokolliert, stoppt das Imaging aber nicht

Läuft es ohne Eingabeaufforderung durch und liefert 0, läuft es auch während des Sealings sauber.