Powershell durchsucht Registry flexibler als Regedit

23. Juli 2012

Mit Hilfe der Powershell von Windows lässt sich die Registry nicht nur flexibler, sondern auch schneller durchsuchen – verglichen mit dem Standard-Programm Regedit. Wer das Skript Search-Registry.ps1 verwendet, der braucht nicht mehr hunderte Male auf F3 drücken, um von einem Suchergebnis zum nächsten zu kommen – wie das bei Regedit üblich ist: Nur mehr ein Powershell-Kommandofenster öffnen und dort Search-Registry.ps1 einsetzen und die Aufgabe ist erledigt.

Für Abonnenten ist diese Skript-Lösung kostenlos verfügbar. Search-Registry.ps1 verbessert die Suchfunktion von Regedit auf vier Arten:

  • Das Skript sucht mithilfe des Einsatzes von regulären Ausdrücken.
  • Das Tool durchsucht auch die Registry von entfernten Systemen.
  • Das Skript begrenzt die Anzahl der zurückgelieferten Suchergebnisse.
  • Die Ausgabeobjekte lassen sich filtern, sortieren oder in CSV-Dateien exportieren – die üblichen Aktionen, die im Rahmen der Powershell zur Verfügung stehen.
Bild 2. So spielen die Größen im Skript mit den Objekten in der Registry zusammen.

Wer sich mit der Verwaltung von Windows-Systemen zu befassen hat, der kennt auch die Registry. Sie wurde ursprünglich als eine Art von Hilfe für das Betriebssystem konzipiert – und zwar in erster Linie um Informationen über Dateitypen zu speichern. Doch mit Windows NT 3.51 und Windows 95 wurde die Registry zum Standardort, an dem das Betriebssystem und die darauf agierenden Anwendungen Konfigurationsinformationen ablegen und auch schnell wieder auslesen.

Mit Windows 95 wurde auch der Regedit eingeführt – das Standard-Interface für den Zugriff aus einer grafischen Benutzeroberfläche auf die Registry. Bei Windows NT gab es einen anderen Registrierungs-Editor: den Regedt32 (eine 32-Bit-Version). Dieser bot Unterstützung für mehrere Registry-Typen als es beim Regedit der Fall war. Bei Windows NT 4.0 und Windows 2000 standen dann beide Registrierungs-Editoren zur Verfügung.

Mit Windows XP gab es von Microsoft eine Erweiterung für den Regedit: Er unterstützte dieselben Datentypen als Regedt32 – und damit wurde dieser obsolet.

Schwächen des Regedit

Mittlerweile hat sich Regedit im täglichen Einsatz als ein sehr nützliches Tool erwiesen. Doch bei einer Aufgabenstellung hat es große Schwächen: die Suchfunktion. Die zugehörige – rudimentär gestaltete – Benutzeroberfläche zeigt die Abbildung 1.

Mit der Tastenkombination Strg+F kommt der Suchdialog. Hier kann man einen Suchtext (als Zeichenfolge) eingeben und dann gibt es noch die vier Checkboxen (Schlüssel, Daten, Werte und „Ganze Zeichenfolge vergleichen“). Wenn man nach dem ersten Ergebnis weitersuchen möchte, kann man noch die Funktionstaste F3 drücken und springt dann zum nächsten Treffer. Das kann in der Praxis mit der Zeit richtig „nervtötend“ sein – vor allem wenn die Suche sehr viele Ergebnisse geliefert hat.

Aufgrund dieser Schwächen bot es sich an, das Powershell-Skript „Search-Registry.ps1“ zu schreiben. Es kann die Registry weitaus flexibler als Regedit durchsuchen. Dieses Skript steht für Abonnenten von NT4ADMINS zum kostenlosen Download bereit (Bitte das Skript zuerst in einer Testumgebung ausprobieren, ehe man damit auf „Produktivsysteme“ losgeht).

Search-Registry.ps1 verbessert die Suchfunktion von Regedit auf vier Arten:

  • Das Skript sucht mithilfe des Einsatzes von regulären Ausdrücken.
  • Das Tool durchsucht auch die Registry von entfernten Systemen.
  • Das Skript begrenzt die Anzahl der zurückgelieferten Suchergebnisse.
  • Die Ausgabeobjekte lassen sich filtern, sortieren oder in CSV-Dateien exportieren – die üblichen Aktionen, die im Rahmen der Powershell zur Verfügung stehen.

