PowerShell: Registry-Hives laden, bearbeiten und entladen – Teil 1

Alle paar Monate muss ich – mal im Einzelfall, mal massenweise – ein „fremdes“ Registry-Hive zur Bearbeitung öffnen. In der Regel geht es darum, ein lokal gespeichertes Benutzer-Hive im SYSTEM-Kontext zu laden, manchmal kann es aber auch nötig sein, auf ein SYSTEM-Hive zuzugreifen, wenn das betreffende System gerade nicht läuft. In Windows ist dafür entweder der Menüpunkt „Datei – Struktur laden…“ in REGEDIT oder der Befehl REG LOAD vorgesehen. Funktional sind beide Verfahren identisch: Man kann einen als Datei vorliegenden Registry-Hive entweder in HKEY_LOCAL_MACHINE oder in HKEY_USERS als Schlüssel auf der obersten Ebene einbinden und fortan so verfahren, als wäre er dort „nativ“ enthalten.

Ein Wort der Warnung: Diese Einbindung überlebt einen Reboot, somit bleibt die Datei für den Zugriff durch andere Sitzungen gesperrt. Das ist besonders wichtig, wenn man lokal gespeicherte NTUSER.DAT „anfasst“ – der betroffene User kann sich nicht anmelden, bis man die Datei wieder – mit REGEDIT (Datei – Struktur entfernen…“) bzw. REG UNLOAD wieder entladen hat. Und genau darum geht es in diesem Beitrag.

Das Problem

PowerShell bringt von sich aus kein neues Werkzeug zum Laden und Entladen von Registry-Hives mit, in Skripten ist man also auf REG LOAD mit anschließendem REG UNLOAD angewiesen. Und die REG.EXE ist nicht gerade die Interoperabilitäts-Kanone. Möchte man damit robust skripten, ist also einiger Aufwand nötig.

Welche anderen Möglichkeiten es gibt, „fremde“ Registry-Hives zu bearbeiten, zeige ich im Teil 2 dieses Posts.

Drum prüfe, wer das Hive einbindet…

REG LOAD prüft nicht, ob die angegebene Datei tatsächlich existiert. Ist es nicht der Fall, so wird ohne Rückfrage eine neue Datei angelegt. Sucht man nach dem Laden nach bestimmten Keys, so wird natürlich spätestens dann auffallen, dass irgendwas nicht stimmt. Nutzt man PowerShell aber nur als Wrapper, z.B. für SetACL, so wird alles fehlerfrei ablaufen, und das gewünschte Ergebnis ist trotzdem nicht erreicht. Besser, man prüft, ob die Datei auch wirklich da ist:

$RegFile = "C:\Users\some.user\NTUSER.DAT"
$RegKey = "SomeUser"
if (Test-Path $RegFile -PathType Leaf) {
    reg load "HKU\$RegKey" $RegFile
    # some code
    reg unload "HKU\$RegKey"
}

Für REG LOAD kann man natürlich auch ausgeklügeltere Wrapper schreiben, die den Aufruf per Invoke-Expression tätigen und somit die Ausgabe auswerten. Das ist aber schon deswegen umständilich, weil die Ausgaben eines Shell-Befehls ja in der Systemsprache sind. Und da es von diesen Ausgaben nicht sehr viele Variationen gibt, ist der Gewinn daraus nicht wirklich groß. Aber zum Protokollieren kann man die Ausgabe sicher gerne festhalten.

Bei Einbindung in HKU wird man sehr schnell feststellen, dass PowerShell von Hause aus kein Laufwerk für diesen Zweig erzeugt, das direkte Adressieren des neu eingebundenen Hives ist also nicht möglich. Hier schafft der folgende Befehl Abhilfe, den man einmalig vor dem ersten Zugriff auf die geladenen Hives ins Skript einbinden muss:

if (!(Get-PSDrive HKU -EA SilentlyContinue)) { New-PSDrive -PSProvider "Registry" -Name "HKU" -Root "HKEY_USERS" }

Dennoch rate ich dazu, für User-Hives HKU zu verwenden, da ich schon mehrmals erlebt habe, dass neue Schlüssel im HKLM-Zweig übermäßig wachsame Monitoring- und SIEM-Agenten in Wallung versetzt haben.

Gesperrt – was tun?

Der häufigste Fall, gerade beim Laden von NTUSER.DATs ist, dass der User noch (oder bereits) angemeldet ist und die Datei daher gesperrt ist. REG.EXE wirft dann einen „NativeCommandError“, was aber in PowerShell nicht terminierend ist. Um den Fehler also mit Try/Catch abzufangen, muss man die ErrorActionPreference global auf „Stop“ stellen – entweder unmittelbar vor dem Aufruf, oder global zu Beginn des Skriptes, so wie hier:

