# Registry und NTFS: Zustand, Typen und Zugriffsrechte

> Registry-Hives, Datentypen und die 32-Bit-Sicht sicher bedienen, NTFS-Rechte in der Reihenfolge lesen, in der Windows sie prüft, und die Dateisystem-Eigenschaften kennen, die keine Auflistung zeigt.

Track: [Windows Server Administration](https://physar.tech/learn/windows-server-administration)  
Kanonische Fassung: https://physar.tech/learn/windows-server-administration/registry-filesystem  
Stand: 2026-09-03  
Interaktiver Teil: 10 Checks (nur im Browser)

## Registry und NTFS: wo der Zustand liegt und wer ihn lesen darf

### Zwei Orte, an denen Windows seinen Zustand hält

Wer von Linux kommt, sucht die Konfiguration in Textdateien unter `/etc`. Auf Windows liegt sie an zwei Orten: in der **Registry** und im **Dateisystem**. Beide sind hierarchisch, beide haben Berechtigungen nach demselben Modell, und beide werden von denselben Fehlannahmen falsch bedient. Dieses Modul ist die Werkzeugkunde für alles, was danach kommt: Dienste, Start, Patchstand und Freigaben liegen alle in einem dieser beiden Bäume.

**Hive:** Eine logische Gruppe von Schlüsseln, Unterschlüsseln und Werten mit eigenen Dateien auf der Platte, die beim Systemstart oder bei der Anmeldung geladen werden. Die meisten liegen unter `%SystemRoot%\System32\config`, der Benutzeranteil in `Ntuser.dat` im Profil.

**Schlüssel und Wert:** Ein Schlüssel entspricht einem Ordner, ein Wert einer Datei darin. Ein Wert hat immer drei Teile: Name, **Datentyp** und Daten. Der Datentyp wird beim Lesen ausgewertet und ist keine Kosmetik.

Die praktische Konsequenz: Ein Wert am richtigen Ort mit dem falschen Datentyp ist kein halb richtiger Eintrag, sondern ein wirkungsloser. Warum das so ist, steht zwei Abschnitte weiter.

### Die fünf Wurzeln — und welche davon echt sind

Der Registrierungs-Editor zeigt fünf Wurzeln. Nur zwei davon haben eigene Dateien; die anderen sind Sichten auf Teile davon. Wer das nicht trennt, sucht Werte an Orten, die es auf der Platte gar nicht gibt.

|  |  |
| --- | --- |
| `HKEY_LOCAL_MACHINE` (HKLM) | Maschinenzustand. Eigene Hive-Dateien: `SYSTEM`, `SOFTWARE`, `SAM`, `SECURITY` unter `System32\config`. |
| `HKEY_USERS` (HKU) | Alle geladenen Benutzerprofile, je Profil eine `Ntuser.dat`. |
| `HKEY_CURRENT_USER` (HKCU) | Sicht auf `HKU\<SID des angemeldeten Benutzers>`. Kein eigener Ort. |
| `HKEY_CLASSES_ROOT` (HKCR) | Zusammengeführte Sicht aus `HKLM\SOFTWARE\Classes` und `HKCU\SOFTWARE\Classes`. |
| `HKEY_CURRENT_CONFIG` (HKCC) | Sicht auf das aktuelle Hardwareprofil unterhalb von `HKLM\SYSTEM`. |

> **Warum das im Betrieb zählt:** `HKCU` ist die Sicht des **gerade angemeldeten** Benutzers. Ein Dienstkonto, ein geplanter Task oder ein Fernwartungswerkzeug hat ein anderes `HKCU` als du an der Konsole. Ein Skript, das unter deinem Konto funktioniert und als geplante Aufgabe nichts bewirkt, hat meistens genau hier seinen Fehler — es schreibt in ein anderes Profil, nicht in ein kaputtes.

### `CurrentControlSet` ist ein Zeiger, kein Ort

Unter `HKLM\SYSTEM` liegen mehrere nummerierte Kontrollsätze: `ControlSet001`, oft ein `ControlSet002`. `HKLM\SYSTEM\CurrentControlSet` ist kein eigener Speicher, sondern ein Verweis auf den gerade aktiven — welcher das ist, steht in `HKLM\SYSTEM\Select` unter dem Wert `Current`. Dort stehen auch `Default`, `Failed` und `LastKnownGood`.

Solange das System läuft, ist das eine Trivialität: Man schreibt nach `CurrentControlSet` und trifft das Richtige. Interessant wird es, sobald ein System **nicht** läuft und man seine Registry von außen einhängt — etwa aus der Wiederherstellungsumgebung oder von einer angehängten virtuellen Festplatte.

> **Der Fehler, der offline entsteht:** In einer eingehängten fremden Hive gibt es **kein** `CurrentControlSet` — nur die nummerierten Sätze. Wer dort blind `ControlSet001` bearbeitet, ändert unter Umständen den Satz, der beim nächsten Start gar nicht gewählt wird. Der richtige Ablauf ist: erst `Select\Current` lesen, dann den Satz mit dieser Nummer bearbeiten. Der Eingriff sieht danach identisch aus — er wirkt nur eben, oder nicht.

Unter `CurrentControlSet\Services` liegt jeder Dienst mit seinem Starttyp, seinem Programmpfad und seinem Konto. Das nächste Modul arbeitet dort; hier ist wichtig, dass es sich um denselben Baum handelt, den `sc.exe` und `Get-Service` bedienen — die Werkzeuge sind eine Oberfläche über diesen Schlüsseln, keine eigene Datenbank.

### Datentypen sind Verhalten, keine Beschriftung

|  |  |
| --- | --- |
| `REG_SZ` | Zeichenkette. Wird genommen, wie sie dasteht. |
| `REG_EXPAND_SZ` | Zeichenkette mit **unaufgelösten** Umgebungsvariablen wie `%SystemRoot%`. Der Lesende löst sie auf. |
| `REG_DWORD` | 32-Bit-Zahl. Der Standard für Schalter — `0` oder `1`, nicht die Zeichenkette `1`. |
| `REG_MULTI_SZ` | Folge von Zeichenketten, mit einer Leerzeichenkette abgeschlossen. |
| `REG_BINARY` | Rohdaten. Ohne Kenntnis des Formats nicht sinnvoll editierbar. |

> **Der klassische Fehlgriff:** Ein Pfad wie `%SystemRoot%\System32\dienst.exe` als `REG_SZ` gespeichert wird **nicht** aufgelöst: Der Lesende bekommt die Zeichenkette mit dem Prozentzeichen und sucht nach einem Ordner dieses Namens. Der Eintrag sieht im Editor exakt richtig aus, denn dort steht dieselbe Zeichenfolge wie beim funktionierenden Nachbarn. Nur der Typ in der zweiten Spalte unterscheidet sich. Dasselbe gilt umgekehrt für einen Schalter, den ein Skript als `REG_SZ` mit dem Inhalt `1` anlegt, während die Software ein `REG_DWORD` erwartet: Sie liest keinen Wert, findet ihren Standard und macht weiter.

### Die Registry aus PowerShell

PowerShell bildet die Registry als Laufwerk ab. Das ist bequem und erzeugt genau eine Verwechslung, die man einmal verstehen muss: In diesem Laufwerk sind **Schlüssel** die Elemente und **Werte** die Eigenschaften daran.

|  |  |
| --- | --- |
| `Get-ChildItem HKLM:\SYSTEM\Select` | Listet Unterschlüssel — hier also nichts, denn `Select` hat keine. |
| `Get-ItemProperty HKLM:\SYSTEM\Select` | Liest die **Werte**: `Current`, `Default`, `Failed`, `LastKnownGood`. |
| `New-ItemProperty -PropertyType DWord` | Legt einen Wert mit ausdrücklichem Typ an. |
| `Set-ItemProperty` | Ändert einen vorhandenen Wert. Legt keinen Typ fest. |
| `reg query <Pfad> /v <Name>` | Dasselbe über `reg.exe`; zeigt den Typ in der Ausgabe mit an. |

Der Ratschlag daraus ist kurz: Wer einen Wert **anlegt**, nennt den Typ ausdrücklich. Wer einen fremden Wert ändert, sieht ihn sich vorher mit `reg query` an — dort steht der Typ direkt neben den Daten und lässt sich nicht übersehen.

### Die 32-Bit-Sicht: `WOW6432Node`

Auf einem 64-Bit-Windows gibt es für Teile der Registry zwei Sichten. Ein 32-Bit-Prozess, der `HKLM\SOFTWARE\Hersteller` liest, wird transparent nach `HKLM\SOFTWARE\WOW6432Node\Hersteller` umgeleitet. Der Prozess merkt davon nichts — er sieht seine eigene, vollständige Welt.

- `reg query HKLM\SOFTWARE\Hersteller /reg:64` — die 64-Bit-Sicht ausdrücklich.
- `reg query HKLM\SOFTWARE\Hersteller /reg:32` — die umgeleitete Sicht ausdrücklich.
- Ohne Angabe entscheidet die Bitness des aufrufenden Prozesses, nicht die des Systems.

> **Zwei Fallen auf einmal:** Erstens: Ein 32-Bit-Installationsprogramm legt seine Einstellungen unter `WOW6432Node` ab, ein 64-Bit-Verwaltungsskript sucht sie darüber und findet nichts. Beide arbeiten korrekt. Zweitens: Schreibt ein 32-Bit-Prozess eine Zeichenkette mit `%ProgramFiles%`, ersetzt WOW64 das laut Dokumentation durch `%ProgramFiles(x86)%`. Der Wert, den du gleich danach ausliest, ist nicht der Wert, der geschrieben wurde. Wer die Sicht nicht ausdrücklich wählt, misst ein Ergebnis, das von seinem eigenen Werkzeug abhängt.

### `.reg`-Dateien sind kein Änderungsverfahren

Ein Doppelklick auf eine `.reg`-Datei schreibt ihren Inhalt in die Registry. Das ist bequem und für einen kontrollierten Change aus drei Gründen ungeeignet.

- **Keine Transaktion.** Bricht der Vorgang in der Mitte ab, bleibt die Hälfte stehen. Es gibt kein Zurück, das die Datei mitliefert.
- **Keine Prüfung.** Ein Tippfehler im Pfad legt einen neuen Schlüssel an, statt eine Fehlermeldung zu erzeugen.
- **Löschen sieht harmlos aus.** Ein Minuszeichen vor dem Schlüsselnamen in eckigen Klammern löscht den Schlüssel samt Unterschlüsseln. Eine Zeile, kein Hinweis.

`reg export <Pfad> vorher.reg` — den betroffenen Teilbaum sichern, bevor irgendetwas geschrieben wird. → Die Änderung als Skript mit ausdrücklichen Typen schreiben, nicht als Datei zum Doppelklicken. → `reg query <Pfad> /v <Name>` — den erwarteten Zustand danach zurücklesen, mitsamt Typ. → Prüfen, ob die Anwendung den Wert überhaupt neu liest, oder ob ein Dienstneustart dazugehört.

### NTFS-Rechte, und warum „Verweigern gewinnt immer“ falsch ist

**DACL und ACE:** Jede Datei, jeder Ordner und jeder Registry-Schlüssel trägt eine **DACL** — eine Liste von Einträgen (**ACE**). Jeder Eintrag nennt einen Kontoinhaber und erlaubt oder verweigert ihm bestimmte Rechte.

Ein Eintrag ist entweder **explizit** am Objekt gesetzt oder vom übergeordneten Ordner **geerbt**. Diese Unterscheidung ist der Kern des nächsten Abschnitts — sie entscheidet, welcher Eintrag zuerst gefunden wird.

Geprüft wird gegen das **Zugriffstoken** des Zugreifenden. Darin stehen sein Konto und seine Gruppen — und zwar so, wie sie zum Zeitpunkt der **Anmeldung** waren. Wer jemanden einer Gruppe hinzufügt, ändert sein Token nicht: Die neue Mitgliedschaft wirkt erst nach einer neuen Anmeldung. `whoami /groups` zeigt, was im aktuellen Token wirklich steht — und beantwortet damit die Frage „ist die Gruppe schon drin“ ohne Raten.

Das System geht die Einträge der Reihe nach durch und hört auf, sobald der angeforderte Zugriff vollständig erlaubt oder ausdrücklich verweigert ist. Die Reihenfolge entscheidet also über das Ergebnis — und sie ist dokumentiert.

_[Abbildung: Alle expliziten Einträge stehen vor allen geerbten; innerhalb jeder Gruppe Verweigern vor Zulassen.]_

Daraus folgt der Satz, den man sich merken muss: **Verweigern gewinnt auf derselben Ebene, nicht über Ebenen hinweg.** Ein am Ordner gesetztes Verweigern wird nach unten vererbt und landet damit in Gruppe 3. Ein direkt an der Datei gesetztes Zulassen steht in Gruppe 2 — es wird zuerst gefunden und beendet die Prüfung.

**Kurzcheck:** Ein Ordner verweigert der Gruppe `Praktikanten` das Lesen. Eine Datei darin hat ein direkt gesetztes Zulassen für dieselbe Gruppe. Was passiert beim Zugriff?

- [x] Der Zugriff gelingt — das explizite Zulassen wird vor dem geerbten Verweigern gefunden
- [ ] Der Zugriff scheitert — ein Verweigern schlägt jedes Zulassen
- [ ] Das Ergebnis ist unbestimmt und hängt von der Reihenfolge im Editor ab

> Richtig. Explizite Einträge stehen vollständig vor geerbten, und die Prüfung endet beim ersten Treffer.

> **Was das für die Praxis heißt:** Ein Verweigern-Eintrag hoch im Baum ist keine verlässliche Sperre — irgendwo weiter unten kann ein explizites Zulassen ihn aushebeln, gesetzt von jemandem, der ein Zugriffsproblem schnell lösen wollte. Verlässlich sperrt man, indem man **Zulassen-Einträge entfernt**, nicht indem man Verweigern-Einträge hinzufügt.

### `icacls`: sichern, setzen, vererben

|  |  |
| --- | --- |
| `icacls <Pfad>` | Zeigt die DACL. Geerbte Einträge sind mit `(I)` markiert. |
| `icacls <Pfad> /save <Datei> /t` | Sichert die DACLs des Baums in eine Datei — der Rückweg vor jeder Änderung. |
| `icacls <Ordner> /restore <Datei>` | Spielt gesicherte DACLs zurück. |
| `icacls <Pfad> /grant <Konto>:<Rechte>` | **Ergänzt** ein Recht zu den vorhandenen expliziten Einträgen. |
| `icacls <Pfad> /grant:r <Konto>:<Rechte>` | **Ersetzt** die vorhandenen expliziten Einträge dieses Kontos. |
| `icacls <Pfad> /inheritancelevel:r` | Entfernt die Vererbung vom übergeordneten Ordner. |

> **Der Unterschied, der zwei Zeichen groß ist:** `/grant` und `/grant:r` sehen fast gleich aus und tun Gegensätzliches. Ohne `:r` sammeln sich bei jedem Lauf eines Skripts weitere Einträge an — nach einem Jahr steht dort eine Liste, die niemand mehr erklären kann, und jeder Lauf war für sich erfolgreich. Mit `:r` ist der Lauf wiederholbar und das Ergebnis unabhängig davon, wie oft er lief.

Ein Sonderfall bleibt: Der **Besitzer** eines Objekts darf dessen Berechtigungen ändern, auch wenn die DACL ihm nichts erlaubt. Deshalb ist `takeown` der Ausweg aus einem Ordner, den sich niemand mehr zutraut — und deshalb ist der Besitzer eine Angabe, die man bei einer Rechteprüfung mitliest statt sie zu übergehen.

### Der Ordner, in den niemand mehr hineinkommt

Ein Projektordner auf einem Dateiserver lässt sich von keinem Administrator mehr öffnen. Die Vererbung wurde irgendwann unterbrochen, und der einzige verbliebene Zulassen-Eintrag gehört einem Konto, das es nicht mehr gibt.

**Anforderungen**

- Der Zugriff muss wiederhergestellt werden.
- Der Zustand vorher muss belegbar bleiben.

**Schritte**

- `icacls D:\Projekte\Alt /save C:\temp\alt.acl /t` — den Ist-Zustand des Baums sichern. Das ist der Rückweg, und er entsteht vor dem ersten Eingriff.
- `takeown /f D:\Projekte\Alt /r /d j` — Besitz übernehmen. Der Besitzer darf Berechtigungen ändern, auch wenn ihm die Liste nichts erlaubt.
- `icacls D:\Projekte\Alt` — die Liste jetzt lesen und notieren, welche Einträge geerbt (`(I)`) und welche explizit sind.
- `icacls D:\Projekte\Alt /grant:r "DOMAIN\Dateiserver-Admins":(OI)(CI)F` — einen definierten Zugang setzen, mit `:r`, damit der Lauf wiederholbar bleibt.
- `icacls D:\Projekte\Alt /inheritancelevel:e` — die Vererbung wieder aktivieren, sofern das dem Sollzustand des Baums entspricht.

**Merksatz:** Der teure Teil war nicht die Reparatur, sondern die fehlende Sicherung: Ohne Schritt 1 lässt sich hinterher nicht mehr belegen, welche Rechte der Ordner vorher hatte — und ob die neue Liste enger oder weiter ist als die alte.

### Verknüpfungen: drei Arten, ein Pfad

NTFS kennt drei Arten von Verweisen. Sie sehen im Explorer ähnlich aus, verhalten sich aber unterschiedlich — und genau eine davon braucht ein Sonderrecht.

|  |  |
| --- | --- |
| Hardlink (`mklink /h`) | Zweiter Name für **dieselbe Datei**. Nur Dateien, nur innerhalb desselben Volumes. |
| Junction (`mklink /j`) | Verzeichnisverweis. Darf über lokale Volumes hinweg zeigen, aber nicht auf Netzwerkpfade. |
| Symbolische Verknüpfung (`mklink`, `mklink /d`) | Datei- oder Verzeichnisverweis, darf auch auf einen UNC-Pfad zeigen. |

> **Das Recht, das den Unterschied macht:** Symbolische Verknüpfungen zu erstellen erfordert das Privileg `SeCreateSymbolicLinkPrivilege`, das standardmäßig nur Mitglieder der Administratoren-Gruppe haben. Junctions brauchen es nicht. Ein Bereitstellungsskript, das unter einem eingeschränkten Dienstkonto laufen soll und `mklink /d` benutzt, scheitert deshalb — und funktioniert, sobald jemand es „zum Testen“ als Administrator startet. Das ist der Grund, warum diese Sorte Fehler es bis in die Produktion schafft.

Ein Detail mit Betriebswirkung: Ein Hardlink ist kein Verweis auf eine Datei, sondern ein gleichberechtigter Name dafür. Das Löschen eines Hardlinks löscht die Daten nicht, solange noch ein anderer Name existiert. Ein Sicherungswerkzeug, das Dateien zählt, zählt dieselben Daten dabei mehrfach.

### Was man in einer Datei nicht sieht

NTFS kann an eine Datei weitere, benannte Datenströme hängen. Der sichtbare Inhalt ist nur der unbenannte Strom; die anderen tauchen weder in der Größenangabe noch in einer normalen Auflistung auf.

- `dir /R` zeigt die zusätzlichen Ströme einer Datei.
- `Get-Item <Datei> -Stream *` listet sie in PowerShell.
- `Get-Content <Datei> -Stream Zone.Identifier` liest einen davon.

Der wichtigste dieser Ströme heißt `Zone.Identifier`. Windows hängt ihn an Dateien aus dem Internet; der Wert `3` steht für die Internet-Zone. Er ist der Grund, warum ein heruntergeladenes PowerShell-Skript unter der Ausführungsrichtlinie `RemoteSigned` nicht startet, obwohl es lokal auf der Platte liegt und der Inhalt korrekt ist. `Unblock-File` entfernt genau diesen Strom — es ändert nichts am Skript selbst.

> **Warum das keine Randnotiz ist:** Ein Kopiervorgang, der Datenströme nicht mitnimmt, entfernt diese Kennzeichnung stillschweigend. Ein Archiv, das nur den Hauptstrom auspackt, ebenso. Wer eine Datei über mehrere Zwischenstationen auf einen Server bringt, kann deshalb nicht mehr sagen, ob die fehlende Sperre eine Entscheidung war oder ein Nebeneffekt des Transportwegs.

### Lange Pfade: der Schalter, der allein nichts tut

Die klassische Obergrenze der Windows-API für einen Pfad ist `MAX_PATH` — **260 Zeichen**, das abschließende Nullzeichen eingerechnet. Auf einem Dateiserver mit gewachsenen Ordnerbäumen ist das keine theoretische Grenze: Dateien werden unkopierbar, Sicherungen melden Fehler, und der Explorer weigert sich, einen Ordner zu löschen.

Wichtig ist, worauf sich die Grenze bezieht: auf den **gesamten Pfad** vom Laufwerksbuchstaben an, nicht auf den Dateinamen. Dieselbe Datei ist deshalb unerreichbar, solange sie tief im Baum liegt, und wieder erreichbar, sobald ein übergeordneter Ordner umbenannt oder der Teilbaum weiter oben eingehängt wird. Das erklärt auch, warum ein Problem nur beim Kopieren auf den Server auftritt und nicht auf dem Arbeitsplatz: Der Zielpfad ist länger als der Quellpfad.

`HKLM\SYSTEM\CurrentControlSet\Control\FileSystem`, Wert `LongPathsEnabled` als `REG_DWORD` auf `1` — oder dieselbe Einstellung per Gruppenrichtlinie. → Die Anwendung muss in ihrem Manifest `longPathAware` deklarieren. → Neustart, weil der Wert je Prozess zwischengespeichert und danach nicht neu gelesen wird.

> **Die häufigste Fehldiagnose:** Der Registry-Wert allein wirkt **nicht**. Die Dokumentation sagt ausdrücklich, dass er nur Anwendungen betrifft, die dafür angepasst wurden — und dass der Wert je Prozess zwischengespeichert wird und während dessen Laufzeit nicht neu gelesen wird. Wer den Wert setzt, den Explorer denselben Ordner noch einmal probieren lässt und daraus schließt, die Einstellung sei wirkungslos, hat zwei Dinge gleichzeitig nicht geprüft. Der Weg, der unabhängig davon immer funktioniert, ist das Präfix `\\?\` vor dem absoluten Pfad — es umgeht die Grenze auf API-Ebene, nicht über eine Einstellung.

## Quellen

- Registry Hives — https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-hives
- Registry Value Types — https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
- Registry Redirector — https://learn.microsoft.com/en-us/windows/win32/winprog64/registry-redirector
- Order of ACEs in a DACL — https://learn.microsoft.com/en-us/windows/win32/secauthz/order-of-aces-in-a-dacl
- How DACLs Control Access to an Object — https://learn.microsoft.com/en-us/windows/win32/secauthz/how-dacls-control-access-to-an-object
- icacls — https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/icacls
- mklink — https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/mklink
- Hard links and junctions — https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
- Create symbolic links (security policy setting) — https://learn.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/create-symbolic-links
- Maximum Path Length Limitation — https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
- Unblock-File (Microsoft.PowerShell.Utility) — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file
- Get-Item (Microsoft.PowerShell.Management) — https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-item