Einige Powershell-Kenner werden wissen, dass die Powershell selbst „Registry-Drives“ – wie etwa HKLM – für den Zugriff auf die Registry zur Verfügung stellt. Doch diese „Drives“ funktionieren in der Powershell-Version 1.0 nur auf dem lokalen System – ein Arbeiten auf entfernten Computern ist damit leider nicht möglich.

Daher könnte man mit dem Feature „Powershell Remoting“  die gewünschte Funktionalität erreichen – doch dazu ist die Version 2.0 der Powershell nötig: Sie muss dann auch allen entfernten Systemen (die man damit ansprechen will)  installiert sowie aktiviert sein. Doch diese Voraussetzung sicherzustellen, würde die Einsatzmöglichkeiten des Skripts erheblich einschränken. Daher wurde ein anderer – weitaus breiter gefasster – Ansatz gewählt.

Bild 3. So zeigt sich das Skript Search-Registry.ps1 im Einsatz

Verwendung von Search-Registry.ps1

Wer das Skript Search-Registry.ps1 verwenden will, der muss die folgende Syntax beachten:

Search-Registry [-StartKey] <String> [-Pattern] <String> [-MatchKey] [-MatchValue] [-MatchData] [-MaximumMatches <n>] [-ComputerName <String[]>]

Der Parameter -StartKey gibt der Registry-Schlüssel an, ab dem die Suche starten soll. Das entspricht der Angabe des Startpunkts für die Suche im linken Darstellungsbereich des Regedit, ehe man Strg+F eingibt. Dieser Parameter verwendet das folgende Format:

subtree:\key

Dabei darf der „subtree“ entweder ein abgekürzter Powershell-Drive-Name sein oder auch der komplette Name des Subtrees, wie man ihn aus Regedit kennt:
•    HKCR oder HKEY_CLASSES_ROOT
•    HKCU oder HKEY_CURRENT_USER
•    HKLM oder HKEY_LOCAL_MACHINE
•    HKU oder HKEY_USERS.

Die Angabe des Doppelpunkts nach dem Namen des Subtree ist optional. Der „key“ gibt an, wo die Suche anfangen soll. Lässt man den Key weg oder gibt man nur das Backslash-Zeichen (\) an, durchsucht das Skript den kompletten Subtree. Zum Beispiel sind die folgenden Zeichenfolgen alle gleichbedeutend, wenn sie mit dem Parameter -StartKey zum Einsatz kommen:

HKLM\SOFTWARE
HKLM:\SOFTWARE
HKEY_LOCAL_MACHINE\SOFTWARE