$ErrorActionPreference = "Stop"
$RegFile = "C:\Users\some.user\NTUSER.DAT"
$RegKey = "SomeUser"
if (Test-Path $RegFile -PathType Leaf) {
    try {
        reg load "HKU\$RegKey" $RegFile
        $RegLoadOK = $true
    } catch {
        Write-Warning "Registry konnte nicht geladen werden: $RegFile"
        $RegLoadOK = $false
    }
    if ($RegLoadOK) {
        # some code
        reg unload "HKU\$RegKey"
    }
}

Entladen unmöglich?

Hat man das Hive erfolgreich geladen und mit dem Inhalt hantiert, so wird man manchmal feststellen, dass beim Entladen die Meldung „Zugriff verweigert“ ausgegeben wird. In diesem Fall bleiben häufig noch unaufgeräumte Bezüge zu Registry im Speicher der aktuellen PowerShell-Sitzung  hängen. Diese räumt der Garbage Collector von .NET irgendwann auf, zum schnellen Entladen der Registry müssen wir ihn aber explizit dazu auffordern:

[gc]::Collect()

Bei vielen offenen Bezügen oder stark ausgelasteten Systemen kann es sinnvoll sein, auf den Abschluss der Aufräumvorgänge zu warten:

[gc]::Collect()
[gc]::WaitForPendingFinalizers()

Items sind tricky!

Mit allen obigen Hinweisen ergibt sich also für das Lesen und Schreiben von Registry-Werten der folgende Code, der normalerweise auch ohne Probleme funktionieren wird:

$ErrorActionPreference = "Stop"
$RegFile = "C:\Users\some.user\NTUSER.DAT"
$RegKey = "SomeUser"
if (!(Get-PSDrive HKU -EA SilentlyContinue)) { New-PSDrive -PSProvider "Registry" -Name "HKU" -Root "HKEY_USERS" }
if (Test-Path $RegFile -PathType Leaf) {
    try {
        reg load "HKU\$RegKey" $RegFile
        $RegLoadOK = $true
    } catch {
        Write-Warning "Registry konnte nicht geladen werden: $RegFile"
        $RegLoadOK = $false
    }
    if ($RegLoadOK) {        
        Get-ItemPropertyValue -Path "HKU:\$RegKey\Environment" -Name "Temp"
        Get-ItemProperty -Path "HKU:\$RegKey\Environment" -Name "Temp"
        Set-ItemProperty -Path "HKU:\$RegKey\Environment" -Name "SomeName" -Value "SomeValue"
        [gc]::Collect()
        [gc]::WaitForPendingFinalizers()
        try {
            reg unload "HKU\$RegKey"
        } catch {
            Write-Warning "Registry-Hive konnte nicht entladen werden!"
        }
    }
}

Ersetzt man jedoch die *-ItemPoperty*-Operationen durch den scheinbar ähnlich harmlosen Befehl

Get-Item -Path "HKU:\$RegKey\Environment"

so wird man zwei Dinge feststellen:

  1. Das anschließende Entladen des Hive schlägt fehl
  2. Sobald man die aktuelle PowerShell (oder die ISE) beendet hat, klappt das Entladen ohne Probleme.

Untersucht man nun das Objekt, das durch Get-Item erzeugt wurde, entdeckt man eine vielversprechende Eigenschaft:

Diese hat wiederum eine Methode, die in unserem Fall sehr hilfreich ist: .Close(). Der sichere Aufruf  sieht also wie folgt aus:

$item = Get-Item -Path "HKU:\$RegKey\Environment"
if (!($item.Handle.IsClosed)) {
    $item.Handle.Close()
}

Alles für Get-Item Gesagte gilt genau so auch für New-Item. Auch hier sollte das neu angelegte Item in eine Variable aufgefangen werden, um anschließend das Handle zu schließen!

ACHTUNG! Für jedes neue Item-Objekt wird ein neues Handle erzeugt, man muss sie also zwingend in Variablen festhalten und die Handles jeweils dort schließen, wo sie geöffnet wurden. Das Löschen der Variablen, das Setzen auf $null oder das Entfernen des PSDrive haben nicht den Effekt, dass die Handles geschlossen werden!

Um die „vergessenen“ Handles zu suchen, sei einem das Modul POSHInternals von Adam Driscoll empfohlen. Schön ist das Ganze aber auch damit nicht, also lieber aufpassen und die Handles aller Items gleich schließen!

Welche anderen Möglichkeiten es gibt, „fremde“ Registry-Hives zu bearbeiten, zeige ich im Teil 2 dieses Posts.

Ersten Kommentar schreiben

Antworten

Deine E-Mail-Adresse wird nicht veröffentlicht.


*


Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.