Der Parameter für -StartKey muss an der ersten Stelle stehen. Solange man das zugehörige Argument zuerst auf der Kommandozeile (nach dem eigentlichen Skriptnamen) angibt, ist die Angabe von „-StartKey“ nicht zwingend nötig. Sollte das Argument für den Parameter -StartKey Leerzeichen enthalten, dann muss der komplette Parameterwert mit Anführungszeichen eingerahmt werden (").

Der zweite Parameter des Skripts bezieht sich auf das „Suchmuster“ – das wird mit dem Parameter -Pattern eingeleitet. Damit lässt sich ein regulärer Ausdruck angeben, der gesucht werden soll. Das Skript interpretiert den zweiten angegebenen Parameter als „-Pattern“, sprich man muss beim Aufruf diese „Parameter-Ankündigung“ nicht explizit angeben: Es reicht die Angabe eines Werts – an der zweiten Stelle nach dem Kommandoaufruf.

Zudem gilt auch hier die Vorgabe: Sollte das Argument Leerzeichen enthalten, dann muss der komplette Parameterwert mit Anführungszeichen eingerahmt werden ("). Das Suchmuster mit dem regulären Ausdruck berücksichtigt keine Groß-Kleinschreibung. Wer mehr Informationen zum Themenbereich „reguläre Ausdrücke“ benötigt, der kann die Powershell-Hilfe verwenden. Dazu eignet sich der folgende Befehl, der am Powershell-Prompt eingegeben werden muss:

help about_Regular_Expressions

Die weiteren drei Parameter – -MatchKey, -MatchValue und -MatchData – geben an, welche Arten von Übereinstimmung gefunden werden sollen. Diese Parameter entsprechen den Checkboxen im Suchdialog von Regedit (Schlüssel – also Key, Werte – sprich Value und die Daten – Data; siehe Abbildung 1).

In der Abbildung 2 wird gezeigt, wie diese Objekte mit denen in der Registry korrespondieren. Es hat sich als besser herausgestellt, diese Begriffe auch im Skript zu verwenden und keine neue Nomenklatur dafür einzuführen. Dabei muss der Benutzer des Skripts zumindest eine dieser drei Größen angeben – er darf aber auch mehr als eine verwenden. Somit bezieht sich -MatchKey auf den Namen eines Schlüssels (Subkey), -MatchValue auf einen Namen eines Registry-Werts und -MatchData auf die Daten des Wertes.

Mit dem Parameter -MaximumMatches lässt sich vorgeben, wie viele Ergebnisse maximal pro durchsuchten Computer angezeigt werden. Der Standardwert für diese Einstellung lautet 0 und damit wird die maximale Anzahl an möglichen Übereinstimmungen zurückgegeben. Dieser Parameter erweist sich als nützlich, wenn man die Registry auf einem entfernten System durchsucht und man eventuell die Belastung auf dem Netzwerk eingrenzen möchte – oder muss.

Mit dem Parameter -ComputerName lässt sich ein bestimmter Rechner oder aber eine Liste von Systemen angeben, die es zu durchsuchen gilt. Dazu kann der Administrator einen einzelnen namen oder ein Array mit Namen angeben. Zudem akzeptiert dieser Parameter die Eingaben via Pipeline. Die Standardeinstellung für diesen Parameter hat zur Folge, dass nur die Registry auf dem lokalen System durchsucht wird.

Das Skript Search-Registry.ps1 gibt Objekte aus, die Properties besitzen, wie sie in Tabelle 1 zu sehen sind. Die Abbildung 3 zeigt ein Beispiel für eine Suche von Search-Registry.ps1 in einem Powershell-Konsolenfenster. Das Skript in dieser Abbildung sendet die Ausgabe an Select-Object, um dort nur die Properties „Key“, „Value“ und „Data“ zu selektieren.

Die Property ComputerName wird hier nicht benötigt, denn dieses Kommando durchsucht nur die Registry des lokalen Systems. Die Ausgabe von Select-Object geht auf das Cmdlet Format-List. Diese Liste enthält nur zwei Übereinstimmungen – da der Parameter -MaximumMatches so angegeben wurde.

Je nach Betriebssystem können einige Plätze in der Registry nicht zugreifbar sein. Das kann zum Beispiel daran liegen, dass nicht genügend Berechtigungen für den Zugriff auf diesen Bereich vorliegen. An diesen Bereichen in der Registry wird die Ausgabe eine Fehlermeldung enthalten, wenn via Search-Registry.ps1 versucht wird, darauf zuzugreifen. Um diese Fehlermeldungen ignorieren zu können, muss man noch den Parameter -ErrorAction SilentlyContinue beim Aufruf des Skripts angeben.

Tabelle 1. Die Properties der Ausgabeobjekte von Search-Registry.ps1

Property              Beschreibung
ComputerName   Das ist das System, auf dem die Übereinstimmung aufgetreten ist

Key                      Das ist der Name des Schlüssels.

Value                   Das ist der Registry-Wert

Data                    Das sind die Registry-Daten

Bild 3. So zeigt sich das Skript Search-Registry.ps1 im Einsatz

So geht das Skript vor

Das Skript selbst verwendet die Skriptblöcke „begin“ und „process“, da es die Eingabe aus der Pipeline unterstützt. Der Begin-Skriptblock enthält den Initialisierungs-Code des Skripts: es werden globale Variable deklariert, die Parameter validiert und Funktionen definiert. Im Skriptblock „Process“ wird dann jeder Computername, der an das Skript übergeben wird, a gearbeitet und dann wird der jeweilige Computername an die Funktion search-registry übergeben.

Diese Funktion verwendet die statische Methode OpenRemoteBaseKey aus der .NET-Klasse Microsoft.Win32.RegistryKey. Damit wird der Subtree geöffnet, den man auf der Kommandozeile übergeben hat. Danach wird jeder Computername an die Funktion search-registry übergeben.

Diese Funktion sucht dann rekursiv nachdem Suchmuster (also den regulären Ausdruck) in den jeweiligen Bereichen der Registry. Das bedeutet: Die startet bei einem bestimmten (vorgegebenen) Ort und sucht dann unterhalb dieses Schlüssels in den Subkeys.

Beispiele für Kommandos mit Search-Registry.ps1

Im Folgenden sollen einige sinnvolle Beispiele zeigen, wie man das Skript Search-Registry.ps1 einsetzen kann:

Search-Registry -StartKey HKCR –Pattern "Word\.Document\.\d" -MatchKey

Dieser Befehl sucht in der Registry des lokalen Systems und startet dazu bei HKEY_CLASSES_ROOT. Es wird dann nach dem Suchmuster "Word\.Document\.\d" gesucht.

In regulären Ausdrücken steht ein einzelner Punkt für ein beliebiges Zeichen. Ein Backslash-Zeichen sagt dagegen: „Interpretiere das folgende Zeichen so wie es hingeschrieben wurde“. Daher bedeutet "\." Nichts anderes als "." – also einen Punkt. Ein Backslash gefolgt von einem d ( also \d) verweist auf eine beliebige Dezimalziffer. Daher werden damit Subkeys in der Registry gefunden, die lauten: „Word.Document.n“ – wobei das n für eine Nummer steht

Liefert dieser Befehl eine Übereinstimmung, dann handelt es sich wahrscheinlich um eine Applikation auf dem lokalen System, das Microsoft-Word-Dokumente öffnen kann.

Search-Registry -StartKey HKLM –Pattern
$ENV:USERNAME –MatchData

Dieser Befehl sucht unter HKEY_LOCAL_MACHINE auf dem lokalen System nach sämtlichen Registry-Daten, die den aktuellen Benutzernamen enthalten.

Dagegen bestimmt der folgende Befehl, ob der Registry-Wert EnableLinkedConnections auf dem lokalen System konfiguriert ist:

Search-Registry -StartKey HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
-Pattern EnableLinkedConnections –MatchValue

Ein ähnliches Verhalten zeigt der folgende Befehl:

Get-Content Computers.txt |
Search-Registry HKLM\SOFTWARE\Microsoft\
Windows\CurrentVersion\Policies\System
-Pattern EnableLinkedConnections -MatchValue |
Export-CSV C:\Reports\EnableLinkedConnections.csv
-NoTypeInformation

Doch hier wird Search-Registry.ps1 verwendet, um die Registry-Werte aus den Computern zu durchsuchen, die in der Datei Computers.txt angegeben sind. Daraus erzeugt das Kommando dann auch noch eine CSV-Datei.

Das Powershell-Skript im Quellcode

Hier steht das Powershell-Skript von Bill Stewart als Quellcode zur Verfügung. Bitte testen Sie das Skript erst in einer Testumgebung, ehe sie damit auf Produktivsystemen arbeiten. Diese Empfehlung gilt besonders für alle Aktionen an der Registry:

# Search-Registry.ps1
# Written by Bill Stewart (bstewart@iname.com)

#requires -version 2

<#
.SYNOPSIS
Searches the registry on one or more computers for a specified text pattern.

.DESCRIPTION
Searches the registry on one or more computers for a specified text pattern. Supports searching for any combination of key names, value names, and/or value data. The text pattern is a case-insensitive regular expression.

.PARAMETER StartKey
Starts searching at the specified key. The key name uses the following format:
subtree[:][\[keyname[\keyname…]]]
subtree can be any of the following:
  HKCR or HKEY_CLASSES_ROOT
  HKCU or HKEY_CURRENT_USER
  HKLM or HKEY_LOCAL_MACHINE
  HKU or HKEY_USERS
This parameter’s format is compatible with PowerShell registry drive (e.g., HKLM:\SOFTWARE), reg.exe (e.g., HKLM\SOFTWARE), and regedit.exe (e.g., HKEY_LOCAL_MACHINE\SOFTWARE).

.PARAMETER Pattern
Searches for the specified regular expression pattern. The pattern is not case-sensitive. See help topic about_Regular_Expressions for more information.

.PARAMETER MatchKey
Matches registry key names. You must specify at least one of -MatchKey, -MatchValue, or -MatchData.

.PARAMETER MatchValue
Matches registry value names. You must specify at least one of -MatchKey, -MatchValue, or -MatchData.

.PARAMETER MatchData
Matches registry value data. You must specify at least one of -MatchKey, -MatchValue, or -MatchData.

.PARAMETER MaximumMatches
Specifies the maximum number of results per computer searched. 0 means "return the maximum number of possible matches." The default is 0. This parameter is useful when searching the registry on remote computers in order to minimize unnecessary network traffic.

.PARAMETER ComputerName
Searches the registry on the specified computer. This parameter supports piped input.

.OUTPUTS
PSObjects with the following properties:
  ComputerName  The computer name on which the match occurred
  Key           The key name (e.g., HKLM:\SOFTWARE)
  Value         The registry value (empty for the default value)
  Data          The registry value’s data

.EXAMPLE
PS C:\> Search-Registry -StartKey HKLM -Pattern $ENV:USERNAME -MatchData
Searches HKEY_LOCAL_MACHINE (i.e., HKLM) on the current computer for registry values whose data contains the current user’s name.

.EXAMPLE
PS C:\> Search-Registry -StartKey HKLM:\SOFTWARE\Classes\Installer -Pattern LastUsedSource -MatchValue | Select-Object Key,Value,Data | Format-List
Outputs the LastUsedSource registry entries on the current computer.

.EXAMPLE
PS C:\> Search-Registry -StartKey HKCR\.odt -Pattern .* -MatchKey -MaximumMatches 1
Outputs at least one match if the specified reistry key exists. This command returns a result if the current computer has a program registered to open files with the .odt extension. The pattern .* means 0 or more of any character (i.e., match everything).

.EXAMPLE
PS C:\> Get-Content Computers.txt | Search-Registry -StartKey "HKLM:\SOFTWARE\My Application\Installed" -Pattern "Installation Complete" -MatchValue -MaximumMatches 1 | Export-CSV C:\Reports\MyReport.csv -NoTypeInformation
Searches for the specified value name pattern in the registry on each computer listed in the file Computers.txt starting at the specified subkey. Output is sent to the specifed CSV file.
#>

[CmdletBinding()]
param(
  [parameter(Position=0,Mandatory=$TRUE)]
    [String] $StartKey,
  [parameter(Position=1,Mandatory=$TRUE)]
    [String] $Pattern,
    [Switch] $MatchKey,
    [Switch] $MatchValue,
    [Switch] $MatchData,
    [UInt32] $MaximumMatches=0,
  [parameter(ValueFromPipeline=$TRUE)]
    [String[]] $ComputerName=$ENV:COMPUTERNAME
)

begin {
  $PIPELINEINPUT = (-not $PSBOUNDPARAMETERS.ContainsKey("ComputerName")) -and
    (-not $ComputerName)

  # Throw an error if -Pattern is not valid
  try {
    "" -match $Pattern | out-null
  }
  catch [System.Management.Automation.RuntimeException] {
    throw "-Pattern parameter not valid – $($_.Exception.Message)"
  }

  # You must specify at least one matching criteria
  if (-not ($MatchKey -or $MatchValue -or $MatchData)) {
    throw "You must specify at least one of: -MatchKey -MatchValue -MatchData"
  }

  # Interpret zero as "maximum possible number of matches"
  if ($MaximumMatches -eq 0) { $MaximumMatches = [UInt32]::MaxValue }

  # These two hash tables speed up lookup of key names and hive types
  $HiveNameToHive = @{
    "HKCR"               = [Microsoft.Win32.RegistryHive] "ClassesRoot";
    "HKEY_CLASSES_ROOT"  = [Microsoft.Win32.RegistryHive] "ClassesRoot";
    "HKCU"               = [Microsoft.Win32.RegistryHive] "CurrentUser";
    "HKEY_CURRENT_USER"  = [Microsoft.Win32.RegistryHive] "CurrentUser";
    "HKLM"               = [Microsoft.Win32.RegistryHive] "LocalMachine";
    "HKEY_LOCAL_MACHINE" = [Microsoft.Win32.RegistryHive] "LocalMachine";
    "HKU"                = [Microsoft.Win32.RegistryHive] "Users";
    "HKEY_USERS"         = [Microsoft.Win32.RegistryHive] "Users";
  }
  $HiveToHiveName = @{
    [Microsoft.Win32.RegistryHive] "ClassesRoot"  = "HKCR";
    [Microsoft.Win32.RegistryHive] "CurrentUser"  = "HKCU";
    [Microsoft.Win32.RegistryHive] "LocalMachine" = "HKLM";
    [Microsoft.Win32.RegistryHive] "Users"        = "HKU";
  }

  # Search for ‚hive:\startkey‘; ‚:‘ and starting key optional
  $StartKey | select-string "([^:\\]+):?\\?(.+)?" | foreach-object {
    $HiveName = $_.Matches[0].Groups[1].Value
    $StartPath = $_.Matches[0].Groups[2].Value
  }

  if (-not $HiveNameToHive.ContainsKey($HiveName)) {
    throw "Invalid registry path"
  } else {
    $Hive = $HiveNameToHive[$HiveName]
    $HiveName = $HiveToHiveName[$Hive]
  }

  # Recursive function that searches the registry
  function search-registrykey($computerName, $rootKey, $keyPath, [Ref] $matchCount) {
    # Write error and return if unable to open the key path as read-only
    try {
      $subKey = $rootKey.OpenSubKey($keyPath, $FALSE)
    }
    catch [System.Management.Automation.MethodInvocationException] {
      $message = $_.Exception.Message
      write-error "$message – $HiveName\$keyPath"
      return
    }

    # Write error and return if the key doesn’t exist
    if (-not $subKey) {
      write-error "Key does not exist: $HiveName\$keyPath" -category ObjectNotFound
      return
    }

    # Search for value and/or data; -MatchValue also returns the data
    if ($MatchValue -or $MatchData) {
      if ($matchCount.Value -lt $MaximumMatches) {
        foreach ($valueName in $subKey.GetValueNames()) {
          $valueData = $subKey.GetValue($valueName)
          if (($MatchValue -and ($valueName -match $Pattern)) -or ($MatchData -and ($valueData -match $Pattern))) {
            "" | select-object `
              @{N="ComputerName"; E={$computerName}},
              @{N="Key"; E={"$HiveName\$keyPath"}},
              @{N="Value"; E={$valueName}},
              @{N="Data"; E={$valueData}}
            $matchCount.Value++
          }
          if ($matchCount.Value -eq $MaximumMatches) { break }
        }
      }
    }

    # Iterate and recurse through subkeys; if -MatchKey requested, output
    # objects only report computer and key (keys do not have values or data)
    if ($matchCount.Value -lt $MaximumMatches) {
      foreach ($keyName in $subKey.GetSubKeyNames()) {
        if ($keyPath -eq "") {
          $subkeyPath = $keyName
        } else {
          $subkeyPath = $keyPath + "\" + $keyName
        }
        if ($MatchKey -and ($keyName -match $Pattern)) {
          "" | select-object `
            @{N="ComputerName"; E={$computerName}},
            @{N="Key"; E={"$HiveName\$subkeyPath"}},
            @{N="Value"; E={}},
            @{N="Data"; E={}}
          $matchCount.Value++
        }
        # $matchCount is a reference
        search-registrykey $computerName $rootKey $subkeyPath $matchCount
        if ($matchCount.Value -eq $MaximumMatches) { break }
      }
    }

    # Close opened subkey
    $subKey.Close()
  }

  # Core function opens the registry on a computer and initiates searching
  function search-registry2($computerName) {
  # Write error and return if unable to open the key on the computer
  try {
      $rootKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive,
        $computerName)
    }
    catch [System.Management.Automation.MethodInvocationException] {
      $message = $_.Exception.Message
      write-error "$message – $computerName"
      return
    }
    # $matchCount is per computer; pass to recursive function as reference
    $matchCount = 0
    search-registrykey $computerName $rootKey $StartPath ([Ref] $matchCount)
    $rootKey.Close()
  }
}

process {
  if ($PIPELINEINPUT) {
    search-registry2 $_
  }
  else {
    $ComputerName | foreach-object {
      search-registry2 $_
    }
  }
}

Lesen Sie auch