Full Code of mkorman90/regipy for AI

master f9b19e688612 cached
223 files
544.5 KB
142.3k tokens
491 symbols
1 requests
Download .txt
Showing preview only (601K chars total). Download the full file or copy to clipboard to get everything.
Repository: mkorman90/regipy
Branch: master
Commit: f9b19e688612
Files: 223
Total size: 544.5 KB

Directory structure:
gitextract_zwilyk2_/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── publish.yml
│       └── scorecard.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── docs/
│   ├── PLUGINS.md
│   └── README.rst
├── pyproject.toml
├── regipy/
│   ├── __init__.py
│   ├── cli.py
│   ├── cli_utils.py
│   ├── constants.py
│   ├── exceptions.py
│   ├── hive_types.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── amcache/
│   │   │   ├── __init__.py
│   │   │   └── amcache.py
│   │   ├── bcd/
│   │   │   ├── __init__.py
│   │   │   └── boot_entry_list.py
│   │   ├── ntuser/
│   │   │   ├── __init__.py
│   │   │   ├── appkeys.py
│   │   │   ├── classes_installer.py
│   │   │   ├── comdlg32.py
│   │   │   ├── installed_programs_ntuser.py
│   │   │   ├── muicache.py
│   │   │   ├── network_drives.py
│   │   │   ├── persistence.py
│   │   │   ├── putty.py
│   │   │   ├── recentdocs.py
│   │   │   ├── runmru.py
│   │   │   ├── shellbags_ntuser.py
│   │   │   ├── sysinternals.py
│   │   │   ├── tsclient.py
│   │   │   ├── typed_paths.py
│   │   │   ├── typed_urls.py
│   │   │   ├── user_assist.py
│   │   │   ├── winrar.py
│   │   │   ├── winscp_saved_sessions.py
│   │   │   ├── word_wheel_query.py
│   │   │   └── wsl.py
│   │   ├── plugin.py
│   │   ├── plugin_template.py
│   │   ├── sam/
│   │   │   ├── __init__.py
│   │   │   ├── local_sid.py
│   │   │   └── samparse.py
│   │   ├── security/
│   │   │   ├── __init__.py
│   │   │   └── domain_sid.py
│   │   ├── software/
│   │   │   ├── __init__.py
│   │   │   ├── appcompatflags.py
│   │   │   ├── appinitdlls.py
│   │   │   ├── apppaths.py
│   │   │   ├── classes_installer.py
│   │   │   ├── defender.py
│   │   │   ├── disablesr.py
│   │   │   ├── execpolicy.py
│   │   │   ├── image_file_execution_options.py
│   │   │   ├── installed_programs.py
│   │   │   ├── last_logon.py
│   │   │   ├── networklist.py
│   │   │   ├── persistence.py
│   │   │   ├── printdemon.py
│   │   │   ├── profilelist.py
│   │   │   ├── pslogging.py
│   │   │   ├── spp_clients.py
│   │   │   ├── susclient.py
│   │   │   ├── tracing.py
│   │   │   ├── uac.py
│   │   │   └── winver.py
│   │   ├── system/
│   │   │   ├── __init__.py
│   │   │   ├── active_controlset.py
│   │   │   ├── appcertdlls.py
│   │   │   ├── backuprestore.py
│   │   │   ├── bam.py
│   │   │   ├── bootkey.py
│   │   │   ├── codepage.py
│   │   │   ├── computer_name.py
│   │   │   ├── crash_dump.py
│   │   │   ├── diag_sr.py
│   │   │   ├── disablelastaccess.py
│   │   │   ├── external/
│   │   │   │   ├── ShimCacheParser.py
│   │   │   │   └── __init__.py
│   │   │   ├── host_domain_name.py
│   │   │   ├── lsa_packages.py
│   │   │   ├── mountdev.py
│   │   │   ├── network_data.py
│   │   │   ├── pagefile.py
│   │   │   ├── pending_file_rename.py
│   │   │   ├── previous_winver.py
│   │   │   ├── processor_architecture.py
│   │   │   ├── routes.py
│   │   │   ├── safeboot_configuration.py
│   │   │   ├── services.py
│   │   │   ├── shares.py
│   │   │   ├── shimcache.py
│   │   │   ├── shutdown.py
│   │   │   ├── timezone_data.py
│   │   │   ├── timezone_data2.py
│   │   │   ├── usb_devices.py
│   │   │   ├── usbstor.py
│   │   │   └── wdigest.py
│   │   ├── usrclass/
│   │   │   ├── __init__.py
│   │   │   └── shellbags_usrclass.py
│   │   ├── utils.py
│   │   ├── validated_plugins.json
│   │   └── validation_status.py
│   ├── py.typed
│   ├── recovery.py
│   ├── regdiff.py
│   ├── registry.py
│   ├── security_utils.py
│   ├── structs.py
│   └── utils.py
├── regipy_mcp_server/
│   ├── README.md
│   ├── claude_desktop_config.example.json
│   ├── server.py
│   └── test_local.py
├── regipy_tests/
│   ├── __init__.py
│   ├── cli_tests.py
│   ├── conftest.py
│   ├── data/
│   │   ├── BCD.xz
│   │   ├── NTUSER-WSL.DAT.xz
│   │   ├── NTUSER.DAT.xz
│   │   ├── NTUSER_BAGMRU.DAT.xz
│   │   ├── NTUSER_modified.DAT.xz
│   │   ├── NTUSER_with_winscp.DAT.xz
│   │   ├── SAM.xz
│   │   ├── SECURITY.xz
│   │   ├── SOFTWARE.xz
│   │   ├── SYSTEM.xz
│   │   ├── SYSTEM_2.xz
│   │   ├── SYSTEM_B.LOG1.xz
│   │   ├── SYSTEM_B.LOG2.xz
│   │   ├── SYSTEM_B.xz
│   │   ├── SYSTEM_WIN_10_1709.xz
│   │   ├── UsrClass.dat.LOG1.xz
│   │   ├── UsrClass.dat.LOG2.xz
│   │   ├── UsrClass.dat.xz
│   │   ├── amcache.hve.xz
│   │   ├── corrupted_system_hive.xz
│   │   ├── ntuser_software_partial.xz
│   │   ├── transactions_NTUSER.DAT.xz
│   │   ├── transactions_ntuser.dat.log1.xz
│   │   └── transactions_ntuser.dat.log2.xz
│   ├── profiling.py
│   ├── test_packaging.py
│   ├── test_utils.py
│   ├── tests.py
│   └── validation/
│       ├── plugin_validation.md
│       ├── plugin_validation.py
│       ├── utils.py
│       ├── validation.py
│       └── validation_tests/
│           ├── __init_.py
│           ├── active_control_set_validation.py
│           ├── amcache_validation.py
│           ├── app_paths_plugin_validation.py
│           ├── backuprestore_plugin_validation.py
│           ├── bam_validation.py
│           ├── boot_entry_list_plugin_validation.py
│           ├── boot_key_plugin_validation.py
│           ├── codepage_validation.py
│           ├── computer_name_plugin_validation.py
│           ├── crash_dump_validation.py
│           ├── diag_sr_validation.py
│           ├── disable_last_access_validation.py
│           ├── disablesr_plugin_validation.py
│           ├── domain_sid_plugin_validation.py
│           ├── execution_policy_plugin_validation.py
│           ├── host_domain_name_plugin_validation.py
│           ├── image_file_execution_options_validation.py
│           ├── installed_programs_ntuser_validation.py
│           ├── installed_programs_software_plugin_validation.py
│           ├── last_logon_plugin_validation.py
│           ├── local_sid_plugin_validation.py
│           ├── lsa_packages_plugin_validation.py
│           ├── mounted_devices_plugin_validation.py
│           ├── network_data_plugin_validation.py
│           ├── network_drives_plugin_validation.py
│           ├── networklist_plugin_validation.py
│           ├── ntuser_classes_installer_plugin_validation.py
│           ├── ntuser_persistence_validation.py
│           ├── ntuser_userassist_validation.py
│           ├── pagefile_plugin_validation.py
│           ├── previous_winver_plugin_validation.py
│           ├── print_demon_plugin_validation.py
│           ├── processor_architecture_validation.py
│           ├── profile_list_plugin_validation.py
│           ├── ras_tracing_plugin_validation.py
│           ├── routes_validation.py
│           ├── safeboot_configuration_validation.py
│           ├── samparse_plugin_validation.py
│           ├── services_plugin_validation.py
│           ├── shell_bag_ntuser_plugin_validation.py
│           ├── shell_bag_usrclass_plugin_validation.py
│           ├── shimcache_validation.py
│           ├── shutdown_validation.py
│           ├── software_classes_installer_plugin_validation.py
│           ├── software_persistence_validation.py
│           ├── spp_clients_plugin_validation.py
│           ├── susclient_plugin_validation.py
│           ├── terminal_services_history_validation.py
│           ├── timezone_data2_validation.py
│           ├── timezone_data_validation.py
│           ├── typed_paths_plugin_validation.py
│           ├── typed_urls_plugin_validation.py
│           ├── uac_status_plugin_validation.py
│           ├── usb_devices_plugin_validation.py
│           ├── usbstor_plugin_validation.py
│           ├── wdigest_plugin_validation.py
│           ├── windows_defender_plugin_validation.py
│           ├── winrar_plugin_validation.py
│           ├── winscp_saved_sessions_plugin_validation.py
│           ├── winver_plugin_validation.py
│           ├── word_wheel_query_ntuser_validation.py
│           └── wsl_plugin_validation.py
├── renovate.json
└── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  # Python dependencies
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 5
    commit-message:
      prefix: "deps"

  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    commit-message:
      prefix: "ci"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install ruff
        run: pip install ruff

      - name: Run ruff linter
        run: ruff check .

      - name: Run ruff formatter check
        run: ruff format --check .

  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]

    steps:
      - uses: actions/checkout@v6

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}

      - name: Cache pip dependencies
        uses: actions/cache@v5
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
          restore-keys: |
            ${{ runner.os }}-pip-${{ matrix.python-version }}-
            ${{ runner.os }}-pip-

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[full,dev]"

      - name: Run tests with pytest
        run: pytest -v regipy_tests/tests.py

      - name: Run CLI tests
        run: pytest -v regipy_tests/cli_tests.py

      - name: Run packaging tests
        run: pytest -v regipy_tests/test_packaging.py

      - name: Run plugin validation
        run: PYTHONPATH=. python regipy_tests/validation/plugin_validation.py

  validation-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[full,dev]"

      - name: Generate plugin validation documentation
        run: PYTHONPATH=. python regipy_tests/validation/plugin_validation.py

      - name: Upload validation documentation
        uses: actions/upload-artifact@v6
        with:
          name: plugin-validation-docs
          path: regipy_tests/validation/plugin_validation.md

  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[full,dev]"

      - name: Run mypy
        run: mypy regipy/ --ignore-missing-imports
        continue-on-error: true  # Allow failures while adding type hints incrementally

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e ".[full,dev]"
          pip install pip-audit cyclonedx-bom

      - name: Run pip-audit (vulnerability scan)
        run: pip-audit --skip-editable

      - name: Generate SBOM (CycloneDX)
        run: |
          cyclonedx-py environment -o sbom.json --of JSON
          cyclonedx-py environment -o sbom.xml --of XML

      - name: Upload SBOM
        uses: actions/upload-artifact@v6
        with:
          name: sbom
          path: |
            sbom.json
            sbom.xml


================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish to PyPI

on:
  release:
    types: [published]

permissions:
  contents: write  # Needed to attach assets to release

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.11"

      - name: Install build dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build cyclonedx-bom

      - name: Build package
        run: python -m build

      - name: Generate SBOM (CycloneDX)
        run: |
          pip install -e ".[full]"
          cyclonedx-py environment -o sbom.json --of JSON
          cyclonedx-py environment -o sbom.xml --of XML

      - name: Upload build artifacts
        uses: actions/upload-artifact@v6
        with:
          name: dist
          path: dist/

      - name: Upload SBOM artifacts
        uses: actions/upload-artifact@v6
        with:
          name: sbom
          path: |
            sbom.json
            sbom.xml

  publish:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write  # Required for trusted publishing
      contents: write  # Required to attach assets to release

    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v7
        with:
          name: dist
          path: dist/

      - name: Download SBOM artifacts
        uses: actions/download-artifact@v7
        with:
          name: sbom
          path: sbom/

      # Uses trusted publishing if configured, otherwise falls back to token
      - name: Publish to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          # Fallback to API token if trusted publishing is not configured
          # To use trusted publishing, configure it at https://pypi.org/manage/account/publishing/
          password: ${{ secrets.PYPI_API_TOKEN }}
          skip-existing: true

      - name: Attach SBOM to GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: |
            sbom/sbom.json
            sbom/sbom.xml


================================================
FILE: .github/workflows/scorecard.yml
================================================
name: Scorecard supply-chain security

on:
  # For Branch-Protection check. Only the default branch is supported.
  branch_protection_rule:
  # To ensure Maintained check is refreshed periodically.
  schedule:
    - cron: '21 5 * * 2'
  push:
    branches: [ "master" ]

# Declare default permissions as read only.
permissions: read-all

jobs:
  analysis:
    name: Scorecard analysis
    runs-on: ubuntu-latest
    permissions:
      security-events: write   # Needed to upload results to code scanning
      id-token: write          # Needed for publishing to OSSF API
      # contents: read         # Uncomment for private repos
      # actions: read          # Uncomment for private repos

    steps:
      - name: "Checkout code"
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: "Run analysis"
        uses: ossf/scorecard-action@v2.4.3
        with:
          results_file: results.sarif
          results_format: sarif
          publish_results: true
          # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Optional: for private repos or enabling Branch-Protection on public ones

      - name: "Upload artifact"
        uses: actions/upload-artifact@v6
        with:
          name: SARIF file
          path: results.sarif
          retention-days: 5

      - name: "Upload to code-scanning"
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: results.sarif


================================================
FILE: .gitignore
================================================
*.ipynb

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

.idea
.vscode

.claude/*

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

.DS_Store


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: check-merge-conflict

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.4
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.13.0
    hooks:
      - id: mypy
        additional_dependencies: []
        args: [--ignore-missing-imports]
        pass_filenames: false
        entry: mypy regipy/


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [6.1.0] - 2025-12-27

### Added

- **22 new plugins** covering additional forensic artifacts:
  - **NTUSER plugins**: `recentdocs`, `comdlg32`, `runmru`, `muicache`, `appkeys`, `sysinternals`, `putty`
  - **SOFTWARE plugins**: `app_paths`, `appinit_dlls`, `appcert_dlls`, `appcompat_flags`, `windows_defender`, `powershell_logging`, `execution_policy`, `networklist`
  - **SYSTEM plugins**: `usb_devices`, `mounted_devices`, `shares`, `pagefile`, `lsa_packages`, `pending_file_rename`
  - **SAM plugins**: `samparse` - Parses user accounts with login times, password info, account flags

- **Plugin validation system** - Plugins are now tracked for validation status
  - Validation status stored in `validated_plugins.json`, generated by the test framework and shipped with the package
  - `is_plugin_validated()` function to check validation status
  - Unvalidated plugins log a warning when executed

- **`--include-unvalidated` CLI flag** for `regipy-plugins-run` command
  - By default, only validated plugins are executed
  - Use this flag to include plugins that don't have validation test cases

- **`include_unvalidated` parameter** for `run_relevant_plugins()` function
  - Default: `False` (only validated plugins run)
  - Set to `True` to include unvalidated plugins

### Changed

- **Default plugin behavior**: Only validated plugins run by default. This is a safer default as unvalidated plugins may return incomplete or inaccurate data.
- Updated README with comprehensive plugin list organized by hive type
- Updated plugin validation documentation

## [6.0.0] - 2025-12-25

### Breaking Changes

- **Minimum Python version raised to 3.9** - Dropped support for Python 3.6, 3.7, and 3.8
- **Removed `attrs` dependency** - All data classes (`Cell`, `VKRecord`, `LIRecord`, `Value`, `Subkey`) now use Python's built-in `dataclasses` module instead of `attrs`
  - If you used `attr.asdict()` on these classes, switch to `dataclasses.asdict()`
  - If you used `attr.fields()` or other attrs introspection, switch to `dataclasses.fields()`
- **Removed `setup.py`** - Package now uses `pyproject.toml` exclusively (PEP 517/518)

### Added

- `pyproject.toml` with full PEP 621 metadata
- `py.typed` marker for PEP 561 type checking support
- Pre-commit configuration with ruff and mypy hooks
- Consolidated CI workflow with test matrix for Python 3.9-3.13
- Development documentation in README

### Changed

- Migrated from `flake8` to `ruff` for linting and formatting
- Modernized Python syntax throughout codebase (f-strings, type hints, import sorting)
- Consolidated GitHub Actions workflows into unified `ci.yml` and `publish.yml`
- Updated all GitHub Actions to latest versions (v4/v5)

### Removed

- `setup.py` (replaced by `pyproject.toml`)
- `.flake8` configuration (replaced by ruff config in `pyproject.toml`)
- Legacy GitHub workflow files (`python-package.yml`, `python-publish.yml`, `tests.yml`)

## [5.2.0] and earlier

See [GitHub Releases](https://github.com/mkorman90/regipy/releases) for previous versions.


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md - regipy

> OS-independent Python library for parsing offline Windows registry hives

## Project Overview

regipy is a forensic-focused library for parsing Windows registry hive files (files with REGF header). It's designed for digital forensics and incident response (DFIR) workflows, providing both a Python API and CLI tools.

### Core Capabilities

- Parse offline registry hives without Windows dependencies
- Recursive traversal of keys and values from any path
- Transaction log recovery (dirty hive reconstruction)
- Hive comparison/diffing (like RegShot)
- Extensible plugin system for artifact extraction
- Timeline generation for forensic analysis

## Architecture

```
regipy/
├── registry.py          # Core RegistryHive class - entry point for all parsing
├── structs.py           # Binary struct definitions (REGF header, NK/VK records, etc.)
├── hive_types.py        # Hive type constants (NTUSER, SYSTEM, SOFTWARE, SAM, etc.)
├── exceptions.py        # Custom exceptions (RegistryKeyNotFoundException, etc.)
├── utils.py             # Helpers: convert_wintime, boomerang_stream, etc.
├── recovery.py          # Transaction log application logic
├── plugins/
│   ├── plugin.py        # Base Plugin class - all plugins inherit from this
│   ├── utils.py         # run_relevant_plugins() - auto-detects hive and runs matching plugins
│   ├── ntuser/          # NTUSER.DAT plugins (persistence, typed_urls, user_assist, etc.)
│   ├── system/          # SYSTEM hive plugins (computer_name, shimcache, bam, bootkey, etc.)
│   ├── software/        # SOFTWARE hive plugins (installed_programs, profilelist, etc.)
│   ├── sam/             # SAM hive plugins (local_sid, etc.)
│   ├── security/        # SECURITY hive plugins
│   ├── amcache/         # Amcache.hve plugins
│   └── usrclass/        # UsrClass.dat plugins (shellbags)
├── cli.py               # CLI entry points
regipy_tests/
├── data/                # Test hive files (often .xz compressed)
├── validation/          # ValidationCase framework for plugin testing
docs/
└── PLUGINS.md           # Plugin development guide
```

## Key Classes and Patterns

### RegistryHive

The main entry point. Handles hive parsing, key navigation, and value retrieval.

```python
from regipy.registry import RegistryHive

reg = RegistryHive('/path/to/NTUSER.DAT')

# Navigate to a key
key = reg.get_key(r'Software\Microsoft\Windows\CurrentVersion\Run')

# Get values
values = key.get_values(as_json=True)

# Iterate subkeys
for sk in key.iter_subkeys():
    print(sk.name, sk.header.last_modified)

# Recursive traversal
for entry in reg.recurse_subkeys(as_json=True):
    print(entry)

# Control sets (SYSTEM hive)
for path in reg.get_control_sets(r'Control\ComputerName\ComputerName'):
    # Yields: ControlSet001\Control\..., ControlSet002\Control\..., etc.
    pass
```

### Plugin System

Plugins inherit from `Plugin` base class and define:
- `NAME`: Snake_case identifier
- `DESCRIPTION`: Human-readable description  
- `COMPATIBLE_HIVE`: Hive type constant from `hive_types.py`
- `run()`: Extraction logic, appends results to `self.entries`

```python
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

class MyPlugin(Plugin):
    NAME = 'my_plugin'
    DESCRIPTION = 'Extract something useful'
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE
    
    def run(self):
        try:
            key = self.registry_hive.get_key(r'Software\MyKey')
            for value in key.get_values(as_json=self.as_json):
                self.entries.append(value)
        except RegistryKeyNotFoundException:
            pass  # Key doesn't exist in this hive
```

### Timestamp Handling

Always use `convert_wintime()` for Windows FILETIME conversion:

```python
from regipy.utils import convert_wintime

timestamp = convert_wintime(key.header.last_modified, as_json=True)
# Returns ISO format string when as_json=True, datetime object otherwise
```

### Transaction Log Recovery

```python
from regipy.recovery import apply_transaction_logs

apply_transaction_logs(
    hive_path='/path/to/NTUSER.DAT',
    transaction_log_path='/path/to/NTUSER.DAT.LOG1',
    restored_hive_path='/path/to/recovered.DAT'
)
```

## Hive Types

Defined in `hive_types.py`:

| Constant | Typical Files |
|----------|---------------|
| `NTUSER_HIVE_TYPE` | NTUSER.DAT |
| `SYSTEM_HIVE_TYPE` | SYSTEM |
| `SOFTWARE_HIVE_TYPE` | SOFTWARE |
| `SAM_HIVE_TYPE` | SAM |
| `SECURITY_HIVE_TYPE` | SECURITY |
| `USRCLASS_HIVE_TYPE` | UsrClass.dat |
| `AMCACHE_HIVE_TYPE` | Amcache.hve |
| `BCD_HIVE_TYPE` | BCD |

## CLI Tools

- `regipy-parse-header` - Display hive header, validate checksums
- `regipy-dump` - Export entire hive to JSON (or timeline with `-t`)
- `regipy-plugins-run` - Auto-detect hive type and run relevant plugins
- `regipy-diff` - Compare two hives, output differences to CSV
- `regipy-process-transaction-logs` - Apply transaction logs to recover dirty hive

## Development Guidelines

### Adding a New Plugin

1. Create file in appropriate `plugins/<hive_type>/` directory
2. Inherit from `Plugin`, set `NAME`, `DESCRIPTION`, `COMPATIBLE_HIVE`
3. Implement `run()` method - append results to `self.entries`
4. Use try/except for `RegistryKeyNotFoundException` (key may not exist)
5. Add validation case in `regipy_tests/validation/`

### Validation Cases

Required for all new plugins:

```python
from regipy_tests.validation.validation import ValidationCase
from regipy.plugins.system.my_plugin import MyPlugin

class MyPluginValidationCase(ValidationCase):
    plugin = MyPlugin
    test_hive_file_name = "SYSTEM_WIN_10.xz"  # Must be in regipy_tests/data/
    
    # Option 1: Check for presence of specific entries
    expected_entries = [
        {"field": "value", ...}
    ]
    
    # Option 2: Exact match
    exact_expected_result = [...]
    
    expected_entries_count = 5  # Optional count validation
```

### Code Style

- Use `logging` module (not `logbook` in newer code)
- Prefer `as_json=True` parameter for JSON-serializable output
- Handle corrupted values gracefully (`is_corrupted` field)
- Document Windows-specific quirks in comments

## Installation Variants

```bash
pip install regipy[full]  # All dependencies including compiled ones
pip install regipy        # Minimal dependencies
pip install -e .[full]    # Development install
```

## Testing

```bash
pytest regipy_tests/
```

Test hives are stored as `.xz` compressed files in `regipy_tests/data/`.

## Common Forensic Artifacts by Hive

**NTUSER.DAT**: User activity
- Run/RunOnce keys (persistence)
- TypedURLs (browser history)
- UserAssist (program execution)
- RecentDocs, MRU lists

**SYSTEM**: System configuration
- ComputerName
- Shimcache/AppCompatCache (execution history)
- BAM/DAM (background activity)
- Services, network interfaces

**SOFTWARE**: Installed software
- Uninstall keys
- ProfileList (user profiles)
- Installed programs

**Amcache.hve**: Application compatibility
- File execution with SHA1 hashes
- Driver information

**UsrClass.dat**: Shell data
- Shellbags (folder access history)

## MCP Server Integration

regipy includes an MCP (Model Context Protocol) server that enables natural language forensic analysis through Claude Desktop or other MCP-compatible AI assistants.

### What it Does

The MCP server bridges Claude and regipy's plugin ecosystem, allowing investigators to:
- Ask forensic questions in plain English instead of remembering CLI syntax
- Auto-detect hive types from a directory of collected registry files
- Leverage all 75+ plugins without knowing which plugin extracts which artifact
- Get correlated results across multiple hives in a single conversational query

### Example Workflow

Instead of:
```bash
regipy-plugins-run SYSTEM -o system_output.json
regipy-plugins-run NTUSER.DAT -o ntuser_output.json
# manually correlate results...
```

You can ask:
> "What persistence mechanisms exist on this machine?"

Claude will automatically run both `software_persistence` and `ntuser_persistence` plugins and synthesize the results.

### Setup

1. Install regipy with MCP support
2. Configure Claude Desktop to use the regipy MCP server
3. Point at your evidence directory
4. Start investigating conversationally

### Design Philosophy

The MCP server exposes plugin metadata (names, descriptions, compatible hives) to Claude, letting it reason about which plugins to run based on the investigator's natural language questions. This means:
- New plugins automatically become available to Claude without prompt updates
- Claude can chain multiple plugins when a question spans artifacts
- Investigators can follow their instincts with follow-up questions

For more details, see the blog post: [Regipy MCP: Natural Language Registry Forensics with Claude](https://medium.com/dfir-dudes/regipy-mcp-natural-language-registry-forensics-with-claude-984d378784d6)

## External References

### Microsoft Documentation

- [Registry Hives (Win32)](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-hives) - Official overview of hive concepts, file formats, and locations
- [Windows Registry for Advanced Users](https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/windows-registry-advanced-users) - Detailed reference on registry structure, data types, and hive files
- [Inside the Registry](https://learn.microsoft.com/en-us/previous-versions/cc750583(v=technet.10)) - Mark Russinovich's deep dive into registry internals (cells, bins, hive file format)

### Community Resources

- [Windows Registry File Format Specification](https://github.com/msuhanov/regf) - Maxim Suhanov's comprehensive REGF format documentation (the de facto standard for forensic tool authors)
- [Google Project Zero: Registry Hives](https://projectzero.google/2024/10/the-windows-registry-adventure-4-hives.html) - Deep technical analysis of hive internals and security boundaries

### Registry File Locations

| Hive | File Path |
|------|-----------|
| SYSTEM | `%SystemRoot%\System32\config\SYSTEM` |
| SOFTWARE | `%SystemRoot%\System32\config\SOFTWARE` |
| SAM | `%SystemRoot%\System32\config\SAM` |
| SECURITY | `%SystemRoot%\System32\config\SECURITY` |
| DEFAULT | `%SystemRoot%\System32\config\DEFAULT` |
| NTUSER.DAT | `%UserProfile%\NTUSER.DAT` |
| UsrClass.dat | `%LocalAppData%\Microsoft\Windows\UsrClass.dat` |
| Amcache.hve | `%SystemRoot%\AppCompat\Programs\Amcache.hve` |

### Transaction Logs

Registry hives use transaction logs (`.LOG1`, `.LOG2`) for crash recovery. When a hive's sequence numbers don't match (primary ≠ secondary), the hive is "dirty" and transaction logs should be applied before analysis. regipy handles this via:

```bash
regipy-process-transaction-logs NTUSER.DAT -p ntuser.dat.log1 -s ntuser.dat.log2 -o recovered.DAT
```

Or programmatically via `regipy.recovery.apply_transaction_logs()`.


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
# Include the README
include *.md

# Include the license file
include LICENSE.txt

recursive-include docs *



================================================
FILE: README.md
================================================
# regipy

[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mkorman90/regipy/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mkorman90/regipy)

> **⚠️ Breaking Changes in v6.0.0**
>
> Version 6.0.0 includes significant modernization changes:
> - **Python 3.9+ required** - Dropped support for Python 3.6, 3.7, and 3.8
> - **`attrs` library removed** - Data classes now use Python's built-in `dataclasses` module
> - If your code imports internal classes (`Cell`, `VKRecord`, `Value`, `Subkey`) and uses `attrs` functions like `attr.asdict()`, switch to `dataclasses.asdict()`
>
> See the [CHANGELOG](CHANGELOG.md) for full details.

Regipy is a python library for parsing offline registry hives (Hive files with REGF header). regipy has a lot of capabilities:
* Use as a library:
    * Recurse over the registry hive, from root or a given path and get all subkeys and values
    * Read specific subkeys and values
    * Apply transaction logs on a registry hive
* Command Line Tools
    * Dump an entire registry hive to json
    * Apply transaction logs on a registry hive
    * Compare registry hives
    * Execute plugins from a robust plugin system (i.e: amcache, shimcache, extract computer name...)

**Requires Python 3.9 or higher.**

## Installation

Regipy latest version can be installed from pypi:

```bash
pip install regipy[full]
```

NOTE: ``regipy[full]`` installs dependencies that require compilation tools and might take some time.
It is possible to install a version with relaxed dependencies, by omitting the ``[full]``.

Also, it is possible to install from source by cloning the repository and executing:
```bash
pip install --editable .[full]
```


## CLI

#### Parse the header:
```bash
regipy-parse-header ~/Documents/TestEvidence/Registry/SYSTEM
```
Example output:
```
╒════════════════════════╤══════════╕
│ signature              │ b'regf'  │
├────────────────────────┼──────────┤
│ primary_sequence_num   │ 11639    │
├────────────────────────┼──────────┤
│ secondary_sequence_num │ 11638    │
├────────────────────────┼──────────┤
│ last_modification_time │ 0        │
├────────────────────────┼──────────┤
│ major_version          │ 1        │
├────────────────────────┼──────────┤
│ minor_version          │ 5        │
├────────────────────────┼──────────┤
│ file_type              │ 0        │
├────────────────────────┼──────────┤
│ file_format            │ 1        │
├────────────────────────┼──────────┤
│ root_key_offset        │ 32       │
├────────────────────────┼──────────┤
│ hive_bins_data_size    │ 10534912 │
├────────────────────────┼──────────┤
│ clustering_factor      │ 1        │
├────────────────────────┼──────────┤
│ file_name              │ SYSTEM   │
├────────────────────────┼──────────┤
│ checksum               │ 0        │
╘════════════════════════╧══════════╛
[2019-02-09 13:46:12.111654] WARNING: regipy.cli: Hive is not clean! You should apply transaction logs
```
* When parsing the header of a hive, also checksum validation and transaction validations are done


#### Dump entire hive to disk (this might take some time)
```bash
regipy-dump ~/Documents/TestEvidence/Registry/NTUSER-CCLEANER.DAT -o /tmp/output.json
```
regipy-dump util can also output a timeline instead of a JSON, by adding the `-t` flag


#### Run relevant plugins on Hive
```bash
regipy-plugins-run ~/Documents/TestEvidence/Registry/SYSTEM -o /tmp/plugins_output.json
```
The hive type will be detected automatically and the relevant plugins will be executed.
[**See the plugins section for more information**](docs/PLUGINS.md)

#### Compare registry hives
Compare registry hives of the same type and output to CSV (if `-o` is not specified output will be printed to screen)
```bash
regipy-diff NTUSER.dat NTUSER_modified.dat -o /tmp/diff.csv
```
Example output:
```
[2019-02-11 19:49:18.824245] INFO: regipy.cli: Comparing NTUSER.DAT vs NTUSER_modified.DAT
╒══════════════╤══════════════╤════════════════════════════════════════════════════════════════════════════════╤════════════════════════════════════════════════╕
│ difference   │ first_hive   │ second_hive                                                                    │ description                                    │
╞══════════════╪══════════════╪════════════════════════════════════════════════════════════════════════════════╪════════════════════════════════════════════════╡
│ new_subkey   │              │ 2019-02-11T19:46:31.832134+00:00                                               │ \Software\Microsoft\legitimate_subkey          │
├──────────────┼──────────────┼────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────┤
│ new_value    │              │ not_a_malware: c:\temp\legitimate_binary.exe @ 2019-02-11 19:45:25.516346+00:00 │ \Software\Microsoft\Windows\CurrentVersion\Run │
╘══════════════╧══════════════╧════════════════════════════════════════════════════════════════════════════════╧════════════════════════════════════════════════╛
[2019-02-11 19:49:18.825328] INFO: regipy.cli: Detected 2 differences
```

## Recover a registry hive, using transaction logs:
```bash
regipy-process-transaction-logs NTUSER.DAT -p ntuser.dat.log1 -s ntuser.dat.log2 -o recovered_NTUSER.dat
```
After recovering, compare the hives with registry-diff to see what changed

## Using as a library

#### Initiate the registry hive object
```python
from regipy.registry import RegistryHive
reg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/Vibranium-NTUSER.DAT')
```

#### Iterate recursively over the entire hive, from root key
```python
for entry in reg.recurse_subkeys(as_json=True):
    print(entry)
```

#### Iterate over a key and get all subkeys and their modification time:
```python
for sk in reg.get_key('Software').iter_subkeys():
    print(sk.name, convert_wintime(sk.header.last_modified).isoformat())

Adobe 2019-02-03T22:05:32.525965
AppDataLow 2019-02-03T22:05:32.526047
McAfee 2019-02-03T22:05:32.526140
Microsoft 2019-02-03T22:05:32.526282
Netscape 2019-02-03T22:05:32.526352
ODBC 2019-02-03T22:05:32.526521
Policies 2019-02-03T22:05:32.526592
```

#### Get the values of a key:
```python
reg.get_key('Software\Microsoft\Internet Explorer\BrowserEmulation').get_values(as_json=True)
[{'name': 'CVListTTL',
  'value': 0,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'UnattendLoaded',
  'value': 0,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'TLDUpdates',
  'value': 0,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'CVListXMLVersionLow',
  'value': 2097211,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'CVListXMLVersionHigh',
  'value': None,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'CVListLastUpdateTime',
  'value': None,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'IECompatVersionHigh',
  'value': None,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'IECompatVersionLow',
  'value': 2097211,
  'value_type': 'REG_DWORD',
  'is_corrupted': False},
 {'name': 'StaleCompatCache',
  'value': 0,
  'value_type': 'REG_DWORD',
  'is_corrupted': False}]
```

#### Use as a plugin:
```python
from regipy.plugins.ntuser.ntuser_persistence import NTUserPersistencePlugin
NTUserPersistencePlugin(reg, as_json=True).run()

{
	'Software\\Microsoft\\Windows\\CurrentVersion\\Run': {
		'timestamp': '2019-02-03T22:10:52.655462',
		'values': [{
			'name': 'Sidebar',
			'value': '%ProgramFiles%\\Windows Sidebar\\Sidebar.exe /autoRun',
			'value_type': 'REG_EXPAND_SZ',
			'is_corrupted': False
		}]
	}
}
```

#### Run all relevant plugins for a specific hive
```python
from regipy.plugins.utils import run_relevant_plugins
reg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/SYSTEM')
run_relevant_plugins(reg, as_json=True)

{
	'routes': {},
	'computer_name': [{
		'control_set': 'ControlSet001\\Control\\ComputerName\\ComputerName',
		'computer_name': 'DESKTOP-5EG84UG',
		'timestamp': '2019-02-03T22:19:28.853219'
	}]
}
```

## Validation cases
[Validation cases report](regipy_tests/validation/plugin_validation.md)

All new plugins should have one or more basic validation cases (which can be expanded in the future), for example:
```python
from regipy.plugins.system.bam import BAMPlugin
from regipy_tests.validation.validation import ValidationCase


class NTUserUserAssistValidationCase(ValidationCase):
    # define your plugin class
    plugin = BAMPlugin
    # define the test file name, which should be present in `regipy_tests/data`
    test_hive_file_name = "SYSTEM_WIN_10_1709.xz"

    # Use `expected_entries` to test for presence of a few samples from the plugin results
    expected_entries = [
        {
            "sequence_number": 9,
            "version": 1,
            "sid": "S-1-5-90-0-1",
            "executable": "\\Device\\HarddiskVolume2\\Windows\\System32\\dwm.exe",
            "timestamp": "2020-04-19T09:09:35.731816+00:00",
            "key_path": "\\ControlSet001\\Services\\bam\\state\\UserSettings\\S-1-5-90-0-1",
        }
    ]

    # OR use `exact_expected_result` to test for an exact result:
    exact_expected_result = [
        {
            "sequence_number": 9,
            "version": 1,
            "sid": "S-1-5-90-0-1",
            "executable": "\\Device\\HarddiskVolume2\\Windows\\System32\\dwm.exe",
            "timestamp": "2020-04-19T09:09:35.731816+00:00",
            "key_path": "\\ControlSet001\\Services\\bam\\state\\UserSettings\\S-1-5-90-0-1",
        },
        {
            "sequence_number": 8,
            "version": 1,
            "sid": "S-1-5-90-0-1",
            "executable": "\\Device\\HarddiskVolume2\\Windows\\System32\\cmd.exe",
            "timestamp": "2020-04-19T09:09:34.544224+00:00",
            "key_path": "\\ControlSet001\\Services\\bam\\state\\UserSettings\\S-1-5-90-0-1",
        }
    ]

    expected_entries_count = 2
```

## Development

### Setting up for development

```bash
# Clone the repository
git clone https://github.com/mkorman90/regipy.git
cd regipy

# Install in development mode with all dependencies
pip install -e ".[full,dev]"

# Install pre-commit hooks
pre-commit install
```

### Running tests

```bash
# Run all tests
pytest

# Run specific test files
pytest regipy_tests/tests.py
pytest regipy_tests/cli_tests.py

# Run plugin validation
PYTHONPATH=. python regipy_tests/validation/plugin_validation.py
```

### Code quality

```bash
# Run linter
ruff check .

# Run formatter
ruff format .

# Run type checker
mypy regipy/
```

### Testing GitHub Actions Locally

To test CI workflow changes locally before pushing, use [act](https://github.com/nektos/act):

```bash
# Install act (Fedora)
sudo dnf install act-cli

# Install act (macOS)
brew install act

# Install act (other)
# See https://nektosact.com/installation/index.html
```

Make sure Docker is running, then:

```bash
# List available jobs
act -l

# Run the lint job
act -j lint

# Run all jobs for a push event
act push

# Run the test job with a specific Python version
act -j test

# Test the build job from publish workflow (simulates a release)
act release -j build --eventpath /dev/stdin <<< '{"action": "published"}'
```

Note: Some jobs may require secrets. You can provide them with:

```bash
act -j publish --secret PYPI_API_TOKEN=your_token
```

## License

MIT


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

| Version | Supported |
| ------- | --------- |
| 6.x.x   | ✅         |
| < 6.0   | ❌         |

## Reporting a Vulnerability

Please report vulnerabilities via [GitHub Security Advisories](https://github.com/mkorman90/regipy/security/advisories/new).

## Supply Chain Security

This project implements several supply chain security measures:

- **SBOM**: Each release includes a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (`sbom.json`, `sbom.xml`) attached to the GitHub release
- **Dependency Scanning**: [pip-audit](https://github.com/pypa/pip-audit) runs on every CI build to detect known vulnerabilities
- **Dependabot**: Automated dependency updates are enabled for both Python packages and GitHub Actions
- **Trusted Publishing**: PyPI releases use [trusted publishing](https://docs.pypi.org/trusted-publishers/) via GitHub Actions OIDC


================================================
FILE: docs/PLUGINS.md
================================================
## regipy Plugins

* The plugin system is a robust and extensive feature that auto-detects the hive type and execute the relevant plugins.

To see a comprehensive list, please refer to the [Validation cases report](../validation/plugin_validation.md)

## Contributing new plugins
Adding a new plugin is very straight forward:
1. Copy the `regipy/plugins/plugin_template.py` file to the relevant folder (according to hive type)
2. Update the code:
   * Update the `NAME` parameter and the Class name accordingly (NAME in snake case, Class name in camel case)
   * Feel free to use/add any utility function to `regipy/utils.py` 
   * Import your class in `regipy/plugins/__init__.py`
3. Add a [validation case](../README.md#validation-cases). This is mandatory and replaces the old regipy tests.


================================================
FILE: docs/README.rst
================================================
regipy
==========
Regipy is a python library for parsing offline registry hives!

**Requires Python 3.9 or higher.**

Features:

* Use as a library
* Recurse over the registry hive, from root or a given path and get all subkeys and values
* Read specific subkeys and values
* Apply transaction logs on a registry hive
* Command Line Tools:
    * Dump an entire registry hive to json
    * Apply transaction logs on a registry hive
    * Compare registry hives
    * Execute plugins from a robust plugin system (i.e: amcache, shimcache, extract computer name...)

:Project page: https://github.com/mkorman90/regipy

Using as a library:
--------------------
.. code:: python

    from regipy.registry import RegistryHive
    reg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/Vibranium-NTUSER.DAT')

    # Iterate over a registry hive recursively:
    for entry in reg.recurse_subkeys(as_json=True):
        print(entry)

    # Iterate over a key and get all subkeys and their modification time:
    for sk in reg.get_key('Software').get_subkeys():
        print(sk.name, convert_wintime(sk.header.last_modified).isoformat())

    # Get values from a specific registry key:
    reg.get_key('Software\Microsoft\Internet Explorer\BrowserEmulation').get_values(as_json=True)

    # Use plugins:
    from regipy.plugins.ntuser.ntuser_persistence import NTUserPersistencePlugin
    NTUserPersistencePlugin(reg, as_json=True).run()

    # Run all validated plugins on a registry hive:
    run_relevant_plugins(reg, as_json=True)

    # Include unvalidated plugins (may return incomplete/inaccurate data):
    run_relevant_plugins(reg, as_json=True, include_unvalidated=True)

Install
^^^^^^^
Install regipy and the command line tools dependencies:

``pip install regipy[cli]``


NOTE: using pip with ``regipy[cli]`` instead of the plain ``regipy`` is a
significant change from version 1.9.x

For using regipy as a library, install only ``regipy`` which comes with fewer
dependencies:

``pip install regipy``

Plugin Validation
^^^^^^^^^^^^^^^^^
Regipy plugins are validated using test cases to ensure they return accurate data.
By default, only validated plugins are executed when using ``run_relevant_plugins()``
or the CLI tools.

To include unvalidated plugins (which may return incomplete or inaccurate data):

.. code:: python

    # As a library:
    run_relevant_plugins(reg, as_json=True, include_unvalidated=True)

.. code:: bash

    # CLI:
    regipy-plugins-run /path/to/hive -o output.json --include-unvalidated

Unvalidated plugins will log a warning when executed. Use them at your own discretion.

Available Plugins
^^^^^^^^^^^^^^^^^

**NTUSER Plugins:**

* ``user_assist`` - Parses User Assist execution history
* ``typed_urls`` - Retrieves typed URLs from IE history
* ``typed_paths`` - Retrieves typed file paths
* ``ntuser_persistence`` - Retrieves persistence entries (Run, RunOnce, etc.)
* ``shellbag_plugin`` - Parses Shellbag items
* ``network_drives_plugin`` - Retrieves mapped network drives
* ``terminal_services_history`` - Parses RDP/Terminal Server client data
* ``winrar_plugin`` - Parses WinRAR archive history
* ``winscp_saved_sessions`` - Retrieves WinSCP saved sessions
* ``word_wheel_query`` - Parses Windows Search word wheel query
* ``wsl`` - Gets WSL distribution information
* ``installed_programs_ntuser`` - Retrieves installed programs
* ``ntuser_classes_installer`` - Parses class installer information
* ``recentdocs`` - Parses recently opened documents
* ``comdlg32`` - Parses Open/Save dialog MRU lists
* ``runmru`` - Parses Run dialog MRU list
* ``muicache`` - Parses MUI Cache (application display names)
* ``appkeys`` - Parses application keyboard shortcuts
* ``sysinternals`` - Parses Sysinternals tools EULA acceptance
* ``putty`` - Parses PuTTY sessions and SSH host keys

**SOFTWARE Plugins:**

* ``installed_programs_software`` - Retrieves installed programs
* ``profilelist_plugin`` - Parses user profile information
* ``uac_plugin`` - Gets User Access Control settings
* ``winver_plugin`` - Gets OS version information
* ``last_logon_plugin`` - Gets last logon information
* ``image_file_execution_options`` - Retrieves IFEO entries
* ``print_demon_plugin`` - Gets installed printer ports
* ``ras_tracing`` - Gets tracing/debugging configuration
* ``disablesr_plugin`` - Gets system restore disable status
* ``spp_clients_plugin`` - Determines volumes monitored by VSS
* ``susclient_plugin`` - Extracts Windows Update client info
* ``software_classes_installer`` - Parses class installer information
* ``software_plugin`` - Retrieves persistence entries
* ``app_paths`` - Parses application paths registry entries
* ``appinit_dlls`` - Parses AppInit_DLLs persistence entries
* ``appcert_dlls`` - Parses AppCertDLLs persistence entries
* ``appcompat_flags`` - Parses application compatibility flags
* ``windows_defender`` - Parses Windows Defender configuration
* ``powershell_logging`` - Parses PowerShell logging configuration
* ``execution_policy`` - Parses PowerShell execution policies
* ``networklist`` - Parses network connection history

**SYSTEM Plugins:**

* ``shimcache`` - Parses Shimcache/AppCompatCache
* ``services`` - Enumerates services and parameters
* ``usbstor_plugin`` - Parses connected USB storage devices
* ``background_activity_moderator`` - Gets BAM execution data
* ``network_data`` - Gets network interface configuration
* ``routes`` - Gets network routes
* ``computer_name`` - Gets the computer name
* ``timezone_data`` / ``timezone_data2`` - Gets timezone configuration
* ``safeboot_configuration`` - Gets safeboot configuration
* ``active_control_set`` - Gets the active control set
* ``backuprestore_plugin`` - Gets backup/restore exclusion entries
* ``processor_architecture`` - Gets processor architecture info
* ``previous_winver_plugin`` - Gets previous Windows version info
* ``codepage`` - Gets system codepage information
* ``host_domain_name`` - Gets host and domain name
* ``crash_dump`` - Gets crash control information
* ``diag_sr`` - Gets diagnostic system restore information
* ``disable_last_access`` - Gets LastAccessTime disable status
* ``wdigest`` - Gets WDIGEST authentication configuration
* ``bootkey`` - Extracts the Windows boot key
* ``shutdown`` - Gets shutdown time data
* ``usb_devices`` - Parses USB device connection history
* ``mounted_devices`` - Parses mounted device information
* ``shares`` - Parses network share configuration
* ``pagefile`` - Parses pagefile configuration
* ``lsa_packages`` - Parses LSA security packages
* ``pending_file_rename`` - Parses pending file rename operations

**SAM Plugins:**

* ``local_sid`` - Extracts the machine local SID
* ``samparse`` - Parses user accounts from SAM hive

**SECURITY Plugins:**

* ``domain_sid`` - Extracts domain name and SID

**AMCACHE Plugins:**

* ``amcache`` - Parses Amcache application execution history

**BCD Plugins:**

* ``boot_entry_list`` - Lists Windows BCD boot entries

**USRCLASS Plugins:**

* ``usrclass_shellbag_plugin`` - Parses USRCLASS Shellbag items


================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "regipy"
version = "6.2.1"
description = "Python Registry Parser"
readme = "docs/README.rst"
license = {text = "MIT"}
authors = [
    {name = "Martin G. Korman", email = "martin@centauri.co.il"}
]
keywords = ["Python", "Python3", "registry", "windows registry", "registry parser"]
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "Natural Language :: English",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Libraries",
    "Topic :: Utilities",
]
requires-python = ">=3.9"
dependencies = [
    "construct>=2.10",
    "inflection>=0.5.1",
    "pytz",
]

[project.optional-dependencies]
cli = [
    "click>=7.0.0",
    "tabulate",
]
full = [
    "click>=7.0.0",
    "tabulate",
    "libfwsi-python>=20240315",
    "libfwps-python>=20240310",
]
dev = [
    "pytest>=8.0",
    "pytest-cov",
    "ruff",
    "mypy",
    "pre-commit",
    "tabulate",  # Required for plugin validation
    "tomli; python_version < '3.11'",  # For test_packaging.py on Python 3.9/3.10
]

[project.scripts]
regipy-parse-header = "regipy.cli:parse_header"
regipy-dump = "regipy.cli:registry_dump"
regipy-plugins-run = "regipy.cli:run_plugins"
regipy-plugins-list = "regipy.cli:list_plugins"
regipy-diff = "regipy.cli:reg_diff"
regipy-process-transaction-logs = "regipy.cli:parse_transaction_log"

[project.urls]
Homepage = "https://github.com/mkorman90/regipy/"
Repository = "https://github.com/mkorman90/regipy/"
Issues = "https://github.com/mkorman90/regipy/issues"

[tool.setuptools]
packages = ["regipy", "regipy.plugins", "regipy.plugins.ntuser", "regipy.plugins.system", "regipy.plugins.system.external", "regipy.plugins.software", "regipy.plugins.sam", "regipy.plugins.security", "regipy.plugins.amcache", "regipy.plugins.bcd", "regipy.plugins.usrclass"]
include-package-data = true

[tool.setuptools.package-data]
"regipy.plugins" = ["validated_plugins.json"]

[tool.ruff]
target-version = "py39"
line-length = 128

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # Pyflakes
    "I",      # isort
    "UP",     # pyupgrade (modernize syntax)
    "B",      # flake8-bugbear
    "C4",     # flake8-comprehensions
    "SIM",    # flake8-simplify
]
ignore = [
    "E501",   # line too long (handled by formatter)
    "B008",   # do not perform function calls in argument defaults
    "B904",   # raise from err (too noisy for existing code)
    "SIM108", # use ternary operator instead of if-else
    "SIM115", # use context manager for opening files
]

[tool.ruff.lint.per-file-ignores]
"regipy_tests/*" = ["B007"]  # Unused loop variables in tests are fine
"regipy/cli.py" = ["B007"]  # subkey_count is used after the loop
"regipy/cli_utils.py" = ["B007"]  # Loop counter used for progress
"regipy/plugins/utils.py" = ["B007"]  # Loop counter pattern

[tool.ruff.lint.isort]
known-first-party = ["regipy"]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"

[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
disallow_untyped_defs = false  # Start lenient, can increase strictness later
check_untyped_defs = true
ignore_missing_imports = true

[tool.pytest.ini_options]
testpaths = ["regipy_tests"]
python_files = ["tests.py", "test_*.py", "*_tests.py"]
addopts = "-v"

[tool.coverage.run]
source = ["regipy"]
branch = true

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "if __name__ == .__main__.:",
]


================================================
FILE: regipy/__init__.py
================================================
from .registry import *  # noqa: F401, F403

__title__ = "regipy"
__version__ = "6.2.1"


================================================
FILE: regipy/cli.py
================================================
import csv
import json
import logging
import os
import time
from dataclasses import asdict

import click
from tabulate import tabulate

from regipy.cli_utils import _normalize_subkey_fields, get_filtered_subkeys
from regipy.exceptions import RegistryKeyNotFoundException
from regipy.plugins.plugin import PLUGINS
from regipy.plugins.utils import run_relevant_plugins
from regipy.recovery import apply_transaction_logs
from regipy.regdiff import compare_hives
from regipy.registry import RegistryHive
from regipy.utils import _setup_logging, calculate_xor32_checksum

logger = logging.getLogger(__name__)


@click.command()
@click.argument(
    "hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option("-v", "--verbose", is_flag=True, default=True, help="Verbosity")
def parse_header(hive_path, verbose):
    _setup_logging(verbose=verbose)
    registry_hive = RegistryHive(hive_path)

    click.secho(tabulate(registry_hive.header.items(), tablefmt="fancy_grid"))

    if registry_hive.header.primary_sequence_num != registry_hive.header.secondary_sequence_num:
        click.secho("Hive is not clean! You should apply transaction logs", fg="red")

    calculated_checksum = calculate_xor32_checksum(registry_hive._stream.read(508))
    if registry_hive.header.checksum != calculated_checksum:
        click.secho("Hive is not clean! Header checksum does not match", fg="red")


@click.command()
@click.argument(
    "hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "-o",
    "output_path",
    type=click.Path(exists=False, dir_okay=False, resolve_path=True),
    required=False,
)
@click.option("-p", "--registry-path", help="A registry path to start iterating from")
@click.option(
    "-t",
    "--timeline",
    is_flag=True,
    default=False,
    help="Create a CSV timeline instead",
)
@click.option(
    "-l",
    "--hive-type",
    type=click.STRING,
    required=False,
    help="Specify a hive type, if it could not be identified for some reason",
)
@click.option(
    "-r",
    "--partial_hive_path",
    type=click.STRING,
    required=False,
    help='The path from which the partial hive actually starts, for example: -t ntuser -r "/Software" '
    "would mean this is actually a HKCU hive, starting from HKCU/Software",
)
@click.option("-v", "--verbose", is_flag=True, default=False, help="Verbosity")
@click.option(
    "-d",
    "--do-not-fetch-values",
    is_flag=True,
    default=False,
    help="Not fetching the values for each subkey makes the iteration way faster. Values count will still be returned",
)
@click.option(
    "-s",
    "--start-date",
    type=click.STRING,
    required=False,
    help='If "-s" was specified, fetch only values for subkeys starting this timestamp in isoformat',
)
@click.option(
    "-e",
    "--end-date",
    type=click.STRING,
    required=False,
    help='If "-e" was specified, fetch only values for subkeys until this timestamp in isoformat',
)
def registry_dump(
    hive_path,
    output_path,
    registry_path,
    timeline,
    hive_type,
    partial_hive_path,
    verbose,
    do_not_fetch_values,
    start_date,
    end_date,
):
    _setup_logging(verbose=verbose)
    registry_hive = RegistryHive(hive_path, hive_type=hive_type, partial_hive_path=partial_hive_path)

    start_time = time.monotonic()

    if registry_path:
        try:
            name_key_entry = registry_hive.get_key(registry_path)
        except RegistryKeyNotFoundException as ex:
            logger.debug(f"Did not find the key: {ex}")
            return
    else:
        name_key_entry = registry_hive.root

    if timeline and not output_path:
        click.secho("You must provide an output path if choosing timeline output!", fg="red")
        return

    if output_path:
        with open(output_path, "w") as output_file:
            if timeline:
                csvwriter = csv.DictWriter(
                    output_file,
                    delimiter=",",
                    quotechar='"',
                    quoting=csv.QUOTE_MINIMAL,
                    fieldnames=["timestamp", "subkey_name", "values_count", "values"],
                )
                csvwriter.writeheader()

            for subkey_count, entry in enumerate(
                get_filtered_subkeys(
                    registry_hive,
                    name_key_entry,
                    fetch_values=not do_not_fetch_values,
                    start_date=start_date,
                    end_date=end_date,
                )
            ):
                if timeline:
                    csvwriter.writerow(
                        {
                            "subkey_name": entry.path,
                            "timestamp": entry.timestamp,
                            "values_count": entry.values_count,
                            "values": entry.values,
                        }
                    )
                else:
                    output_file.write(
                        json.dumps(
                            asdict(
                                entry,
                            ),
                            separators=(
                                ",",
                                ":",
                            ),
                            default=_normalize_subkey_fields,
                        )
                    )
                    output_file.write("\n")
    else:
        for subkey_count, entry in enumerate(
            registry_hive.recurse_subkeys(name_key_entry, as_json=True, fetch_values=not do_not_fetch_values)
        ):
            click.secho(json.dumps(asdict(entry), indent=4))

    click.secho(f"Completed in {time.monotonic() - start_time}s ({subkey_count} subkeys enumerated)")


@click.command()
@click.argument(
    "hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "-o",
    "output_path",
    type=click.Path(exists=False, dir_okay=False, resolve_path=True),
    required=True,
    help="Output path for plugins result",
)
@click.option(
    "-p",
    "--plugins",
    type=click.STRING,
    required=False,
    help="A plugin or list of plugins to execute command separated",
)
@click.option(
    "-t",
    "--hive-type",
    type=click.STRING,
    required=False,
    help="Specify a hive type, if it could not be identified for some reason",
)
@click.option(
    "-r",
    "--partial_hive_path",
    type=click.STRING,
    required=False,
    help='The path from which the partial hive actually starts, for example: -t ntuser -r "/Software" '
    "would mean this is actually a HKCU hive, starting from HKCU/Software",
)
@click.option("-v", "--verbose", is_flag=True, default=False, help="Verbosity")
@click.option(
    "--include-unvalidated",
    is_flag=True,
    default=False,
    help="Include plugins that don't have validation test cases. "
    "These plugins may return incomplete or inaccurate data. Use at your own risk.",
)
def run_plugins(hive_path, output_path, plugins, hive_type, partial_hive_path, verbose, include_unvalidated):
    _setup_logging(verbose=verbose)
    registry_hive = RegistryHive(hive_path, hive_type=hive_type, partial_hive_path=partial_hive_path)
    click.secho(f"Loaded {len(PLUGINS)} plugins", fg="white")

    if plugins:
        plugin_names = {x.NAME for x in PLUGINS}
        plugins = plugins.split(",")
        plugins = set(plugins)
        if not plugins.issubset(plugin_names):
            click.secho(
                "Invalid plugin names given: {}".format(",".join(set(plugins) - plugin_names)),
                fg="red",
            )
            click.secho(
                "Use --help or -h to get list of plugins and their descriptions",
                fg="red",
            )
            return

    if include_unvalidated:
        click.secho(
            "Warning: Including unvalidated plugins. These may return incomplete or inaccurate data.",
            fg="yellow",
        )

    # Run relevant plugins
    plugin_results = run_relevant_plugins(
        registry_hive,
        as_json=True,
        plugins=plugins,
        include_unvalidated=include_unvalidated,
    )

    # If output path was set, dump results to disk
    if output_path:
        with open(output_path, "w") as f:
            f.write(json.dumps(plugin_results, indent=4))
    else:
        print(json.dumps(plugin_results, indent=4))
    click.secho(
        f"Finished: {len(plugin_results)}/{len(PLUGINS)} plugins matched the hive type",
        fg="green",
    )


@click.command()
def list_plugins():
    click.secho(
        tabulate(
            [(x.NAME, x.COMPATIBLE_HIVE, x.DESCRIPTION) for x in PLUGINS],
            headers=["Plugin Name", "Compatible hive", "Description"],
        )
    )


@click.command()
@click.argument(
    "first_hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.argument(
    "second_hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "-o",
    "output_path",
    type=click.Path(exists=False, dir_okay=False, resolve_path=True),
    required=False,
)
@click.option("-v", "--verbose", is_flag=True, default=False, help="Verbosity")
def reg_diff(first_hive_path, second_hive_path, output_path, verbose):
    _setup_logging(verbose=verbose)
    REGDIFF_HEADERS = ["difference", "first_hive", "second_hive", "description"]

    found_differences = compare_hives(first_hive_path, second_hive_path, verbose=verbose)
    click.secho(f"Comparing {os.path.basename(first_hive_path)} vs {os.path.basename(second_hive_path)}")

    if output_path:
        with open(output_path, "w") as csvfile:
            csvwriter = csv.writer(csvfile, delimiter="|", quoting=csv.QUOTE_MINIMAL)
            csvwriter.writerow(REGDIFF_HEADERS)
            for difference in found_differences:
                csvwriter.writerow(difference)
    else:
        click.secho(tabulate(found_differences, headers=REGDIFF_HEADERS, tablefmt="fancy_grid"))
    click.secho(f"Detected {len(found_differences)} differences", fg="green")


@click.command()
@click.argument(
    "hive_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "-p",
    "primary_log_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=True,
)
@click.option(
    "-s",
    "secondary_log_path",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True),
    required=False,
)
@click.option(
    "-o",
    "output_path",
    type=click.Path(exists=False, dir_okay=False, resolve_path=True),
    required=False,
)
@click.option("-v", "--verbose", is_flag=True, default=True, help="Verbosity")
def parse_transaction_log(hive_path, primary_log_path, secondary_log_path, output_path, verbose):
    _setup_logging(verbose=verbose)
    logger.info(f"Processing hive {hive_path} with transaction log {primary_log_path}")
    if secondary_log_path:
        logger.info(f"Processing hive {hive_path} with secondary transaction log {secondary_log_path}")

    restored_hive_path, recovered_dirty_pages_count = apply_transaction_logs(
        hive_path,
        primary_log_path,
        secondary_log_path=secondary_log_path,
        restored_hive_path=output_path,
        verbose=verbose,
    )
    if recovered_dirty_pages_count:
        click.secho(
            f"Recovered {recovered_dirty_pages_count} dirty pages. Restored hive is at {restored_hive_path}",
            fg="green",
        )


================================================
FILE: regipy/cli_utils.py
================================================
import binascii
import datetime as dt
import logging
from collections.abc import Iterator

import pytz
from click import progressbar

from regipy import NKRecord, RegistryHive, Subkey
from regipy.utils import MAX_LEN

logger = logging.getLogger(__name__)


def get_filtered_subkeys(
    registry_hive: RegistryHive,
    name_key_entry: NKRecord,
    start_date: str = None,
    end_date: str = None,
    verbose=False,
    fetch_values=True,
) -> Iterator[NKRecord]:
    """
    Get records filtered by the specified timestamps
    :param registry_hive: A RegistryHive object
    :param name_key_entry: A list of paths as strings
    :param start_date: Include only subkeys modified after the specified date
                     in isoformat UTC, for example: 2020-02-18T14:15:00.000000
    :param end_date: Include only subkeys modified before the specified date
                     in isoformat UTC, for example: 2020-02-20T14:15:00.000000
    """
    skipped_entries_count = 0
    if start_date:
        start_date = pytz.utc.localize(dt.datetime.fromisoformat(start_date))

    if end_date:
        end_date = pytz.utc.localize(dt.datetime.fromisoformat(end_date))

    with progressbar(registry_hive.recurse_subkeys(name_key_entry, fetch_values=False)) as reg_subkeys:
        for subkey_count, subkey in enumerate(reg_subkeys):
            if start_date and subkey.timestamp < start_date:
                skipped_entries_count += 1
                logger.debug(f"Skipping entry {subkey} which has a timestamp prior to start_date")
                continue

            if end_date and subkey.timestamp > end_date:
                skipped_entries_count += 1
                logger.debug(f"Skipping entry {subkey} which has a timestamp after the end_date")
                continue

            nk = registry_hive.get_key(subkey.path)
            yield Subkey(
                subkey_name=subkey.subkey_name,
                path=subkey.path,
                timestamp=subkey.timestamp,
                values=list(nk.iter_values(as_json=True)) if fetch_values else [],
                values_count=subkey.values_count,
            )
        logger.info(f"{skipped_entries_count} out of {subkey_count} subkeys were filtered out due to timestamp constrains")


def _normalize_subkey_fields(field) -> str:
    if isinstance(field, bytes):
        return binascii.b2a_hex(field[:MAX_LEN])
    elif isinstance(field, dt.datetime):
        return field.isoformat()
    return field


================================================
FILE: regipy/constants.py
================================================
# ShellBags Known GUIDs
KNOWN_GUIDS = {
    "008ca0b1-55b4-4c56-b8a8-4de4b299d3be": "Account Pictures",
    "00bcfc5a-ed94-4e48-96a1-3f6217f21990": "RoamingTiles",
    "00c6d95f-329c-409a-81d7-c46c66ea7f33": "Default Location",
    "00f2886f-cd64-4fc9-8ec5-30ef6cdbe8c3": "Scanners and Cameras",
    "0139d44e-6afe-49f2-8690-3dafcae6ffb8": "Programs",
    "0142e4d0-fb7a-11dc-ba4a-000ffe7ab428": "Biometric Devices (Biometrics)",
    "018d5c66-4533-4307-9b53-224de2ed1fe6": "OneDrive",
    "025a5937-a6be-4686-a844-36fe4bec8b6d": "Power Options",
    "031e4825-7b94-4dc3-b131-e946b44c8dd5": "Users Libraries",
    "04731b67-d933-450a-90e6-4acd2e9408fe": "Search Folder",
    "0482af6c-08f1-4c34-8c90-e17ec98b1e17": "Public Account Pictures",
    "054fae61-4dd8-4787-80b6-090220c4b700": "GameExplorer",
    "05d7b0f4-2121-4eff-bf6b-ed3f69b894d9": "Taskbar (Notification Area Icons)",
    "0762d272-c50a-4bb0-a382-697dcd729b80": "Users",
    "087da31b-0dd3-4537-8e23-64a18591f88b": "Windows Security Center",
    "0907616e-f5e6-48d8-9d61-a91c3d28106d": "Hyper-V Remote File Browsing",
    "0ac0837c-bbf8-452a-850d-79d08e667ca7": "Computer",
    "0afaced1-e828-11d1-9187-b532f1e9575d": "Folder Shortcut",
    "0b2baaeb-0042-4dca-aa4d-3ee8648d03e5": "Pictures Library",
    "0c15d503-d017-47ce-9016-7b3f978721cc": "Portable Device Values",
    "0c39a5cf-1a7a-40c8-ba74-8900e6df5fcd": "Recent Items",
    "0cd7a5c0-9f37-11ce-ae65-08002b2e1262": "Cabinet File",
    "0d4c3db6-03a3-462f-a0e6-08924c41b5d4": "History",
    "0df44eaa-ff21-4412-828e-260a8728e7f1": "Taskbar and Start Menu",
    "0f214138-b1d3-4a90-bba9-27cbc0c5389a": "Sync Setup",
    "11016101-e366-4d22-bc06-4ada335c892b": "Internet Explorer History and Feeds Shell Data Source for Windows Search",
    "1206f5f1-0569-412c-8fec-3204630dfb70": "Credential Manager",
    "13e7f612-f261-4391-bea2-39df4f3fa311": "Windows Desktop Search",
    "15ca69b3-30ee-49c1-ace1-6b5ec372afb5": "Sample Playlists",
    "15eae92e-f17a-4431-9f28-805e482dafd4": "Install New Programs (Get Programs)",
    "1723d66a-7a12-443e-88c7-05e1bfe79983": "Previous Versions Delegate Folder",
    "1777f761-68ad-4d8a-87bd-30b759fa33dd": "Favorites",
    "17cd9488-1228-4b2f-88ce-4298e93e0966": "Default Programs (Set User Defaults)",
    "18989b1d-99b5-455b-841c-ab7c74e4ddfc": "Videos",
    "190337d1-b8ca-4121-a639-6d472d16972a": "Search Results",
    "1a6fdba2-f42d-4358-a798-b74d745926c5": "Recorded TV",
    "1a9ba3a0-143a-11cf-8350-444553540000": "Shell Favorite Folder",
    "1ac14e77-02e7-4e5d-b744-2eb1ae5198b7": "System32",
    "1b3ea5dc-b587-4786-b4ef-bd1dc332aeae": "Libraries",
    "1cf1260c-4dd0-4ebb-811f-33c572699fde": "Music",
    "1d2680c9-0e2a-469d-b787-065558bc7d43": "Fusion Cache",
    "1e87508d-89c2-42f0-8a7e-645a0f50ca58": "Applications",
    "1f3427c8-5c10-4210-aa03-2ee45287d668": "User Pinned",
    "1f43a58c-ea28-43e6-9ec4-34574a16ebb7": "Windows Desktop Search MAPI Namespace Extension Class",
    "1f4de370-d627-11d1-ba4f-00a0c91eedba": "Search Results - Computers (Computer Search Results Folder, Network Computers)",
    "1fa9085f-25a2-489b-85d4-86326eedcd87": "Manage Wireless Networks",
    "208d2c60-3aea-1069-a2d7-08002b30309d": "My Network Places",
    "20d04fe0-3aea-1069-a2d8-08002b30309d": "My Computer",
    "2112ab0a-c86a-4ffe-a368-0de96e47012e": "Music",
    "21ec2020-3aea-1069-a2dd-08002b30309d": "Control Panel",
    "2227a280-3aea-1069-a2de-08002b30309d": "Printers and Faxes",
    "22877a6d-37a1-461a-91b0-dbda5aaebc99": "Recent Places",
    "2400183a-6185-49fb-a2d8-4a392a602ba3": "Public Videos",
    "241d7c96-f8bf-4f85-b01f-e2b043341a4b": "Workspaces Center (Remote Application and Desktop Connections)",
    "24d89e24-2f19-4534-9dde-6a6671fbb8fe": "Documents",
    "2559a1f0-21d7-11d4-bdaf-00c04f60b9f0": "Search",
    "2559a1f1-21d7-11d4-bdaf-00c04f60b9f0": "Help and Support",
    "2559a1f2-21d7-11d4-bdaf-00c04f60b9f0": "Windows Security",
    "2559a1f3-21d7-11d4-bdaf-00c04f60b9f0": "Run...",
    "2559a1f4-21d7-11d4-bdaf-00c04f60b9f0": "Internet",
    "2559a1f5-21d7-11d4-bdaf-00c04f60b9f0": "E-mail",
    "2559a1f6-21d7-11d4-bdaf-00c04f60b9f0": "OEM link",
    "2559a1f7-21d7-11d4-bdaf-00c04f60b9f0": "Set Program Access and Defaults",
    "259ef4b1-e6c9-4176-b574-481532c9bce8": "Game Controllers",
    "267cf8a9-f4e3-41e6-95b1-af881be130ff": "Location Folder",
    "26ee0668-a00a-44d7-9371-beb064c98683": "Control Panel",
    "2728520d-1ec8-4c68-a551-316b684c4ea7": "Network Setup Wizard",
    "27e2e392-a111-48e0-ab0c-e17705a05f85": "WPD Content Type Folder",
    "28803f59-3a75-4058-995f-4ee5503b023c": "Bluetooth Devices",
    "289978ac-a101-4341-a817-21eba7fd046d": "Sync Center Conflict Folder",
    "289a9a43-be44-4057-a41b-587a76d7e7f9": "Sync Results",
    "289af617-1cc3-42a6-926c-e6a863f0e3ba": "DLNA Media Servers Data Source",
    "292108be-88ab-4f33-9a26-7748e62e37ad": "Videos library",
    "2965e715-eb66-4719-b53f-1672673bbefa": "Results Folder",
    "2a00375e-224c-49de-b8d1-440df7ef3ddc": "LocalizedResourcesDir",
    "2b0f765d-c0e9-4171-908e-08a611b84ff6": "Cookies",
    "2c36c0aa-5812-4b87-bfd0-4cd0dfb19b39": "Original Images",
    "2e9e59c0-b437-4981-a647-9c34b9b90891": "Sync Setup Folder",
    "2f6ce85c-f9ee-43ca-90c7-8a9bd53a2467": "File History Data Source",
    "3080f90d-d7ad-11d9-bd98-0000947b0257": "Show Desktop",
    "3080f90e-d7ad-11d9-bd98-0000947b0257": "Window Switcher",
    "3214fab5-9757-4298-bb61-92a9deaa44ff": "Public Music",
    "323ca680-c24d-4099-b94d-446dd2d7249e": "Common Places",
    "328b0346-7eaf-4bbe-a479-7cb88a095f5b": "Layout Folder",
    "335a31dd-f04b-4d76-a925-d6b47cf360df": "Backup and Restore Center",
    "339719b5-8c47-4894-94c2-d8f77add44a6": "Pictures",
    "33e28130-4e1e-4676-835a-98395c3bc3bb": "Pictures",
    "352481e8-33be-4251-ba85-6007caedcf9d": "Temporary Internet Files",
    "35786d3c-b075-49b9-88dd-029876e11c01": "Portable Devices",
    "36011842-dccc-40fe-aa3d-6177ea401788": "Documents Search Results",
    "36eef7db-88ad-4e81-ad49-0e313f0c35f8": "Windows Update",
    "374de290-123f-4565-9164-39c4925e467b": "Downloads",
    "088e3905-0323-4b02-9826-5d99428e115f": "Downloads",
    "37efd44d-ef8d-41b1-940d-96973a50e9e0": "Desktop Gadgets",
    "38a98528-6cbf-4ca9-8dc0-b1e1d10f7b1b": "Connect To",
    "3add1653-eb32-4cb0-bbd7-dfa0abb5acca": "Pictures",
    "3c5c43a3-9ce9-4a9b-9699-2ac0cf6cc4bf": "Configure Wireless Network",
    "3d644c9b-1fb8-4f30-9b45-f670235f79c0": "Public Downloads",
    "3e7efb4c-faf1-453d-89eb-56026875ef90": "Windows Marketplace",
    "3eb685db-65f9-4cf6-a03a-e3ef65729f3d": "RoamingAppData",
    "3f2a72a7-99fa-4ddb-a5a8-c604edf61d6b": "Music Library",
    "3f6bc534-dfa1-4ab4-ae54-ef25a74e0107": "System Restore",
    "3f98a740-839c-4af7-8c36-5badfb33d5fd": "Documents library",
    "4026492f-2f69-46b8-b9bf-5654fc07e423": "Windows Firewall",
    "40419485-c444-4567-851a-2dd7bfa1684d": "Phone and Modem",
    "418c8b64-5463-461d-88e0-75e2afa3c6fa": "Explorer Browser Results Folder",
    "4234d49b-0245-4df3-b780-3893943456e1": "Applications",
    "4336a54d-038b-4685-ab02-99bb52d3fb8b": "Samples",
    "43668bf8-c14e-49b2-97c9-747784d784b7": "Sync Center",
    "437ff9c0-a07f-4fa0-af80-84b6c6440a16": "Command Folder",
    "450d8fba-ad25-11d0-98a8-0800361b1103": "My Documents",
    "4564b25e-30cd-4787-82ba-39e73a750b14": "Recent Items Instance Folder",
    "45c6afa5-2c13-402f-bc5d-45cc8172ef6b": "Toshiba Bluetooth Stack",
    "46137b78-0ec3-426d-8b89-ff7c3a458b5e": "Network Neighborhood",
    "46e06680-4bf0-11d1-83ee-00a0c90dc849": "NETWORK_DOMAIN",
    "48daf80b-e6cf-4f4e-b800-0e69d84ee384": "Libraries",
    "48e7caab-b918-4e58-a94d-505519c795dc": "Start Menu Folder",
    "491e922f-5643-4af4-a7eb-4e7a138d8174": "Videos",
    "4bd8d571-6d19-48d3-be97-422220080e43": "Music",
    "4bfefb45-347d-4006-a5be-ac0cb0567192": "Conflicts",
    "4c5c32ff-bb9d-43b0-b5b4-2d72e54eaaa4": "Saved Games",
    "4d9f7874-4e0c-4904-967b-40b0d20c3e4b": "Internet",
    "4dcafe13-e6a7-4c28-be02-ca8c2126280d": "Pictures Search Results",
    "5224f545-a443-4859-ba23-7b5a95bdc8ef": "People Near Me",
    "52528a6b-b9e3-4add-b60d-588c2dba842d": "Homegroup",
    "52a4f021-7b75-48a9-9f6b-4b87a210bc8f": "Quick Launch",
    "5399e694-6ce5-4d6c-8fce-1d8870fdcba0": "Control Panel command object for Start menu and desktop",
    "54a754c0-4bf1-11d1-83ee-00a0c90dc849": "NETWORK_SHARE",
    "56784854-c6cb-462b-8169-88e350acb882": "Contacts",
    "58e3c745-d971-4081-9034-86e34b30836a": "Speech Recognition Options",
    "59031a47-3f72-44a7-89c5-5595fe6b30ee": "Shared Documents Folder (Users Files)",
    "5b3749ad-b49f-49c1-83eb-15370fbd4882": "TreeProperties",
    "5b934b42-522b-4c34-bbfe-37a3ef7b9c90": "This Device Folder",
    "5c4f28b5-f869-4e84-8e60-f11db97c5cc7": "Generic (All folder items)",
    "5cd7aee2-2219-4a67-b85d-6c9ce15660cb": "Programs",
    "5ce4a5e9-e4eb-479d-b89f-130c02886155": "DeviceMetadataStore",
    "5e6c858f-0e22-4760-9afe-ea3317b67173": "Profile",
    "5e8fc967-829a-475c-93ea-51fce6d9ffce": "RealPlayer Cloud",
    "5ea4f148-308c-46d7-98a9-49041b1dd468": "Mobility Center Control Panel",
    "5f4eab9a-6833-4f61-899d-31cf46979d49": "Generic library",
    "5fa947b5-650a-4374-8a9a-5efa4f126834": "OpenDrive",
    "5fa96407-7e77-483c-ac93-691d05850de8": "Videos",
    "5fcd4425-ca3a-48f4-a57c-b8a75c32acb1": "Hewlett-Packard Recovery (Protect.dll)",
    "60632754-c523-4b62-b45c-4172da012619": "User Accounts",
    "625b53c3-ab48-4ec1-ba1f-a1ef4146fc19": "Start Menu",
    "62ab5d82-fdc1-4dc3-a9dd-070d1d495d97": "ProgramData",
    "62d8ed13-c9d0-4ce8-a914-47dd628fb1b0": "Regional and Language Options",
    "631958a6-ad0f-4035-a745-28ac066dc6ed": "Videos Library",
    "6365d5a7-0f0d-45e5-87f6-0da56b6a4f7d": "Common Files",
    "63da6ec0-2e98-11cf-8d82-444553540000": "Microsoft FTP Folder",
    "640167b4-59b0-47a6-b335-a6b3c0695aea": "Portable Media Devices",
    "645ff040-5081-101b-9f08-00aa002f954e": "Recycle Bin",
    "64693913-1c21-4f30-a98f-4e52906d3b56": "CLSID_AppInstanceFolder",
    "67718415-c450-4f3c-bf8a-b487642dc39b": "Windows Features",
    "6785bfac-9d2d-4be5-b7e2-59937e8fb80a": "Other Users Folder",
    "679f85cb-0220-4080-b29b-5540cc05aab6": "Home Folder",
    "67ca7650-96e6-4fdd-bb43-a8e774f73a57": "Home Group Control Panel (Home Group)",
    "692f0339-cbaa-47e6-b5b5-3b84db604e87": "Extensions Manager Folder",
    "69d2cf90-fc33-4fb7-9a0c-ebb0f0fcb43c": "Slide Shows",
    "6c8eec18-8d75-41b2-a177-8831d59d2d50": "Mouse",
    "6dfd7c5c-2451-11d3-a299-00c04f8ef6af": "Folder Options",
    "6f0cd92b-2e97-45d1-88ff-b0d186b8dedd": "Network Connections",
    "7007acc7-3202-11d1-aad2-00805fc1270e": "Network Connections",
    "708e1662-b832-42a8-bbe1-0a77121e3908": "Tree property value folder",
    "71689ac1-cc88-45d0-8a22-2943c3e7dfb3": "Music Search Results",
    "71d99464-3b6b-475c-b241-e15883207529": "Sync Results Folder",
    "724ef170-a42d-4fef-9f26-b60e846fba4f": "Administrative tools",
    "725be8f7-668e-4c7b-8f90-46bdb0936430": "Keyboard",
    "72b36e70-8700-42d6-a7f7-c9ab3323ee51": "Search Connector Folder",
    "74246bfc-4c96-11d0-abef-0020af6b0b7a": "Device Manager",
    "767e6811-49cb-4273-87c2-20f355e1085b": "Camera Roll",
    "76fc4e2d-d6ad-4519-a663-37bd56068185": "Printers",
    "78cb147a-98ea-4aa6-b0df-c8681f69341c": "Windows CardSpace",
    "78f3955e-3b90-4184-bd14-5397c15f1efc": "Performance Information and Tools",
    "7a979262-40ce-46ff-aeee-7884ac3b6136": "Add Hardware",
    "7a9d77bd-5403-11d2-8785-2e0420524153": "User Accounts (Users and Passwords)",
    "7b0db17d-9cd2-4a93-9733-46cc89022e7c": "Documents",
    "7b396e54-9ec5-4300-be0a-2482ebae1a26": "Gadgets",
    "7b81be6a-ce2b-4676-a29e-eb907a5126c5": "Programs and Features",
    "7bd29e00-76c1-11cf-9dd0-00a0c9034933": "Temporary Internet Files",
    "7bd29e01-76c1-11cf-9dd0-00a0c9034933": "Temporary Internet Files",
    "7be9d83c-a729-4d97-b5a7-1b7313c39e0a": "Programs Folder",
    "7c5a40ef-a0fb-4bfc-874a-c0f2e0b9fa8e": "Program Files",
    "7d1d3a04-debb-4115-95cf-2f29da2920da": "Searches",
    "7d49d726-3c21-4f05-99aa-fdc2c9474656": "Documents",
    "7e636bfe-dfa9-4d5e-b456-d7b39851d8a9": "Templates",
    "7fde1a1e-8b31-49a5-93b8-6be14cfa4943": "Generic Search Results",
    "80213e82-bcfd-4c4f-8817-bb27601267a9": "Compressed Folder (zip folder)",
    "8060b2e3-c9d7-4a5d-8c6b-ce8eba111328": "Proximity CPL",
    "80f3f1d5-feca-45f3-bc32-752c152e456e": "Tablet PC Settings",
    "82a5ea35-d9cd-47c5-9629-e15d2f714e6e": "CommonStartup",
    "82a74aeb-aeb4-465c-a014-d097ee346d63": "Control Panel",
    "82ba0782-5b7a-4569-b5d7-ec83085f08cc": "TopViews",
    "8343457c-8703-410f-ba8b-8b026e431743": "Feedback Tool",
    "859ead94-2e85-48ad-a71a-0969cb56a6cd": "Sample Videos",
    "85bbd920-42a0-1069-a2e4-08002b30309d": "Briefcase",
    "863aa9fd-42df-457b-8e4d-0de1b8015c60": "Remote Printers",
    "865e5e76-ad83-4dca-a109-50dc2113ce9a": "Programs Folder and Fast Items",
    "871c5380-42a0-1069-a2ea-08002b30309d": "Internet Explorer (Homepage)",
    "87630419-6216-4ff8-a1f0-143562d16d5c": "Mobile Broadband Profile Settings Editor",
    "877ca5ac-cb41-4842-9c69-9136e42d47e2": "File Backup Index",
    "87d66a43-7b11-4a28-9811-c86ee395acf7": "Indexing Options",
    "88c6c381-2e85-11d0-94de-444553540000": "ActiveX Cache Folder",
    "896664f7-12e1-490f-8782-c0835afd98fc": "Libraries delegate folder that appears in Users Files Folder",
    "8983036c-27c0-404b-8f08-102d10dcfd74": "SendTo",
    "89d83576-6bd1-4c86-9454-beb04e94c819": "MAPI Folder",
    "8ad10c31-2adb-4296-a8f7-e4701232c972": "Resources",
    "8e74d236-7f35-4720-b138-1fed0b85ea75": "OneDrive",
    "8e908fc9-becc-40f6-915b-f4ca0e70d03d": "Network and Sharing Center",
    "8fd8b88d-30e1-4f25-ac2b-553d3d65f0ea": "DXP",
    "905e63b6-c1bf-494e-b29c-65b732d3d21a": "Program Files",
    "9113a02d-00a3-46b9-bc5f-9c04daddd5d7": "Enhanced Storage Data Source",
    "9274bd8d-cfd1-41c3-b35e-b13f55a758f4": "Printer Shortcuts",
    "93412589-74d4-4e4e-ad0e-e0cb621440fd": "Font Settings",
    "9343812e-1c37-4a49-a12e-4b2d810d956b": "Search Home",
    "94d6ddcc-4a68-4175-a374-bd584a510b78": "Music",
    "96437431-5a90-4658-a77c-25478734f03e": "Server Manager",
    "96ae8d84-a250-4520-95a5-a47a7e3c548b": "Parental Controls",
    "978e0ed7-92d6-4cec-9b59-3135b9c49ccf": "Music library",
    "98d99750-0b8a-4c59-9151-589053683d73": "Windows Search Service Media Center Namespace Extension Handler",
    "98ec0e18-2098-4d44-8644-66979315a281": "Microsoft Office Outlook",
    "98f275b4-4fff-11e0-89e2-7b86dfd72085": "CLSID_StartMenuLauncherProviderFolder",
    "992cffa0-f557-101a-88ec-00dd010ccc48": "Network Connections (Network and Dial-up Connections)",
    "9a096bb5-9dc3-4d1c-8526-c3cbf991ea4e": "Internet Explorer RSS Feeds Folder",
    "9b74b6a3-0dfd-4f11-9e78-5f7800f2e772": "The user's username (%USERNAME%)",
    "9c60de1e-e5fc-40f4-a487-460851a8d915": "AutoPlay",
    "9c73f5e5-7ae7-4e32-a8e8-8d23b85255bf": "Sync Center Folder",
    "9db7a13c-f208-4981-8353-73cc61ae2783": "Previous Versions",
    "9e3995ab-1f9c-4f13-b827-48b24b6c7174": "User Pinned",
    "9e52ab10-f80d-49df-acb8-4330f5687855": "CDBurning",
    "9f433b7c-5f96-4ce1-ac28-aeaa1cc04d7c": "Security Center",
    "9fe63afd-59cf-4419-9775-abcc3849f861": "System Recovery (Recovery)",
    "a00ee528-ebd9-48b8-944a-8942113d46ac": "CLSID_StartMenuCommandingProviderFolder",
    "a0275511-0e86-4eca-97c2-ecd8f1221d08": "Infrared",
    "a0953c92-50dc-43bf-be83-3742fed03c9c": "Videos",
    "a302545d-deff-464b-abe8-61c8648d939b": "Libraries",
    "a304259d-52b8-4526-8b1a-a1d6cecc8243": "iSCSI Initiator",
    "a305ce99-f527-492b-8b1a-7e76fa98d6e4": "Installed Updates",
    "a3918781-e5f2-4890-b3d9-a7e54332328c": "Application Shortcuts",
    "a3c3d402-e56c-4033-95f7-4885e80b0111": "Previous Versions Results Delegate Folder",
    "a3dd4f92-658a-410f-84fd-6fbbbef2fffe": "Internet Options",
    "a4115719-d62e-491d-aa7c-e74b8be3b067": "Start Menu",
    "a5110426-177d-4e08-ab3f-785f10b4439c": "Sony Ericsson File Manager",
    "a520a1a4-1780-4ff6-bd18-167343c5af16": "AppDataLow",
    "a52bba46-e9e1-435f-b3d9-28daa648c0f6": "OneDrive",
    "a5a3563a-5755-4a6f-854e-afa3230b199f": "Library Folder",
    "a5e46e3a-8849-11d1-9d8c-00c04fc99d61": "Microsoft Browser Architecture",
    "a63293e8-664e-48db-a079-df759e0509f7": "Templates",
    "a6482830-08eb-41e2-84c1-73920c2badb9": "Removable Storage Devices",
    "a75d362e-50fc-4fb7-ac2c-a8beaa314493": "SidebarParts",
    "a77f5d77-2e2b-44c3-a6a2-aba601054a51": "Programs",
    "a8a91a66-3a7d-4424-8d24-04e180695c7a": "Device Center (Devices and Printers)",
    "a8cdff1c-4878-43be-b5fd-f8091c1c60d0": "Documents",
    "a990ae9f-a03b-4e80-94bc-9912d7504104": "Pictures",
    "aaa8d5a5-f1d6-4259-baa8-78e7ef60835e": "RoamedTileImages",
    "ab4f43ca-adcd-4384-b9af-3cecea7d6544": "Sitios Web",
    "ab5fb87b-7ce2-4f83-915d-550846c9537b": "Camera Roll",
    "ae50c081-ebd2-438a-8655-8a092e34987a": "Recent Items",
    "aee2420f-d50e-405c-8784-363c582bf45a": "Device Pairing Folder",
    "afdb1f70-2a4c-11d2-9039-00c04f8eeb3e": "Offline Files Folder",
    "b155bdf8-02f0-451e-9a26-ae317cfd7779": "Delegate folder that appears in Computer",
    "b250c668-f57d-4ee1-a63c-290ee7d1aa1f": "Sample Music",
    "b28aa736-876b-46da-b3a8-84c5e30ba492": "Web sites",
    "b2952b16-0e07-4e5a-b993-58c52cb94cae": "DB Folder",
    "b2c761c6-29bc-4f19-9251-e6195265baf1": "Color Management",
    "b3690e58-e961-423b-b687-386ebfd83239": "Pictures folder",
    "b4bfcc3a-db2c-424c-b029-7fe99a87c641": "Desktop",
    "b4fb3f98-c1ea-428d-a78a-d1f5659cba93": "Other Users Folder",
    "b5947d7f-b489-4fde-9e77-23780cc610d1": "Virtual Machines",
    "b689b0d0-76d3-4cbb-87f7-585d0e0ce070": "Games folder",
    "b6ebfb86-6907-413c-9af7-4fc2abf07cc5": "Public Pictures",
    "b7534046-3ecb-4c18-be4e-64cd4cb7d6ac": "Recycle Bin",
    "b7bede81-df94-4682-a7d8-57a52620b86f": "Screenshots",
    "b94237e7-57ac-4347-9151-b08c6c32d1f7": "CommonTemplates",
    "b97d20bb-f46a-4c97-ba10-5e3608430854": "Startup",
    "b98a2bea-7d42-4558-8bd1-832f41bac6fd": "Backup And Restore (Windows 7)",
    "bb06c0e4-d293-4f75-8a90-cb05b6477eee": "System",
    "bb64f8a7-bee7-4e1a-ab8d-7d8273f7fdb6": "Action Center Control Panel",
    "bc476f4c-d9d7-4100-8d4e-e043f6dec409": "Microsoft Browser Architecture",
    "bc48b32f-5910-47f5-8570-5074a8a5636a": "Sync Results Delegate Folder",
    "bcb5256f-79f6-4cee-b725-dc34e402fd46": "ImplicitAppShortcuts",
    "bcbd3057-ca5c-4622-b42d-bc56db0ae516": "Programs",
    "bd7a2e7b-21cb-41b2-a086-b309680c6b7e": "Client Side Cache Folder",
    "bd84b380-8ca2-1069-ab1d-08000948f534": "Microsoft Windows Font Folder",
    "bd85e001-112e-431e-983b-7b15ac09fff1": "RecordedTV",
    "bdbe736f-34f5-4829-abe8-b550e65146c4": "TopViews",
    "bdeadf00-c265-11d0-bced-00a0c90ab50f": "Web Folders",
    "be122a0e-4503-11da-8bde-f66bad1e3f3a": "Windows Anytime Upgrade",
    "bf782cc9-5a52-4a17-806c-2a894ffeeac5": "Language Settings",
    "bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968": "Links",
    "c0542a90-4bf0-11d1-83ee-00a0c90dc849": "NETWORK_SERVER",
    "c1bae2d0-10df-4334-bedd-7aa20b227a9d": "Common OEM Links",
    "c1f8339f-f312-4c97-b1c6-ecdf5910c5c0": "Pictures library",
    "c291a080-b400-4e34-ae3f-3d2b9637d56c": "UNCFATShellFolder Class",
    "c2b136e2-d50e-405c-8784-363c582bf43e": "Device Center Initialization",
    "c4900540-2379-4c75-844b-64e6faf8716b": "Sample Pictures",
    "c4aa340d-f20f-4863-afef-f87ef2e6ba25": "Public Desktop",
    "c4d98f09-6124-4fe0-9942-826416082da9": "Users libraries",
    "c555438b-3c23-4769-a71f-b6d3d9b6053a": "Display",
    "c57a6066-66a3-4d91-9eb9-41532179f0a5": "Application Suggested Locations",
    "c58c4893-3be0-4b45-abb5-a63e4b8c8651": "Troubleshooting",
    "c5abbf53-e17f-4121-8900-86626fc2c973": "Network Shortcuts",
    "c870044b-f49e-4126-a9c3-b52a1ff411e8": "Ringtones",
    "cac52c1a-b53d-4edc-92d7-6b2e8ac19434": "Games",
    "cb1b7f8c-c50a-4176-b604-9e24dee8d4d1": "Welcome Center (Getting Started)",
    "cce6191f-13b2-44fa-8d14-324728beef2c": "{Unknown CSIDL}",
    "d0384e7d-bac3-4797-8f14-cba229b392b5": "Administrative Tools",
    "d17d1d6d-cc3f-4815-8fe3-607e7d5d10b3": "Text to Speech",
    "d2035edf-75cb-4ef1-95a7-410d9ee17170": "DLNA Content Directory Data Source",
    "d20beec4-5ca8-4905-ae3b-bf251ea09b53": "Network",
    "d20ea4e1-3957-11d2-a40b-0c5020524152": "Fonts",
    "d20ea4e1-3957-11d2-a40b-0c5020524153": "Administrative Tools",
    "d24f75aa-4f2b-4d07-a3c4-469b3d9030c4": "Offline Files",
    "d34a6ca6-62c2-4c34-8a7c-14709c1ad938": "Common Places FS Folder",
    "d426cfd0-87fc-4906-98d9-a23f5d515d61": "Windows Search Service Outlook Express Protocol Handler",
    "d4480a50-ba28-11d1-8e75-00c04fa31a86": "Add Network Place",
    "d450a8a1-9568-45c7-9c0e-b4f9fb4537bd": "Installed Updates",
    "d555645e-d4f8-4c29-a827-d93c859c4f2a": "Ease of Access (Ease of Access Center)",
    "d5b1944e-db4e-482e-b3f1-db05827f0978": "Softex OmniPass Encrypted Folder",
    "d6277990-4c6a-11cf-8d87-00aa0060f5bf": "Scheduled Tasks",
    "d65231b0-b2f1-4857-a4ce-a8e7c6ea7d27": "System32",
    "d8559eb9-20c0-410e-beda-7ed416aecc2a": "Windows Defender",
    "d9dc8a3b-b784-432e-a781-5a1130a75963": "History",
    "d9ef8727-cac2-4e60-809e-86f80a666c91": "Secure Startup (BitLocker Drive Encryption)",
    "da3f6866-35fe-4229-821a-26553a67fc18": "General (Generic) library",
    "daf95313-e44d-46af-be1b-cbacea2c3065": "CLSID_StartMenuProviderFolder",
    "de2b70ec-9bf7-4a93-bd3d-243f7881d492": "Contacts",
    "de61d971-5ebc-4f02-a3a9-6c82895e5c04": "AddNewPrograms",
    "de92c1c7-837f-4f69-a3bb-86e631204a23": "Playlists",
    "de974d24-d9c6-4d3e-bf91-f4455120b917": "Common Files",
    "debf2536-e1a8-4c59-b6a2-414586476aea": "GameExplorer",
    "df7266ac-9274-4867-8d55-3bd661de872d": "Programs and Features",
    "dfdf76a2-c82a-4d63-906a-5644ac457385": "Public",
    "dffacdc5-679f-4156-8947-c5c76bc0b67f": "Delegate folder that appears in Users Files Folder",
    "e17d4fc0-5564-11d1-83f2-00a0c90dc849": "Search Results Folder",
    "e211b736-43fd-11d1-9efb-0000f8757fcd": "Scanners and Cameras",
    "e2e7934b-dce5-43c4-9576-7fe4f75e7480": "Date and Time",
    "e345f35f-9397-435c-8f95-4e922c26259e": "CLSID_StartMenuPathCompleteProviderFolder",
    "e413d040-6788-4c22-957e-175d1c513a34": "Sync Center Conflict Delegate Folder",
    "e555ab60-153b-4d17-9f04-a5fe99fc15ec": "Ringtones",
    "e773f1af-3a65-4866-857d-846fc9c4598a": "Shell Storage Folder Viewer",
    "e7de9b1a-7533-4556-9484-b26fb486475e": "Network Map",
    "e7e4bc40-e76a-11ce-a9bb-00aa004ae837": "Shell DocObject Viewer",
    "e88dcce0-b7b3-11d1-a9f0-00aa0060fa31": "Compressed Folder",
    "e95a4861-d57a-4be1-ad0f-35267e261739": "Windows SideShow",
    "e9950154-c418-419e-a90a-20c5287ae24b": "Sensors (Location and Other Sensors)",
    "ea25fbd7-3bf7-409e-b97f-3352240903f4": "Videos Search Results",
    "ecdb0924-4208-451e-8ee0-373c0956de16": "Work Folders",
    "ed228fdf-9ea8-4870-83b1-96b02cfe0d52": "My Games",
    "ed4824af-dce4-45a8-81e2-fc7965083634": "Public Documents",
    "ed50fc29-b964-48a9-afb3-15ebb9b97f36": "PrintHood delegate folder",
    "ed7ba470-8e54-465e-825c-99712043e01c": "All Tasks",
    "ed834ed6-4b5a-4bfe-8f11-a626dcb6a921": "Personalization Control Panel",
    "edc978d6-4d53-4b2f-a265-5805674be568": "Stream Backed Folder",
    "ee32e446-31ca-4aba-814f-a5ebd2fd6d5e": "Offline Files",
    "f02c1a0d-be21-4350-88b0-7367fc96ef3c": "Computers and Devices",
    "f0d63f85-37ec-4097-b30d-61b4a8917118": "Photo Stream",
    "f1390a9a-a3f4-4e5d-9c5f-98f3bd8d935c": "Sync Setup Delegate Folder",
    "f1b32785-6fba-4fcf-9d55-7b8e7f157091": "LocalAppData",
    "f2ddfc82-8f12-4cdd-b7dc-d4fe1425aa4d": "Sound",
    "f38bf404-1d43-42f2-9305-67de0b28fc23": "Windows",
    "f3ce0f7c-4901-4acc-8648-d5d44b04ef8f": "Users Files",
    "f3f5824c-ad58-4728-af59-a1ebe3392799": "Sticky Notes Namespace Extension for Windows Desktop Search",
    "f5175861-2688-11d0-9c5e-00aa00a45957": "Subscription Folder",
    "f6b6e965-e9b2-444b-9286-10c9152edbc5": "History Vault",
    "f7f1ed05-9f6d-47a2-aaae-29d317c6f066": "Common Files",
    "f82df8f7-8b9f-442e-a48c-818ea735ff9b": "Pen and Input Devices",
    "f8c2ab3b-17bc-41da-9758-339d7dbf2d88": "Previous Versions Results Folder",
    "f90c627b-7280-45db-bc26-cce7bdd620a4": "All Tasks",
    "f942c606-0914-47ab-be56-1321b8035096": "Storage Spaces",
    "fb0c9c8a-6c50-11d1-9f1d-0000f8757fcd": "Scanners & Cameras",
    "fbb3477e-c9e4-4b3b-a2ba-d3f5d3cd46f9": "Documents Library",
    "fc9fb64a-1eb2-4ccf-af5e-1a497a9b5c2d": "My sharing folders",
    "fcfeecae-ee1b-4849-ae50-685dcf7717ec": "Problem Reports and Solutions",
    "fd228cb7-ae11-4ae3-864c-16f3910ab8fe": "Fonts",
    "fdd39ad0-238f-46af-adb4-6c85480369c7": "Documents",
    "d3162b92-9365-467a-956b-92703aca08af": "Documents",
    "fe1290f0-cfbd-11cf-a330-00aa00c16e65": "Directory",
    "ff393560-c2a7-11cf-bff4-444553540000": "History",
    "0db7e03f-fc29-4dc6-9020-ff41b59e513a": "3D Objects",
    "24ad3ad4-a569-4530-98e1-ab02f9417aa8": "Pictures",
    "f86fa3ab-70d2-4fc7-9c99-fcbf05467f3a": "Videos",
    "3dfdf296-dbec-4fb4-81d1-6a3438bcf4de": "Music",
    "e31ea727-12ed-4702-820c-4b6445f28e1a": "Dropbox",
    "4a8fcd9f-623c-4283-96f0-10f41846a98a": "Box Sync",
    "fbf23b42-e3f0-101b-8488-00aa003e56f8": "Internet Explorer",
    "00020d75-0000-0000-c000-000000000046": "Inbox",
    "00020d76-0000-0000-c000-000000000046": "Inbox",
    "0": "All Control Panel Items",
    "1": "Appearance and Personalization",
    "2": "Hardware and Sound",
    "3": "Network and Internet",
    "4": "Sounds, Speech, and Audio Devices",
    "5": "System and Security",
    "6": "Clock, Language, and Region",
    "7": "Ease of Access",
    "8": "Programs",
    "9": "User Accounts",
    "10": "Security Center",
    "11": "Mobile PC",
    "0ddd015d-b06c-45d5-8c4c-f59713854639": "Local Pictures",
    "a0c69a99-21c8-4671-8703-7934162fcf1d": "Local Music",
    "7d83ee9b-2244-4e70-b1f5-5393042af1e4": "Local Downloads",
    "35286a68-3c57-41a1-bbb1-0eae73d76c95": "Local Videos",
    "f42ee2d3-909f-4907-8871-4c22fc0bf756": "Local Documents",
    "5ed4f38c-d3ff-4d61-b506-6820320aebfe": "All Settings",
    "1bef2128-2f96-4500-ba7c-098dc0049cb2": "CLSID_DBFolderBoth",
    "00021400-0000-0000-c000-000000000046": "Desktop",
    "3936e9e4-d92c-4eee-a85a-bc16d5ea0819": "Frequent folders",
    "45e8e0e8-7ae9-41ad-a9e8-594972716684": "Pictures",
    "f5fb2c77-0e2f-4a16-a381-3e560c68bc83": "Removable Drives",
    "0e5aae11-a475-4c5b-ab00-c66de400274e": "Shell File System Folder",
    "f3364ba0-65b9-11ce-a9ba-00aa004ae837": "Shell File System Folder",
    "5e5f29ce-e0a8-49d3-af32-7a7bdc173478": "This PC",
    "e44e5d18-0652-4508-a4e2-8a090067bcb0": "Default Programs",
    "e88865ea-0e1c-4e20-9aa6-edcd0212c87c": "Gallery",
    "b2b4a4d1-2754-4140-a2eb-9a76d9d7cdc6": "Linux",
    "2559a1f8-21d7-11d4-bdaf-00c04f60b9f0": "Windows Search",
    "e342f0fe-ff1c-4c41-be37-a0271fc90396": "Intel Rapid Storage Technology",
    "0bbca823-e77d-419e-9a44-5adec2c8eeb0": "NVIDIA Control Panel",
    "8e0c279d-0bd1-43c3-9ebd-31c3dc5b8a77": "Windows To Go",
    "00028b00-0000-0000-c000-000000000046": "Microsoft Network",
}


================================================
FILE: regipy/exceptions.py
================================================
class RegipyException(Exception):
    """
    This is the parent exception for all regipy exceptions
    """

    pass


class RegipyGeneralException(RegipyException):
    """
    General exception
    """

    pass


class RegistryValueNotFoundException(RegipyException):
    pass


class NoRegistrySubkeysException(RegipyException):
    pass


class NoRegistryValuesException(RegipyException):
    pass


class RegistryKeyNotFoundException(RegipyException):
    pass


class UnidentifiedHiveException(RegipyException):
    pass


class RegistryRecoveryException(RegipyException):
    pass


class RegistryParsingException(RegipyException):
    """
    Raised when there is a parsing error, most probably a corrupted hive
    """

    pass


================================================
FILE: regipy/hive_types.py
================================================
NTUSER_HIVE_TYPE = "ntuser"
SYSTEM_HIVE_TYPE = "system"
AMCACHE_HIVE_TYPE = "amcache"
SOFTWARE_HIVE_TYPE = "software"
SAM_HIVE_TYPE = "sam"
SECURITY_HIVE_TYPE = "security"
CLASSES_ROOT_HIVE_TYPE = "classes_root"
BCD_HIVE_TYPE = "bcd"
USRCLASS_HIVE_TYPE = "usrclass"


SUPPORTED_HIVE_TYPES = [
    NTUSER_HIVE_TYPE,
    SYSTEM_HIVE_TYPE,
    AMCACHE_HIVE_TYPE,
    SOFTWARE_HIVE_TYPE,
    SAM_HIVE_TYPE,
    SECURITY_HIVE_TYPE,
    BCD_HIVE_TYPE,
    USRCLASS_HIVE_TYPE,
]


HVLE_TRANSACTION_LOG_MAGIC = b"HvLE"
DIRT_TRANSACTION_LOG_MAGIC = b"DIRT"


================================================
FILE: regipy/plugins/__init__.py
================================================
# flake8: noqa
from .amcache.amcache import AmCachePlugin
from .bcd.boot_entry_list import BootEntryListPlugin
from .ntuser.installed_programs_ntuser import InstalledProgramsNTUserPlugin
from .ntuser.network_drives import NetworkDrivesPlugin
from .ntuser.persistence import NTUserPersistencePlugin
from .ntuser.shellbags_ntuser import ShellBagNtuserPlugin
from .ntuser.tsclient import TSClientPlugin
from .ntuser.typed_urls import TypedUrlsPlugin
from .ntuser.typed_paths import TypedPathsPlugin
from .ntuser.user_assist import UserAssistPlugin
from .ntuser.winrar import WinRARPlugin
from .ntuser.winscp_saved_sessions import WinSCPSavedSessionsPlugin
from .ntuser.word_wheel_query import WordWheelQueryPlugin
from .sam.local_sid import LocalSidPlugin
from .security.domain_sid import DomainSidPlugin
from .software.classes_installer import SoftwareClassesInstallerPlugin
from .software.image_file_execution_options import ImageFileExecutionOptions
from .software.installed_programs import InstalledProgramsSoftwarePlugin
from .software.last_logon import LastLogonPlugin
from .software.persistence import SoftwarePersistencePlugin
from .software.printdemon import PrintDemonPlugin
from .software.profilelist import ProfileListPlugin
from .software.tracing import RASTracingPlugin
from .software.uac import UACStatusPlugin
from .system.active_controlset import ActiveControlSetPlugin
from .system.bam import BAMPlugin
from .system.bootkey import BootKeyPlugin
from .system.computer_name import ComputerNamePlugin
from .system.host_domain_name import HostDomainNamePlugin
from .system.routes import RoutesPlugin
from .system.safeboot_configuration import SafeBootConfigurationPlugin
from .system.services import ServicesPlugin
from .system.shimcache import ShimCachePlugin
from .system.timezone_data import TimezoneDataPlugin
from .system.usbstor import USBSTORPlugin
from .system.wdigest import WDIGESTPlugin
from .usrclass.shellbags_usrclass import ShellBagUsrclassPlugin
from .ntuser.classes_installer import NtuserClassesInstallerPlugin
from .system.network_data import NetworkDataPlugin
from .software.winver import WinVersionPlugin
from .system.previous_winver import PreviousWinVersionPlugin
from .system.shutdown import ShutdownPlugin
from .system.processor_architecture import ProcessorArchitecturePlugin
from .system.crash_dump import CrashDumpPlugin
from .software.susclient import SusclientPlugin
from .system.disablelastaccess import DisableLastAccessPlugin
from .system.codepage import CodepagePlugin
from .software.disablesr import DisableSRPlugin
from .system.diag_sr import DiagSRPlugin
from .software.spp_clients import SppClientsPlugin
from .system.backuprestore import BackupRestorePlugin
from .system.timezone_data2 import TimezoneDataPlugin2
from .ntuser.wsl import WSLPlugin
from .ntuser.recentdocs import RecentDocsPlugin
from .ntuser.comdlg32 import ComDlg32Plugin
from .ntuser.runmru import RunMRUPlugin
from .ntuser.muicache import MUICachePlugin
from .ntuser.appkeys import AppKeysPlugin
from .ntuser.sysinternals import SysinternalsPlugin
from .ntuser.putty import PuTTYPlugin
from .software.appinitdlls import AppInitDLLsPlugin
from .system.appcertdlls import AppCertDLLsPlugin
from .software.appcompatflags import AppCompatFlagsPlugin
from .software.apppaths import AppPathsPlugin
from .software.defender import WindowsDefenderPlugin
from .software.pslogging import PowerShellLoggingPlugin
from .software.execpolicy import ExecutionPolicyPlugin
from .software.networklist import NetworkListPlugin
from .system.usb_devices import USBDevicesPlugin
from .system.mountdev import MountedDevicesPlugin
from .system.shares import SharesPlugin
from .system.pagefile import PagefilePlugin
from .system.lsa_packages import LSAPackagesPlugin
from .system.pending_file_rename import PendingFileRenamePlugin
from .sam.samparse import SAMParsePlugin


================================================
FILE: regipy/plugins/amcache/__init__.py
================================================


================================================
FILE: regipy/plugins/amcache/amcache.py
================================================
import logging

from inflection import underscore

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import AMCACHE_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

AMCACHE_FIELD_NUMERIC_MAPPINGS = {
    "0": "product_name",
    "1": "company_name",
    "2": "file_version_number",
    "3": "language_code",
    "4": "switchback_context",
    "5": "file_version",
    "6": "file_size",
    "7": "pe_header_hash",
    "8": "unknown1",
    "9": "pe_header_checksum",
    "a": "unknown2",
    "b": "unknown3",
    "c": "file_description",
    "d": "unknown4",
    "f": "linker_compile_time",
    "10": "unknown5",
    "11": "last_modified_timestamp",
    "12": "created_timestamp",
    "15": "full_path",
    "16": "unknown6",
    "17": "last_modified_timestamp_2",
    "100": "program_id",
    "101": "sha1",
}

WIN8_TS_FIELDS = [
    "last_modified_timestamp",
    "created_timestamp",
    "last_modified_timestamp_2",
]


class AmCachePlugin(Plugin):
    NAME = "amcache"
    DESCRIPTION = "Parse Amcache"
    COMPATIBLE_HIVE = AMCACHE_HIVE_TYPE

    def parse_amcache_file_entry(self, subkey):
        entry = {underscore(x.name): x.value for x in subkey.iter_values(as_json=self.as_json)}

        # Sometimes the value names might be numeric instead. Translate them:
        for k, v in AMCACHE_FIELD_NUMERIC_MAPPINGS.items():
            content = entry.pop(k, None)
            if content:
                entry[v] = content

        if "sha1" in entry:
            entry["sha1"] = entry["sha1"][4:]

        if "file_id" in entry and entry["file_id"] != 0:
            entry["file_id"] = entry["file_id"][4:]
            if "sha1" not in entry:
                entry["sha1"] = entry["file_id"]

        if "program_id" in entry:
            entry["program_id"] = entry["program_id"][4:]

        entry["timestamp"] = convert_wintime(subkey.header.last_modified, as_json=self.as_json)

        if "size" in entry:
            entry["size"] = int(entry["size"], 16) if isinstance(entry["size"], str) else entry["size"]

        is_pefile = entry.get("is_pe_file")
        if is_pefile is not None:
            entry["is_pe_file"] = bool(is_pefile)

        is_os_component = entry.get("is_os_component")
        if is_os_component is not None:
            entry["is_os_component"] = bool(is_os_component)

        if entry.get("link_date") == 0:
            entry.pop("link_date")

        for ts_field_name in WIN8_TS_FIELDS:
            ts = entry.pop(ts_field_name, None)
            if ts:
                entry[ts_field_name] = convert_wintime(ts, as_json=self.as_json)

        self.entries.append(entry)

    def run(self):
        logger.debug("Started AmCache Plugin...")

        try:
            amcache_file_subkey = self.registry_hive.get_key(r"\Root\File")
        except RegistryKeyNotFoundException:
            logger.error(r"Could not find \Root\File subkey")
            amcache_file_subkey = None

        try:
            amcache_inventory_file_subkey = self.registry_hive.get_key(r"\Root\InventoryApplicationFile")
        except RegistryKeyNotFoundException:
            logger.info(r"Could not find \Root\InventoryApplicationFile subkey")
            amcache_inventory_file_subkey = None

        if amcache_file_subkey:
            for subkey in amcache_file_subkey.iter_subkeys():
                if subkey.header.subkey_count > 0:
                    for file_subkey in subkey.iter_subkeys():
                        self.parse_amcache_file_entry(file_subkey)
                if subkey.header.values_count > 0:
                    self.entries.append(subkey)

        if amcache_inventory_file_subkey:
            for file_subkey in amcache_inventory_file_subkey.iter_subkeys():
                self.parse_amcache_file_entry(file_subkey)


================================================
FILE: regipy/plugins/bcd/__init__.py
================================================


================================================
FILE: regipy/plugins/bcd/boot_entry_list.py
================================================
"""
Windows Boot Configuration Data (BCD) boot entry list plugin
"""

import logging
import uuid
from typing import Union

from regipy.hive_types import BCD_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.registry import NKRecord
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

# BCD object store key path
# See https://www.geoffchappell.com/notes/windows/boot/bcd/objects.htm
BCD_OBJECTS_PATH = r"\Objects"

# Relevant BCD object element types:

# BcdLibraryDevice_ApplicationDevice
ELEM_TYPE_APPLICATION_DEVICE = 0x11000001
# BcdLibraryString_ApplicationPath
ELEM_TYPE_APPLICATION_PATH = 0x12000002
# BcdLibraryString_Description
ELEM_TYPE_DESCRIPTION = 0x12000004


def _get_element_by_type(obj_key: NKRecord, datatype: int) -> Union[str, bytes, None]:
    """
    Retrieves stored BCD object elements by their datatype.

    See https://www.geoffchappell.com/notes/windows/boot/bcd/elements.htm
    """

    # The BCD object attributes are stored as "elements" instead of normal values
    elements_key = obj_key.get_subkey("Elements", raise_on_missing=False)
    if elements_key.subkey_count == 0:
        return None

    elem_key = elements_key.get_subkey(f"{datatype:08X}", raise_on_missing=False)
    if elem_key is None:
        return None

    return elem_key.get_value("Element")


class BootEntryListPlugin(Plugin):
    """
    Windows Boot Configuration Data (BCD) boot entry list extractor
    """

    NAME = "boot_entry_list"
    DESCRIPTION = "List the Windows BCD boot entries"
    COMPATIBLE_HIVE = BCD_HIVE_TYPE

    def run(self) -> None:
        logger.debug("Started Boot Entry List Plugin...")

        objects_key = self.registry_hive.get_key(BCD_OBJECTS_PATH)

        for obj_key in objects_key.iter_subkeys():
            desc_key = obj_key.get_subkey("Description")
            # Object type defines the boot entry features
            desc_type = desc_key.get_value("Type")

            # The remaining boot entry attributes are stored as object elements
            desc_name = _get_element_by_type(obj_key, ELEM_TYPE_DESCRIPTION)
            path_name = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_PATH)
            device_data = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_DEVICE)

            # Filter out objects that do not look like boot entries
            if desc_name is None or path_name is None or device_data is None:
                continue

            # TODO: Figure out the device data blob format
            if not isinstance(device_data, bytes) or len(device_data) < 72:
                continue

            # TODO: Figure out how non-GPT partitions are encoded
            gpt_part_guid = str(uuid.UUID(bytes_le=device_data[32:48]))
            gpt_disk_guid = str(uuid.UUID(bytes_le=device_data[56:72]))

            entry_type = f"0x{desc_type:08X}" if self.as_json else desc_type

            self.entries.append(
                {
                    "guid": obj_key.name,
                    "type": entry_type,
                    "name": desc_name,
                    "gpt_disk": gpt_disk_guid,
                    "gpt_partition": gpt_part_guid,
                    "image_path": path_name,
                    "timestamp": convert_wintime(obj_key.header.last_modified, as_json=self.as_json),
                }
            )


================================================
FILE: regipy/plugins/ntuser/__init__.py
================================================


================================================
FILE: regipy/plugins/ntuser/appkeys.py
================================================
"""
AppKeys plugin - Parses application-specific keyboard shortcuts
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

APPKEYS_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\AppKey"


class AppKeysPlugin(Plugin):
    """
    Parses Application Keys from NTUSER.DAT
    Registry Key: Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\AppKey
    These are keyboard shortcuts that launch specific applications.
    """

    NAME = "appkeys"
    DESCRIPTION = "Parses application keyboard shortcuts"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started AppKeys Plugin...")

        try:
            appkeys_key = self.registry_hive.get_key(APPKEYS_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.debug(f"Could not find {self.NAME} plugin data at: {APPKEYS_PATH}: {ex}")
            return

        for subkey in appkeys_key.iter_subkeys():
            key_id = subkey.name
            subkey_path = f"{APPKEYS_PATH}\\{key_id}"

            entry = {
                "key_path": subkey_path,
                "key_id": key_id,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
            }

            for value in subkey.iter_values():
                if value.name == "ShellExecute":
                    entry["shell_execute"] = value.value
                elif value.name == "Association":
                    entry["association"] = value.value
                elif value.name == "RegisteredApp":
                    entry["registered_app"] = value.value

            # [comment] why filter?
            if "shell_execute" in entry or "association" in entry or "registered_app" in entry:
                self.entries.append(entry)


================================================
FILE: regipy/plugins/ntuser/classes_installer.py
================================================
import logging

from regipy import RegistryKeyNotFoundException, convert_wintime
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

logger = logging.getLogger(__name__)

CLASSES_INSTALLER_PATH = r"\Software\Microsoft\Installer\Products"


class NtuserClassesInstallerPlugin(Plugin):
    NAME = "ntuser_classes_installer"
    DESCRIPTION = "List of installed software from NTUSER hive"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            classes_installer_subkey = self.registry_hive.get_key(CLASSES_INSTALLER_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(ex)
            return

        for entry in classes_installer_subkey.iter_subkeys():
            identifier = entry.name
            timestamp = convert_wintime(entry.header.last_modified, as_json=self.as_json)
            product_name = entry.get_value("ProductName")
            self.entries.append(
                {
                    "identifier": identifier,
                    "timestamp": timestamp,
                    "product_name": product_name,
                    "is_hidden": product_name is None,
                }
            )


================================================
FILE: regipy/plugins/ntuser/comdlg32.py
================================================
"""
ComDlg32 plugin - Parses Open/Save dialog history (OpenSavePidlMRU, OpenSaveMRU)
"""

import logging
from typing import Optional

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

# Windows Vista+ path
OPEN_SAVE_PIDL_MRU_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU"
# Windows XP path
OPEN_SAVE_MRU_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU"
# Last visited path
LAST_VISITED_PIDL_MRU_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU"


def parse_pidl_mru_value(data: bytes) -> Optional[str]:
    """
    Parse the PIDL MRU value to extract path/filename.
    """
    if not data or len(data) < 4:
        return None

    try:
        # Try to find readable unicode strings
        result_parts = []
        i = 0
        while i < len(data) - 1:
            # Look for printable unicode sequences
            start = i
            while i < len(data) - 1:
                char_code = int.from_bytes(data[i : i + 2], "little")
                # Check if it's a printable character or common path character
                if 0x20 <= char_code <= 0x7E or char_code in [0x5C, 0x2F, 0x3A]:  # \, /, :
                    i += 2
                else:
                    break

            if i - start >= 4:  # At least 2 unicode characters
                try:
                    part = data[start:i].decode("utf-16-le", errors="ignore").strip("\x00")
                    if part and len(part) >= 2:
                        result_parts.append(part)
                except Exception:
                    pass
            i += 2

        # Return the longest meaningful path-like string
        for part in sorted(result_parts, key=len, reverse=True):
            if "\\" in part or "/" in part or "." in part:
                return part
        if result_parts:
            return result_parts[0]
    except Exception:
        pass

    return None


class ComDlg32Plugin(Plugin):
    """
    Parses Open/Save dialog history from NTUSER.DAT
    Provides information about files opened or saved through common dialogs.
    """

    NAME = "comdlg32"
    DESCRIPTION = "Parses Open/Save dialog MRU lists"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started ComDlg32 Plugin...")

        # Try OpenSavePidlMRU (Vista+)
        self._parse_open_save_mru(OPEN_SAVE_PIDL_MRU_PATH, "OpenSavePidlMRU")

        # Try OpenSaveMRU (XP)
        self._parse_open_save_mru(OPEN_SAVE_MRU_PATH, "OpenSaveMRU")

        # Try LastVisitedPidlMRU
        self._parse_last_visited_mru(LAST_VISITED_PIDL_MRU_PATH)

    def _parse_open_save_mru(self, base_path: str, mru_type: str):
        """Parse OpenSaveMRU or OpenSavePidlMRU entries"""
        try:
            base_key = self.registry_hive.get_key(base_path)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find {mru_type} at: {base_path}")
            return

        # Process the main key and all extension subkeys
        for subkey in base_key.iter_subkeys():
            extension = subkey.name
            subkey_path = f"{base_path}\\{extension}"

            entry = {
                "key_path": subkey_path,
                "mru_type": mru_type,
                "extension": extension,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
                "items": [],
            }

            mru_list = None
            mru_values = {}

            for value in subkey.iter_values():
                if value.name == "MRUListEx":
                    mru_list = value.value
                elif value.name.isdigit():
                    parsed = parse_pidl_mru_value(value.value)
                    if parsed:
                        mru_values[int(value.name)] = parsed

            # Build ordered list
            if mru_list and mru_values:
                for i in range(0, len(mru_list) - 4, 4):
                    index = int.from_bytes(mru_list[i : i + 4], "little", signed=True)
                    if index == -1:
                        break
                    if index in mru_values:
                        entry["items"].append({"index": index, "path": mru_values[index]})

            if entry["items"]:
                self.entries.append(entry)

    def _parse_last_visited_mru(self, path: str):
        """Parse LastVisitedPidlMRU entries"""
        try:
            key = self.registry_hive.get_key(path)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find LastVisitedPidlMRU at: {path}")
            return

        entry = {
            "key_path": path,
            "mru_type": "LastVisitedPidlMRU",
            "last_write": convert_wintime(key.header.last_modified, as_json=self.as_json),
            "items": [],
        }

        mru_list = None
        mru_values = {}

        for value in key.iter_values():
            if value.name == "MRUListEx":
                mru_list = value.value
            elif value.name.isdigit():
                parsed = parse_pidl_mru_value(value.value)
                if parsed:
                    mru_values[int(value.name)] = parsed

        if mru_list and mru_values:
            for i in range(0, len(mru_list) - 4, 4):
                index = int.from_bytes(mru_list[i : i + 4], "little", signed=True)
                if index == -1:
                    break
                if index in mru_values:
                    entry["items"].append({"index": index, "path": mru_values[index]})

        if entry["items"]:
            self.entries.append(entry)


================================================
FILE: regipy/plugins/ntuser/installed_programs_ntuser.py
================================================
import logging

from regipy import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

INSTALLED_SOFTWARE_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Uninstall"


class InstalledProgramsNTUserPlugin(Plugin):
    NAME = "installed_programs_ntuser"
    DESCRIPTION = "Retrieve list of installed programs and their install date from the NTUSER Hive"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def _get_installed_software(self, subkey_path):
        try:
            uninstall_sk = self.registry_hive.get_key(subkey_path)
        except RegistryKeyNotFoundException as ex:
            logger.error(ex)
            return

        for installed_program in uninstall_sk.iter_subkeys():
            values = (
                {x.name: x.value for x in installed_program.iter_values(as_json=self.as_json)}
                if installed_program.values_count
                else {}
            )
            self.entries.append(
                {
                    "service_name": installed_program.name,
                    "timestamp": convert_wintime(installed_program.header.last_modified, as_json=self.as_json),
                    "registry_path": subkey_path,
                    **values,
                }
            )

    def run(self):
        self._get_installed_software(INSTALLED_SOFTWARE_PATH)


================================================
FILE: regipy/plugins/ntuser/muicache.py
================================================
"""
MUICache plugin - Parses MUI Cache entries (application display names)
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

# Windows Vista+ path
MUICACHE_PATH_VISTA = r"\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache"
# Windows XP path
MUICACHE_PATH_XP = r"\Software\Microsoft\Windows\ShellNoRoam\MUICache"


class MUICachePlugin(Plugin):
    """
    Parses MUICache entries from NTUSER.DAT or UsrClass.dat
    MUICache stores the display names of applications that have been run.
    """

    NAME = "muicache"
    DESCRIPTION = "Parses MUI Cache (application display names)"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started MUICache Plugin...")

        # Try Vista+ path first
        muicache_found = self._parse_muicache(MUICACHE_PATH_VISTA)

        # Try XP path if Vista+ not found
        if not muicache_found:
            self._parse_muicache(MUICACHE_PATH_XP)

    def _parse_muicache(self, path: str) -> bool:
        """Parse MUICache at the given path"""
        try:
            muicache_key = self.registry_hive.get_key(path)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find MUICache at: {path}")
            return False

        entry = {
            "key_path": path,
            "last_write": convert_wintime(muicache_key.header.last_modified, as_json=self.as_json),
            "applications": [],
        }

        for value in muicache_key.iter_values():
            # Skip system values
            if value.name.startswith("@") or value.name == "LangID":
                continue

            app_entry = {
                "path": value.name,
                "display_name": value.value if isinstance(value.value, str) else None,
            }

            # Extract just the filename from the path for easier reading
            if "\\" in value.name:
                app_entry["filename"] = value.name.split("\\")[-1]
            else:
                app_entry["filename"] = value.name

            entry["applications"].append(app_entry)

        if entry["applications"]:
            self.entries.append(entry)
            return True

        return False


================================================
FILE: regipy/plugins/ntuser/network_drives.py
================================================
import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

NETWORK_DRIVES = r"\Network"


class NetworkDrivesPlugin(Plugin):
    NAME = "network_drives_plugin"
    DESCRIPTION = "Parse the user's mapped network drives"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            network_drives = self.registry_hive.get_key(NETWORK_DRIVES)
            for mapped_drive in network_drives.iter_subkeys():
                timestamp = convert_wintime(mapped_drive.header.last_modified, as_json=self.as_json)
                self.entries.append(
                    {
                        "last_write": timestamp,
                        "drive_letter": mapped_drive.name,
                        "network_path": mapped_drive.get_value("RemotePath"),
                    }
                )

        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {NETWORK_DRIVES}: {ex}")


================================================
FILE: regipy/plugins/ntuser/persistence.py
================================================
import logging

from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import get_subkey_values_from_list

logger = logging.getLogger(__name__)

PERSISTENCE_ENTRIES = [
    r"\Software\Microsoft\Windows NT\CurrentVersion\Run",
    r"\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\Run",
    r"\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunOnce",
    r"\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run",
    r"\Software\Microsoft\Windows\CurrentVersion\Run",
    r"\Software\Microsoft\Windows\CurrentVersion\RunOnce",
    r"\Software\Microsoft\Windows\CurrentVersion\RunOnceEx",
    r"\Software\Microsoft\Windows\CurrentVersion\RunOnce\Setup",
    r"\Software\Microsoft\Windows\CurrentVersion\RunServices",
    r"\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce",
    r"\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run",
    r"\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run",
    r"\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce",
    r"\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnceEx",
    r"\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce\Setup",
    r"\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify",
]


class NTUserPersistencePlugin(Plugin):
    NAME = "ntuser_persistence"
    DESCRIPTION = "Retrieve values from known persistence subkeys in NTUSER hive"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        self.entries = get_subkey_values_from_list(
            self.registry_hive,
            PERSISTENCE_ENTRIES,
            as_json=self.as_json,
            trim_values=False,
        )


================================================
FILE: regipy/plugins/ntuser/putty.py
================================================
"""
PuTTY plugin - Parses PuTTY SSH client configuration and session history
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.plugins.utils import extract_values
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

PUTTY_SESSIONS_PATH = r"\Software\SimonTatham\PuTTY\Sessions"
PUTTY_SSH_HOST_KEYS_PATH = r"\Software\SimonTatham\PuTTY\SshHostKeys"
PUTTY_JUMPLIST_PATH = r"\Software\SimonTatham\PuTTY\Jumplist"


class PuTTYPlugin(Plugin):
    """
    Parses PuTTY configuration and session history from NTUSER.DAT

    Extracts:
    - Saved sessions with connection details
    - SSH host keys (evidence of connections)
    - Jump list entries
    """

    NAME = "putty"
    DESCRIPTION = "Parses PuTTY sessions and SSH host keys"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started PuTTY Plugin...")

        self._parse_sessions()
        self._parse_ssh_host_keys()
        self._parse_jumplist()

    def _parse_sessions(self):
        """Parse saved PuTTY sessions"""
        try:
            sessions_key = self.registry_hive.get_key(PUTTY_SESSIONS_PATH)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find PuTTY sessions at: {PUTTY_SESSIONS_PATH}")
            return

        for subkey in sessions_key.iter_subkeys():
            session_name = subkey.name
            # Session names are URL-encoded
            try:
                from urllib.parse import unquote

                decoded_name = unquote(session_name)
            except Exception:
                decoded_name = session_name

            entry = {
                "type": "session",
                "key_path": f"{PUTTY_SESSIONS_PATH}\\{session_name}",
                "session_name": decoded_name,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
            }

            # Extract required fields
            extract_values(
                subkey,
                {
                    "HostName": "hostname",
                    "PortNumber": "port",
                    "UserName": "username",
                    "Protocol": ("protocol", self._get_protocol_name),
                },
                entry,
            )

            # Extract optional fields (only if non-empty)
            for value in subkey.iter_values():
                name = value.name
                val = value.value

                if name == "ProxyHost" and val:
                    entry["proxy_host"] = val
                elif name == "ProxyPort" and val and val != 0:
                    entry["proxy_port"] = val
                elif name == "ProxyUsername" and val:
                    entry["proxy_username"] = val
                elif name == "PublicKeyFile" and val:
                    entry["public_key_file"] = val
                elif name == "RemoteCommand" and val:
                    entry["remote_command"] = val
                elif name == "PortForwardings" and val:
                    entry["port_forwardings"] = val
                elif name == "LogFileName" and val:
                    entry["log_filename"] = val
                elif name == "WinTitle" and val:
                    entry["window_title"] = val

            self.entries.append(entry)

    def _parse_ssh_host_keys(self):
        """Parse SSH host keys - evidence of connections made"""
        try:
            hostkeys_key = self.registry_hive.get_key(PUTTY_SSH_HOST_KEYS_PATH)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find PuTTY SSH host keys at: {PUTTY_SSH_HOST_KEYS_PATH}")
            return

        entry = {
            "type": "ssh_host_keys",
            "key_path": PUTTY_SSH_HOST_KEYS_PATH,
            "last_write": convert_wintime(hostkeys_key.header.last_modified, as_json=self.as_json),
            "hosts": [],
        }

        for value in hostkeys_key.iter_values():
            # Format: algorithm@port:hostname
            # e.g., rsa2@22:192.168.1.1
            host_entry = {"raw_key": value.name}

            if "@" in value.name and ":" in value.name:
                try:
                    algo_port, hostname = value.name.rsplit(":", 1)
                    algo, port = algo_port.split("@", 1)
                    host_entry["algorithm"] = algo
                    host_entry["port"] = int(port)
                    host_entry["hostname"] = hostname
                except Exception:
                    pass

            entry["hosts"].append(host_entry)

        if entry["hosts"]:
            self.entries.append(entry)

    def _parse_jumplist(self):
        """Parse PuTTY jump list entries"""
        try:
            jumplist_key = self.registry_hive.get_key(PUTTY_JUMPLIST_PATH)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find PuTTY jumplist at: {PUTTY_JUMPLIST_PATH}")
            return

        entry = {
            "type": "jumplist",
            "key_path": PUTTY_JUMPLIST_PATH,
            "last_write": convert_wintime(jumplist_key.header.last_modified, as_json=self.as_json),
            "recent_sessions": [],
        }

        for value in jumplist_key.iter_values():
            if value.name == "Recent sessions" and value.value:
                # Value is a comma-separated list of session names
                sessions = [s.strip() for s in value.value.split(",") if s.strip()]
                entry["recent_sessions"] = sessions

        if entry["recent_sessions"]:
            self.entries.append(entry)

    @staticmethod
    def _get_protocol_name(protocol_id):
        """Convert protocol ID to name"""
        protocols = {
            0: "Raw",
            1: "Telnet",
            2: "Rlogin",
            3: "SSH",
            4: "Serial",
        }
        return protocols.get(protocol_id, f"Unknown ({protocol_id})")


================================================
FILE: regipy/plugins/ntuser/recentdocs.py
================================================
"""
RecentDocs plugin - Parses recently opened documents from the registry
"""

import logging
from typing import Optional

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

RECENT_DOCS_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs"


def parse_mru_value(data: bytes) -> Optional[str]:
    """
    Parse the binary MRU value to extract the filename.
    The format is: filename (null-terminated unicode) + shell item data
    """
    if not data:
        return None

    try:
        # Find the null terminator for the unicode string
        null_pos = 0
        for i in range(0, len(data) - 1, 2):
            if data[i] == 0 and data[i + 1] == 0:
                null_pos = i
                break

        if null_pos > 0:
            return data[:null_pos].decode("utf-16-le", errors="replace")
    except Exception:
        pass

    return None


class RecentDocsPlugin(Plugin):
    """
    Parses Recently opened documents from NTUSER.DAT
    Registry Key: Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs
    """

    NAME = "recentdocs"
    DESCRIPTION = "Parses recently opened documents"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started RecentDocs Plugin...")

        try:
            recent_docs_key = self.registry_hive.get_key(RECENT_DOCS_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.debug(f"Could not find {self.NAME} plugin data at: {RECENT_DOCS_PATH}: {ex}")
            return

        # Process the main RecentDocs key
        self._process_recent_docs_key(recent_docs_key, RECENT_DOCS_PATH)

        # Process extension subkeys (e.g., .txt, .docx, .pdf, etc.)
        for subkey in recent_docs_key.iter_subkeys():
            subkey_path = f"{RECENT_DOCS_PATH}\\{subkey.name}"
            self._process_recent_docs_key(subkey, subkey_path, extension=subkey.name)

    def _process_recent_docs_key(self, key, key_path: str, extension: str = None):
        """Process a RecentDocs key and extract document entries"""
        entry = {
            "key_path": key_path,
            "last_write": convert_wintime(key.header.last_modified, as_json=self.as_json),
            "extension": extension,
            "documents": [],
        }

        mru_list = None
        mru_values = {}

        for value in key.iter_values():
            if value.name == "MRUListEx":
                # MRUListEx contains the order of recently accessed items
                # It's an array of DWORDs representing indices
                mru_list = value.value
            elif value.name.isdigit():
                # Numeric values contain the actual document data
                parsed_name = parse_mru_value(value.value)
                if parsed_name:
                    mru_values[int(value.name)] = parsed_name

        # Build ordered list of documents based on MRUListEx
        if mru_list and mru_values:
            # MRUListEx is a binary blob of 4-byte integers
            for i in range(0, len(mru_list) - 4, 4):
                index = int.from_bytes(mru_list[i : i + 4], "little", signed=True)
                if index == -1:  # End marker
                    break
                if index in mru_values:
                    entry["documents"].append({"index": index, "name": mru_values[index]})

        if entry["documents"]:
            self.entries.append(entry)


================================================
FILE: regipy/plugins/ntuser/runmru.py
================================================
"""
RunMRU plugin - Parses Run dialog history
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

RUN_MRU_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"


class RunMRUPlugin(Plugin):
    """
    Parses Run dialog MRU (Most Recently Used) list from NTUSER.DAT
    Registry Key: Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU
    """

    NAME = "runmru"
    DESCRIPTION = "Parses Run dialog MRU list"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started RunMRU Plugin...")

        try:
            runmru_key = self.registry_hive.get_key(RUN_MRU_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.debug(f"Could not find {self.NAME} plugin data at: {RUN_MRU_PATH}: {ex}")
            return

        entry = {
            "key_path": RUN_MRU_PATH,
            "last_write": convert_wintime(runmru_key.header.last_modified, as_json=self.as_json),
            "mru_order": None,
            "commands": [],
        }

        mru_list = None
        mru_values = {}

        for value in runmru_key.iter_values():
            if value.name == "MRUList":
                # MRUList contains the order as a string of letters (e.g., "dcba")
                mru_list = value.value
            elif len(value.name) == 1 and value.name.isalpha():
                # Single letter values (a, b, c, etc.) contain the commands
                # Commands end with \1 which indicates the command was typed
                command = value.value
                if command and isinstance(command, str):
                    # Remove the trailing \1 marker if present
                    command = command.rstrip("\x01")
                    mru_values[value.name] = command

        if mru_list:
            entry["mru_order"] = mru_list

        # Build ordered list based on MRUList
        if mru_list and mru_values:
            for letter in mru_list:
                if letter in mru_values:
                    entry["commands"].append({"letter": letter, "command": mru_values[letter]})

        if entry["commands"] or entry["mru_order"]:
            self.entries.append(entry)


================================================
FILE: regipy/plugins/ntuser/shellbags_ntuser.py
================================================
import logging
from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime
from regipy.constants import KNOWN_GUIDS

logger = logging.getLogger(__name__)

NTUSER_SHELLBAG = "\\Software\\Microsoft\\Windows\\Shell\\BagMRU"
DEFAULT_CODEPAGE = "cp1252"


class ShellBagNtuserPlugin(Plugin):
    NAME = "ntuser_shellbag_plugin"
    DESCRIPTION = "Parse NTUSER Shellbag items"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    @staticmethod
    def _parse_mru(mru_val):
        mru_order_string = ""
        if isinstance(mru_val, bytes):
            mru_val = mru_val[:-4]
            for i in range(0, len(mru_val), 4):
                current_val = int.from_bytes(mru_val[i : i + 4], byteorder="little")
                mru_order_string += f"{current_val}-"

            return mru_order_string[:-1]
        else:
            return mru_order_string

    @staticmethod
    def _get_shell_item_type(shell_item):
        try:
            import pyfwsi
        except ModuleNotFoundError as ex:
            logger.exception(
                "Plugin `shellbag_plugin` has missing modules, install regipy using"
                " `pip install regipy[full]` in order to install plugin dependencies. "
                "This might take some time... "
            )
            raise ex

        if isinstance(shell_item, pyfwsi.volume):
            item_type = "Volume"

        elif isinstance(shell_item, pyfwsi.file_entry):
            item_type = "Directory"

        elif isinstance(shell_item, pyfwsi.network_location):
            item_type = "Network Location"

        elif isinstance(shell_item, pyfwsi.root_folder):
            item_type = "Root Folder"

        elif isinstance(shell_item, pyfwsi.control_panel_category):
            item_type = "Control Panel Category"

        elif isinstance(shell_item, pyfwsi.control_panel_item):
            item_type = "Control Panel Item"

        elif isinstance(shell_item, pyfwsi.users_property_view):
            item_type = "Users Property View"

        else:
            item_type = "unknown"

        return item_type

    @staticmethod
    def _check_known_guids(guid):
        if guid in KNOWN_GUIDS:
            path_segment = KNOWN_GUIDS[guid]
        else:
            path_segment = "{{{0:s}}}".format(guid)
        return path_segment

    @staticmethod
    def _get_entry_string(fwps_record):
        if fwps_record.entry_name:
            entry_string = fwps_record.entry_name
        else:
            entry_string = f"{fwps_record.entry_type:d}"
        return entry_string

    @staticmethod
    def _create_entry(
        value,
        slot,
        reg_path,
        value_name,
        node_slot,
        shell_type,
        path,
        full_path=None,
        location_description=None,
        creation_time=None,
        access_time=None,
        modification_time=None,
        last_write=None,
        mru_order=None,
        mru_order_location=None,
        first_interacted=None,
    ):
        return {
            "value": value,
            "slot": slot,
            "reg_path": reg_path,
            "value_name": value_name,
            "node_slot": node_slot,
            "shell_type": shell_type,
            "path": path,
            "full path": full_path,
            "location description": location_description,
            "creation_time": creation_time,
            "access_time": access_time,
            "modification_time": modification_time,
            "last_write": last_write,
            "mru_order": mru_order,
            "mru_order_location": mru_order_location,
            "first_interacted": first_interacted,
        }

    @staticmethod
    def _parse_shell_item_path_segment(self, shell_item):
        """Parses a shell item path segment.
        Args:
          shell_item (pyfwsi.item): shell item.
        Returns:
          str: shell item path segment.
        """

        try:
            import pyfwsi
            import pyfwps
        except ModuleNotFoundError as ex:
            logger.exception(
                f"Plugin `shellbag_plugin` has missing modules, install regipy using"
                f" `pip install regipy[full]` in order to install plugin dependencies. "
                f"This might take some time... "
            )
            raise ex

        path_segment = None
        full_path = None
        location_description = None

        if isinstance(shell_item, pyfwsi.volume):
            if shell_item.name:
                path_segment = shell_item.name
            elif shell_item.identifier:
                path_segment = self._check_known_guids(shell_item.identifier)

        elif isinstance(shell_item, pyfwsi.file_entry):
            long_name = ""
            for extension_block in shell_item.extension_blocks:
                if isinstance(extension_block, pyfwsi.file_entry_extension):
                    long_name = extension_block.long_name

            if long_name:
                path_segment = long_name
            elif shell_item.name:
                path_segment = shell_item.name

        elif isinstance(shell_item, pyfwsi.network_location):
            if shell_item.location:
                path_segment = shell_item.location
            if shell_item.description:
                location_description = shell_item.description
                if shell_item.comments:
                    location_description += f", {shell_item.comments}"

        elif isinstance(shell_item, pyfwsi.root_folder):
            if shell_item.shell_folder_identifier in KNOWN_GUIDS:
                path_segment = KNOWN_GUIDS[shell_item.shell_folder_identifier]
            elif hasattr(shell_item, "identifier") and shell_item.identifier in KNOWN_GUIDS:
                path_segment = KNOWN_GUIDS[shell_item.identifier]
            else:
                path_segment = "{{{0:s}}}".format(shell_item.shell_folder_identifier)

        elif isinstance(shell_item, pyfwsi.users_property_view):
            # Users property view
            if shell_item.delegate_folder_identifier in KNOWN_GUIDS:
                path_segment = KNOWN_GUIDS[shell_item.delegate_folder_identifier]
            elif hasattr(shell_item, "identifier") and shell_item.identifier in KNOWN_GUIDS:
                path_segment = KNOWN_GUIDS[shell_item.identifier]

            # Variable: Users property view
            elif shell_item.property_store_data:
                fwps_store = pyfwps.store()
                fwps_store.copy_from_byte_stream(shell_item.property_store_data)

                for fwps_set in iter(fwps_store.sets):
                    if fwps_set.identifier == "b725f130-47ef-101a-a5f1-02608c9eebac":
                        for fwps_record in iter(fwps_set.records):
                            entry_string = self._get_entry_string(fwps_record)

                            # PKEY_DisplayName: {b725f130-47ef-101a-a5f1-02608c9eebac}/10
                            if entry_string == "10":
                                if fwps_record.value_type == 0x0001:
                                    value_string = "<VT_NULL>"
                                elif fwps_record.value_type in (
                                    0x0003,
                                    0x0013,
                                    0x0014,
                                    0x0015,
                                ):
                                    value_string = str(fwps_record.get_data_as_integer())
                                elif fwps_record.value_type in (0x0008, 0x001E, 0x001F):
                                    value_string = fwps_record.get_data_as_string()
                                elif fwps_record.value_type == 0x000B:
                                    value_string = str(fwps_record.get_data_as_boolean())
                                elif fwps_record.value_type == 0x0040:
                                    filetime = fwps_record.get_data_as_integer()
                                    value_string = self._FormatFiletimeValue(filetime)
                                elif fwps_record.value_type == 0x0042:
                                    # TODO: add support
                                    value_string = "<VT_STREAM>"
                                elif fwps_record.value_type == 0x0048:
                                    value_string = fwps_record.get_data_as_guid()
                                elif fwps_record.value_type & 0xF000 == 0x1000:
                                    # TODO: add support
                                    value_string = "<VT_VECTOR>"
                                else:
                                    value_string = None

                                path_segment = value_string

                    elif fwps_set.identifier == "28636aa6-953d-11d2-b5d6-00c04fd918d0":
                        for fwps_record in iter(fwps_set.records):
                            entry_string = self._get_entry_string(fwps_record)

                            # PKEY_ParsingPath: {28636aa6-953d-11d2-b5d6-00c04fd918d0}/30
                            if entry_string == "30":
                                full_path = fwps_record.get_data_as_string()

        elif isinstance(shell_item, pyfwsi.control_panel_category):
            path_segment = self._check_known_guids(str(shell_item.identifier))

        elif isinstance(shell_item, pyfwsi.control_panel_item):
            path_segment = self._check_known_guids(shell_item.identifier)

        if path_segment is None:
            path_segment = "<UNKNOWN: 0x{0:02x}>".format(shell_item.class_type)

        return path_segment, full_path, location_description

    def iter_sk(self, key, reg_path, codepage=DEFAULT_CODEPAGE, base_path="", path=""):
        try:
            import pyfwsi
        except ModuleNotFoundError as ex:
            logger.exception(
                f"Plugin `shellbag_plugin` has missing modules, install regipy using"
                f" `pip install regipy[full]` in order to install plugin dependencies. "
                f"This might take some time... "
            )
            raise ex

        last_write = convert_wintime(key.header.last_modified, as_json=True)

        mru_val = key.get_value("MRUListEx")
        mru_order = self._parse_mru(mru_val)
        base_path = path

        if key.get_value("NodeSlot"):
            node_slot = str(key.get_value("NodeSlot"))
        else:
            node_slot = ""

        processed_values = set()
        for v in key.iter_values(trim_values=False):
            if v.name.isdigit():
                processed_values.add(v.name)
                slot = v.name
                byte_stream = v.value
                shell_items = pyfwsi.item_list()
                shell_items.copy_from_byte_stream(byte_stream, ascii_codepage=codepage)
                for item in shell_items.items:
                    shell_type = self._get_shell_item_type(item)
                    value, full_path, location_description = self._parse_shell_item_path_segment(self, item)
                    if not path:
                        path = value
                        base_path = ""
                    else:
                        path += f"\\{value}"

                    creation_time = None
                    access_time = None
                    modification_time = None

                    if len(item.extension_blocks) > 0:
                        for extension_block in item.extension_blocks:
                            if isinstance(extension_block, pyfwsi.file_entry_extension):
                                try:
                                    creation_time = extension_block.get_creation_time()
                                    if self.as_json:
                                        creation_time = creation_time.isoformat()
                                except OSError:
                                    logger.exception(f"Malformed creation time for {path}")
                                try:
                                    access_time = extension_block.get_access_time()
                                    if self.as_json:
                                        access_time = access_time.isoformat()
                                except OSError:
                                    logger.exception(f"Malformed access time for {path}")

                    try:
                        if hasattr(item, "modification_time"):
                            modification_time = item.get_modification_time()
                            if self.as_json:
                                modification_time = modification_time.isoformat()
                    except OSError:
                        logger.exception(f"Malformed modification time for {path}")

                    value_name = v.name
                    mru_order_location = mru_order.split("-").index(value_name)
                    self.entries.append(
                        self._create_entry(
                            value=value,
                            slot=slot,
                            reg_path=reg_path,
                            value_name=value_name,
                            node_slot=node_slot,
                            shell_type=shell_type,
                            path=path,
                            full_path=full_path,
                            location_description=location_description,
                            creation_time=creation_time,
                            access_time=access_time,
                            modification_time=modification_time,
                            last_write=last_write,
                            mru_order=mru_order,
                            mru_order_location=mru_order_location,
                        )
                    )
                    sk_reg_path = f"{reg_path}\\{value_name}"
                    try:
                        sk = self.registry_hive.get_key(sk_reg_path)
                        self.iter_sk(sk, sk_reg_path, codepage, base_path, path)
                    except RegistryKeyNotFoundException:
                        pass  # Subkey doesn't exist, continue
                    path = base_path

        # Issue #268: Handle childless subkeys - subkeys without corresponding numbered
        # values contain "first interacted" timestamps for when a folder was initially accessed
        for subkey in key.iter_subkeys():
            if subkey.name not in processed_values:
                childless_last_write = convert_wintime(subkey.header.last_modified, as_json=True)
                self.entries.append(
                    self._create_entry(
                        value=None,
                        slot=subkey.name,
                        reg_path=f"{reg_path}\\{subkey.name}",
                        value_name=subkey.name,
                        node_slot=str(subkey.get_value("NodeSlot")) if subkey.get_value("NodeSlot") else "",
                        shell_type="Childless",
                        path=path or None,
                        last_write=childless_last_write,
                        first_interacted=childless_last_write,
                    )
                )
                self.iter_sk(subkey, f"{reg_path}\\{subkey.name}", codepage, base_path, path)

    def run(self, codepage=DEFAULT_CODEPAGE):
        try:
            # flake8: noqa
            import pyfwsi
        except ModuleNotFoundError as ex:
            logger.exception(
                "Plugin `shellbag_plugin` has missing modules, install regipy using"
                " `pip install regipy[full]` in order to install plugin dependencies. "
                "This might take some time... "
            )
            raise ex

        try:
            shellbag_ntuser_subkey = self.registry_hive.get_key(NTUSER_SHELLBAG)
            self.iter_sk(shellbag_ntuser_subkey, NTUSER_SHELLBAG, codepage=codepage)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {NTUSER_SHELLBAG}: {ex}")


================================================
FILE: regipy/plugins/ntuser/sysinternals.py
================================================
"""
Sysinternals plugin - Parses Sysinternals tools EULA acceptance records
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

SYSINTERNALS_PATH = r"\Software\Sysinternals"


class SysinternalsPlugin(Plugin):
    """
    Parses Sysinternals EULA acceptance records from NTUSER.DAT
    Registry Key: Software\\Sysinternals

    When a Sysinternals tool is run and the EULA is accepted,
    it creates a subkey with the tool name containing an EulaAccepted value.
    This provides evidence of which Sysinternals tools have been executed.
    """

    NAME = "sysinternals"
    DESCRIPTION = "Parses Sysinternals tools EULA acceptance"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        logger.debug("Started Sysinternals Plugin...")

        try:
            sysinternals_key = self.registry_hive.get_key(SYSINTERNALS_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.debug(f"Could not find {self.NAME} plugin data at: {SYSINTERNALS_PATH}: {ex}")
            return

        for subkey in sysinternals_key.iter_subkeys():
            tool_name = subkey.name
            subkey_path = f"{SYSINTERNALS_PATH}\\{tool_name}"

            entry = {
                "key_path": subkey_path,
                "tool_name": tool_name,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
                "eula_accepted": False,
            }

            for value in subkey.iter_values():
                if value.name == "EulaAccepted":
                    entry["eula_accepted"] = value.value == 1

            self.entries.append(entry)


================================================
FILE: regipy/plugins/ntuser/tsclient.py
================================================
import logging

from regipy import (
    NoRegistrySubkeysException,
    RegistryKeyNotFoundException,
    convert_wintime,
)
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

logger = logging.getLogger(__name__)

TSCLIENT_HISTORY_PATH = r"\Software\Microsoft\Terminal Server Client\Servers"


class TSClientPlugin(Plugin):
    NAME = "terminal_services_history"
    DESCRIPTION = "Retrieve history of RDP connections"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            tsclient_subkey = self.registry_hive.get_key(TSCLIENT_HISTORY_PATH)
        except (RegistryKeyNotFoundException, NoRegistrySubkeysException) as ex:
            logger.error(ex)
            return

        for server in tsclient_subkey.iter_subkeys():
            self.entries.append(
                {
                    "server": server.name,
                    "last_connection": convert_wintime(server.header.last_modified, as_json=self.as_json),
                    "username_hint": server.get_value("UsernameHint"),
                }
            )


================================================
FILE: regipy/plugins/ntuser/typed_paths.py
================================================
import logging

from inflection import underscore

from regipy import RegistryKeyNotFoundException, convert_wintime
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

logger = logging.getLogger(__name__)


TYPED_PATHS_KEY_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\TypedPaths"


class TypedPathsPlugin(Plugin):
    NAME = "typed_paths"
    DESCRIPTION = "Retrieve the typed Paths from the history"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            subkey = self.registry_hive.get_key(TYPED_PATHS_KEY_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {TYPED_PATHS_KEY_PATH}: {ex}")
            return None

        self.entries = {
            "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
            "entries": [{underscore(x.name): x.value} for x in subkey.iter_values(as_json=self.as_json)],
        }


================================================
FILE: regipy/plugins/ntuser/typed_urls.py
================================================
import logging

from inflection import underscore

from regipy import RegistryKeyNotFoundException, convert_wintime
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

logger = logging.getLogger(__name__)


TYPED_URLS_KEY_PATH = r"\Software\Microsoft\Internet Explorer\TypedURLs"


class TypedUrlsPlugin(Plugin):
    NAME = "typed_urls"
    DESCRIPTION = "Retrieve the typed URLs from IE history"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            subkey = self.registry_hive.get_key(TYPED_URLS_KEY_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {TYPED_URLS_KEY_PATH}: {ex}")
            return None

        self.entries = {
            "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
            "entries": [{underscore(x.name): x.value} for x in subkey.iter_values(as_json=self.as_json)],
        }


================================================
FILE: regipy/plugins/ntuser/user_assist.py
================================================
import codecs
import logging

from construct import Bytes, Const, ConstError, Int32ul, Int64ul, Struct

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

USER_ASSIST_KEY_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist"

# guids for the various Operating Systems
GUIDS = [
    "{75048700-EF1F-11D0-9888-006097DEACF9}",  # Windows XP GUIDs
    "{5E6AB780-7743-11CF-A12B-00AA004AE837}",
    "{75048700-EF1F-11D0-9888-006097DEACF9}",  # Windows vista
    "{5E6AB780-7743-11CF-A12B-00AA004AE837}",
    "{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}",  # Windows 7
    "{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}",
    "{FA99DFC7-6AC2-453A-A5E2-5E2AFF4507BD}",  # Windows 8
    "{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}",
    "{F2A1CB5A-E3CC-4A2E-AF9D-505A7009D442}",
    "{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}",
    "{CAA59E3C-4792-41A5-9909-6A6A8D32490E}",
    "{B267E3AD-A825-4A09-82B9-EEC22AA3B847}",
    "{A3D53349-6E61-4557-8FC7-0028EDCEEBF6}",
    "{9E04CAB2-CC14-11DF-BB8C-A2F1DED72085}",
]

GUID_TO_PATH_MAPPINGS = {
    "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}": r"%SYSTEM32%",
    "{6D809377-6AF0-444B-8957-A3773F02200E}": r"%PROGRAMFILES%",
    "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}": r"%PROGRAMFILES(X86)%",
    "{F38BF404-1D43-42F2-9305-67DE0B28FC23}": r"%WINDIR%",
    "{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}": r"%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs",
    "{9E3995AB-1F9C-4F13-B827-48B24B6C7174}": r"%AppData%\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned",
    "{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}": r"%AppData%\Roaming\Microsoft\Windows\Start Menu\Programs",
    "{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}": r"%WINDIR%\SysWOW64",
}

WHITELISTED_NAMES = ["UEME_CTLSESSION"]

WIN_XP_USER_ASSIST = Struct(
    "session_id" / Int32ul,
    "run_counter" / Int32ul,
    "last_execution_timestamp" / Int64ul,
)

WIN7_USER_ASSIST = Struct(
    "session_id" / Int32ul,
    "run_counter" / Int32ul,
    "focus_count" / Int32ul,
    "total_focus_time_ms" / Int32ul,
    "unknown" * Bytes(44),
    "last_execution_timestamp" / Int64ul,
    "Const" * Const(b"\x00\x00\x00\x00"),
)


class UserAssistPlugin(Plugin):
    NAME = "user_assist"
    DESCRIPTION = "Parse User Assist artifact"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        for guid in GUIDS:
            try:
                subkey = self.registry_hive.get_key(rf"{USER_ASSIST_KEY_PATH}\{guid}")
                count_subkey = subkey.get_subkey("Count")

                if not count_subkey.values_count:
                    logger.debug(f"Skipping {guid}")
                    continue

                for value in count_subkey.iter_values(trim_values=False):
                    name = codecs.decode(value.name, encoding="rot-13")

                    if name in WHITELISTED_NAMES:
                        continue

                    for k, v in GUID_TO_PATH_MAPPINGS.items():
                        if k in name:
                            name = name.replace(k, v)
                            break

                    entry = None
                    data = value.value
                    if len(data) == 72:
                        try:
                            parsed_entry = WIN7_USER_ASSIST.parse(data)
                        except ConstError as ex:
                            logger.error(f"Could not parse user assist entry named {name}: {ex}")
                            continue

                        entry = {
                            "name": name,
                            "timestamp": convert_wintime(
                                parsed_entry.last_execution_timestamp,
                                as_json=self.as_json,
                            ),
                            "run_counter": parsed_entry.run_counter,
                            "focus_count": parsed_entry.focus_count,
                            "total_focus_time_ms": parsed_entry.total_focus_time_ms,
                            "session_id": parsed_entry.session_id,
                        }

                    elif len(data) == 16:
                        try:
                            parsed_entry = WIN_XP_USER_ASSIST.parse(data)
                        except ConstError as ex:
                            logger.error(f"Could not parse user assist entry named {name}: {ex}")
                            continue

                        entry = {
                            "name": name,
                            "timestamp": convert_wintime(
                                parsed_entry.last_execution_timestamp,
                                as_json=self.as_json,
                            ),
                            "session_id": parsed_entry.session_id,
                            "run_counter": parsed_entry.run_counter - 5,
                        }

                    if entry:
                        self.entries.append(entry)
            except RegistryKeyNotFoundException:
                continue


================================================
FILE: regipy/plugins/ntuser/winrar.py
================================================
import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

WINRAR_ARCHIVE_CREATION_HIST = r"\SOFTWARE\WinRAR\DialogEditHistory\ArcName"
WINRAR_ARCHIVE_EXTRACT_HIST = r"\SOFTWARE\WinRAR\DialogEditHistory\ExtrPath"
WINRAR_ARCHIVE_OPEN_HIST = r"\SOFTWARE\WinRAR\ArcHistory"


class WinRARPlugin(Plugin):
    NAME = "winrar_plugin"
    DESCRIPTION = "Parse the WinRAR archive history"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            open_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_OPEN_HIST)

            timestamp = convert_wintime(open_subkey.header.last_modified, as_json=self.as_json)
            for value in open_subkey.iter_values(as_json=self.as_json):
                self.entries.append(
                    {
                        "last_write": timestamp,
                        "file_path": value.value,
                        "value_name": value.name,
                        "operation": "archive_opened",
                    }
                )

        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_OPEN_HIST}: {ex}")

        try:
            create_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_CREATION_HIST)

            timestamp = convert_wintime(create_subkey.header.last_modified, as_json=self.as_json)
            for value in create_subkey.iter_values(as_json=self.as_json):
                self.entries.append(
                    {
                        "last_write": timestamp,
                        "file_name": value.value,
                        "value_name": value.name,
                        "operation": "archive_created",
                    }
                )

        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_CREATION_HIST}: {ex}")

        try:
            extract_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_EXTRACT_HIST)

            timestamp = convert_wintime(extract_subkey.header.last_modified, as_json=self.as_json)
            for value in extract_subkey.iter_values(as_json=self.as_json):
                self.entries.append(
                    {
                        "last_write": timestamp,
                        "file_path": value.value,
                        "value_name": value.name,
                        "operation": "archive_extracted",
                    }
                )

        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_EXTRACT_HIST}: {ex}")


================================================
FILE: regipy/plugins/ntuser/winscp_saved_sessions.py
================================================
import logging

from regipy import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

WINSCP_SAVED_SESSIONS_PATH = r"\Software\Martin Prikryl\WinSCP 2\Sessions"


class WinSCPSavedSessionsPlugin(Plugin):
    NAME = "winscp_saved_sessions"
    DESCRIPTION = "Retrieve list of WinSCP saved sessions"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def _get_winscp_saved_sessions(self, subkey_path):
        try:
            sessions_sk = self.registry_hive.get_key(subkey_path)
        except RegistryKeyNotFoundException as ex:
            logger.error(ex)
            return

        for winscp_saved_session in sessions_sk.iter_subkeys():
            values = (
                {x.name: x.value for x in winscp_saved_session.iter_values(as_json=self.as_json)}
                if winscp_saved_session.values_count
                else {}
            )
            self.entries.append(
                {
                    "timestamp": convert_wintime(winscp_saved_session.header.last_modified, as_json=self.as_json),
                    "hive_name": "HKEY_CURRENT_USER",
                    "key_path": rf"HKEY_CURRENT_USER{subkey_path}\{winscp_saved_session.name}",
                    **values,
                }
            )

    def run(self):
        self._get_winscp_saved_sessions(WINSCP_SAVED_SESSIONS_PATH)


================================================
FILE: regipy/plugins/ntuser/word_wheel_query.py
================================================
import logging

from construct import CString, GreedyRange, Int32ul

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

WORD_WHEEL_QUERY_KEY_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Explorer\WordWheelQuery"


class WordWheelQueryPlugin(Plugin):
    NAME = "word_wheel_query"
    DESCRIPTION = "Parse the word wheel query artifact"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def run(self):
        try:
            subkey = self.registry_hive.get_key(WORD_WHEEL_QUERY_KEY_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find {self.NAME} plugin data at: {WORD_WHEEL_QUERY_KEY_PATH}: {ex}")
            return None

        timestamp = convert_wintime(subkey.header.last_modified, as_json=self.as_json)

        mru_list_order = subkey.get_value("MRUListEx")

        # If this is the value, the list is empty
        if mru_list_order == 0xFFFFFFFF:
            return None

        for i, entry_name in enumerate(GreedyRange(Int32ul).parse(mru_list_order)):
            entry_value = subkey.get_value(str(entry_name))

            if not entry_value:
                continue

            self.entries.append(
                {
                    "last_write": timestamp,
                    "mru_id": entry_name,
                    "order": i,
                    "name": CString("utf-16").parse(entry_value),
                }
            )


================================================
FILE: regipy/plugins/ntuser/wsl.py
================================================
import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

# Ressources : https://patrickwu.space/2020/07/19/wsl-related-registry/

WSL_PATH = r"\Software\Microsoft\Windows\CurrentVersion\Lxss"


class WSLPlugin(Plugin):
    NAME = "wsl"
    DESCRIPTION = "Get WSL information"
    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE

    def get_wsl_info(self, subkey, distribs=None):
        if distribs is None:
            distribs = []

        try:
            flags = subkey.get_value("Flags")
            state = subkey.get_value("State")
            version = subkey.get_value("Version")

            # Initialize the entry for a distribution with its GUID as the key
            distribution_entry = {
                "GUID": subkey.name,
                "last_modified": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
                "wsl_distribution_source_location": subkey.get_value("BasePath"),
                "default_uid": subkey.get_value("DefaultUid"),
                "distribution_name": subkey.get_value("DistributionName"),
                "default_environment": subkey.get_value("DefaultEnvironment"),  # REG_MULTI_SZ
                "flags": flags,
                "kernel_command_line": subkey.get_value("KernelCommandLine"),
                "package_family_name": subkey.get_value("PackageFamilyName"),
                "state": state,
                "filesystem": ("lxfs" if version == 1 else "wslfs" if version == 2 else "Unknown"),
            }

            # Decode flags for additional information
            if flags is not None:
                distribution_entry["enable_interop"] = bool(flags & 0x1)
                distribution_entry["append_nt_path"] = bool(flags & 0x2)
                distribution_entry["enable_drive_mounting"] = bool(flags & 0x4)

            # Decode the state of the distribution
            if state is not None:
                if state == 0x1:
                    distribution_entry["state"] = "Normal"
                elif state == 0x3:
                    distribution_entry["state"] = "Installing"
                elif state == 0x4:
                    distribution_entry["state"] = "Uninstalling"
                else:
                    distribution_entry["state"] = "Unknown"

            # Add the distribution entry with its GUID to the list of distributions
            distribs.append(distribution_entry)

        except Exception as e:
            logger.error(f"Error processing subkey {subkey.name}: {e}")
            raise

        return distribs

    def run(self):
        try:
            # Attempt to get the WSL registry key
            wsl_key = self.registry_hive.get_key(WSL_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Registry key not found at path {WSL_PATH}: {ex}")
            return

        self.entries = {
            WSL_PATH: {
                "last_modified": convert_wintime(wsl_key.header.last_modified, as_json=self.as_json),
                "number_of_distrib": wsl_key.header.subkey_count,
                "default_distrib_GUID": wsl_key.get_value("DefaultDistribution"),
                "wsl_version": (
                    "WSL1"
                    if wsl_key.get_value("DefaultVersion") == 1
                    else ("WSL2" if wsl_key.get_value("DefaultVersion") == 2 else "Unknown")
                ),
                "nat_ip_address": wsl_key.get_value("NatIpAddress"),
                "distributions": [],
            }
        }

        try:
            for distrib in wsl_key.iter_subkeys():
                self.get_wsl_info(distrib, self.entries[WSL_PATH]["distributions"])
        except Exception as e:
            logger.error(f"Error iterating over subkeys in {distrib.path}: {e}")


================================================
FILE: regipy/plugins/plugin.py
================================================
import logging
from typing import Any

from regipy.registry import RegistryHive

PLUGINS = set()

logger = logging.getLogger(__name__)


class Plugin:
    NAME: str = None
    DESCRIPTION: str = None
    COMPATIBLE_HIVE: str = None

    def __init_subclass__(cls):
        PLUGINS.add(cls)

    def __init__(self, registry_hive: RegistryHive, as_json=False, trim_values=False):
        self.registry_hive = registry_hive
        self.as_json = as_json
        self.trim_values = trim_values

        self.partial_hive_path = registry_hive.partial_hive_path

        # This variable should always hold the final result - in order to use it in anomaly detection and timeline gen.
        self.entries: list[dict[str, Any]] = []

    def can_run(self):
        """
        Wether the plugin can run or not, according to specific checks
        :return:
        """
        return self.registry_hive.hive_type == self.COMPATIBLE_HIVE

    def run(self):
        """
        Execute the plugin
        :return:
        """

    def generate_timeline_artifacts(self):
        """
        Run on the output of a plugin and generate timeline entries
        :return:
        """
        pass

    def detect_anomalies(self):
        """
        Run on the output of a plugin and detect possible anomalies
        :return:
        """
        pass


================================================
FILE: regipy/plugins/plugin_template.py
================================================
import logging

from regipy.hive_types import NTUSER_HIVE_TYPE
from regipy.plugins.plugin import Plugin

logger = logging.getLogger(__name__)


class TemplatePlugin(Plugin):
    NAME = "template_plugin"
    DESCRIPTION = "template_description"

    def can_run(self):
        # TODO: Choose the relevant condition - to determine if the plugin is relevant for the given hive
        return self.registry_hive.hive_type == NTUSER_HIVE_TYPE

    def run(self):
        # TODO: Return the relevant values
        raise NotImplementedError


================================================
FILE: regipy/plugins/sam/__init__.py
================================================


================================================
FILE: regipy/plugins/sam/local_sid.py
================================================
"""
Windows machine local SID extractor plugin
"""

import logging

from regipy.hive_types import SAM_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.security_utils import convert_sid
from regipy.structs import SID
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

ACCOUNT_PATH = r"\SAM\Domains\Account"


class LocalSidPlugin(Plugin):
    """
    Windows machine local SID extractor
    """

    NAME = "local_sid"
    DESCRIPTION = "Get the machine local SID"
    COMPATIBLE_HIVE = SAM_HIVE_TYPE

    def run(self) -> None:
        logger.debug("Started Machine Local SID Plugin...")

        account_key = self.registry_hive.get_key(ACCOUNT_PATH)

        # A computer's SID is stored in the SECURITY hive
        # under 'SECURITY\SAM\Domains\Account'.
        # This key has a value named 'F' and a value named 'V'.
        v_value = account_key.get_value("V")

        # The 'V' value is a binary value that has the computer SID embedded
        # within it at the end of its data.
        sid_value = v_value[-24:]

        parsed_sid = SID.parse(sid_value)

        self.entries.append(
            {
                "machine_sid": convert_sid(parsed_sid),
                "timestamp": convert_wintime(account_key.header.last_modified, as_json=self.as_json),
            }
        )


================================================
FILE: regipy/plugins/sam/samparse.py
================================================
"""
SAM Parse plugin - Parses user account information from SAM hive
"""

import contextlib
import logging
import struct
from datetime import datetime
from typing import Optional

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import SAM_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

SAM_USERS_PATH = r"\SAM\Domains\Account\Users"
SAM_NAMES_PATH = r"\SAM\Domains\Account\Users\Names"

# Account type flags
ACCOUNT_FLAGS = {
    0x0001: "Account Disabled",
    0x0002: "Home Directory Required",
    0x0004: "Password Not Required",
    0x0008: "Temp Duplicate Account",
    0x0010: "Normal User Account",
    0x0020: "MNS Logon Account",
    0x0040: "Interdomain Trust Account",
    0x0080: "Workstation Trust Account",
    0x0100: "Server Trust Account",
    0x0200: "Password Does Not Expire",
    0x0400: "Account Auto Locked",
    0x0800: "Encrypted Text Password Allowed",
    0x1000: "Smartcard Required",
    0x2000: "Trusted For Delegation",
    0x4000: "Not Delegated",
    0x8000: "Use DES Key Only",
    0x10000: "Preauth Not Required",
    0x20000: "Password Expired",
    0x40000: "Trusted To Auth For Delegation",
    0x80000: "No Auth Data Required",
    0x100000: "Partial Secrets Account",
}


def filetime_to_datetime(filetime: int) -> Optional[str]:
    """Convert Windows FILETIME to ISO datetime string"""
    if filetime == 0 or filetime == 0x7FFFFFFFFFFFFFFF:
        return None
    try:
        # FILETIME is 100-nanosecond intervals since January 1, 1601
        epoch_diff = 116444736000000000  # Difference between 1601 and 1970 in 100-ns
        timestamp = (filetime - epoch_diff) / 10000000
        dt = datetime.utcfromtimestamp(timestamp)
        return dt.isoformat() + "+00:00"
    except (ValueError, OSError, OverflowError):
        return None


def parse_account_flags(flags: int) -> list:
    """Parse account flags bitmask to list of descriptions"""
    result = []
    for bit, description in ACCOUNT_FLAGS.items():
        if flags & bit:
            result.append(description)
    return result


class SAMParsePlugin(Plugin):
    """
    Parses user account information from SAM hive

    Extracts:
    - User names and RIDs
    - Account creation time
    - Last login time
    - Password last set time
    - Login count
    - Account flags (disabled, locked, etc.)
    - Group memberships

    Registry Key: SAM\\Domains\\Account\\Users
    """

    NAME = "samparse"
    DESCRIPTION = "Parses user accounts from SAM hive"
    COMPATIBLE_HIVE = SAM_HIVE_TYPE

    def run(self):
        logger.debug("Started SAM Parse Plugin...")

        # First, build a mapping of RID to username from the Names subkey
        rid_to_name = self._get_rid_to_name_mapping()

        # Then parse user data from the RID subkeys
        try:
            users_key = self.registry_hive.get_key(SAM_USERS_PATH)
        except RegistryKeyNotFoundException as ex:
            logger.error(f"Could not find SAM Users at: {SAM_USERS_PATH}: {ex}")
            return

        for subkey in users_key.iter_subkeys():
            # Skip the Names subkey
            if subkey.name == "Names":
                continue

            # RID is the subkey name in hex (e.g., "000001F4" = 500 = Administrator)
            try:
                rid = int(subkey.name, 16)
            except ValueError:
                continue

            entry = {
                "key_path": f"{SAM_USERS_PATH}\\{subkey.name}",
                "rid": rid,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
            }

            # Get username from mapping
            if rid in rid_to_name:
                entry["username"] = rid_to_name[rid]["username"]
                entry["name_key_last_write"] = rid_to_name[rid]["last_write"]

            # Parse the V value (contains most user data)
            v_value = None
            f_value = None
            for value in subkey.iter_values():
                if value.name == "V":
                    v_value = value.value
                elif value.name == "F":
                    f_value = value.value

            if f_value:
                self._parse_f_value(f_value, entry)

            if v_value:
                self._parse_v_value(v_value, entry)

            self.entries.append(entry)

    def _get_rid_to_name_mapping(self) -> dict:
        """Build mapping of RID to username from Names subkey"""
        mapping = {}
        try:
            names_key = self.registry_hive.get_key(SAM_NAMES_PATH)
        except RegistryKeyNotFoundException:
            return mapping

        for subkey in names_key.iter_subkeys():
            username = subkey.name
            last_write = convert_wintime(subkey.header.last_modified, as_json=self.as_json)

            # The default value's type contains the RID
            for value in subkey.iter_values():
                if value.name == "" or value.name == "(Default)":
                    # The value type is the RID
                    rid = value.value_type_raw if hasattr(value, "value_type_raw") else None
                    if rid is None:
                        # Try to get from the raw type
                        with contextlib.suppress(Exception):
                            rid = value._vk_record.data_type
                    if rid:
                        mapping[rid] = {"username": username, "last_write": last_write}
                    break

        return mapping

    def _parse_f_value(self, data, entry: dict):
        """Parse the F value containing user account metadata"""
        if not data:
            return

        # Ensure data is bytes
        if isinstance(data, str):
            try:
                data = bytes.fromhex(data)
            except ValueError:
                data = data.encode("latin-1")

        if len(data) < 72:
            return

        try:
            # F value structure (offsets are for Vista+)
            # 0x08: Last Login Time (FILETIME)
            # 0x18: Password Last Set (FILETIME)
            # 0x20: Account Expires (FILETIME)
            # 0x28: Last Failed Login (FILETIME)
            # 0x30: RID
            # 0x38: Account Control Flags
            # 0x40: Failed Login Count
            # 0x42: Login Count

            last_login = struct.unpack("<Q", data[8:16])[0]
            entry["last_login"] = filetime_to_datetime(last_login)

            pwd_last_set = struct.unpack("<Q", data[24:32])[0]
            entry["password_last_set"] = filetime_to_datetime(pwd_last_set)

            account_expires = struct.unpack("<Q", data[32:40])[0]
            entry["account_expires"] = filetime_to_datetime(account_expires)

            last_failed_login = struct.unpack("<Q", data[40:48])[0]
            entry["last_failed_login"] = filetime_to_datetime(last_failed_login)

            rid_from_f = struct.unpack("<I", data[48:52])[0]
            if rid_from_f and rid_from_f == entry.get("rid"):
                pass  # Consistent

            acct_flags = struct.unpack("<H", data[56:58])[0]
            entry["account_flags"] = acct_flags
            entry["account_flags_parsed"] = parse_account_flags(acct_flags)

            failed_login_count = struct.unpack("<H", data[64:66])[0]
            entry["failed_login_count"] = failed_login_count

            login_count = struct.unpack("<H", data[66:68])[0]
            entry["login_count"] = login_count

        except (struct.error, IndexError) as e:
            logger.debug(f"Error parsing F value: {e}")

    def _parse_v_value(self, data, entry: dict):
        """Parse the V value containing user details like name, comment, etc."""
        if not data:
            return

        # Ensure data is bytes
        if isinstance(data, str):
            try:
                data = bytes.fromhex(data)
            except ValueError:
                data = data.encode("latin-1")

        if len(data) < 0xCC:
            return

        try:
            # V value has a header with offsets to various strings
            # Each entry is: offset (4 bytes), length (4 bytes), unknown (4 bytes)
            # The offsets are relative to 0xCC

            # Username at offset 0x0C
            name_offset = struct.unpack("<I", data[0x0C:0x10])[0] + 0xCC
            name_length = struct.unpack("<I", data[0x10:0x14])[0]
            if name_offset + name_length <= len(data) and name_length > 0:
                username = data[name_offset : name_offset + name_length].decode("utf-16-le", errors="replace")
                if "username" not in entry:
                    entry["username"] = username

            # Full Name at offset 0x18
            fullname_offset = struct.unpack("<I", data[0x18:0x1C])[0] + 0xCC
            fullname_length = struct.unpack("<I", data[0x1C:0x20])[0]
            if fullname_offset + fullname_length <= len(data) and fullname_length > 0:
                entry["full_name"] = data[fullname_offset : fullname_offset + fullname_length].decode(
                    "utf-16-le", errors="replace"
                )

            # Comment at offset 0x24
            comment_offset = struct.unpack("<I", data[0x24:0x28])[0] + 0xCC
            comment_length = struct.unpack("<I", data[0x28:0x2C])[0]
            if comment_offset + comment_length <= len(data) and comment_length > 0:
                entry["comment"] = data[comment_offset : comment_offset + comment_length].decode("utf-16-le", errors="replace")

            # User Comment at offset 0x30
            user_comment_offset = struct.unpack("<I", data[0x30:0x34])[0] + 0xCC
            user_comment_length = struct.unpack("<I", data[0x34:0x38])[0]
            if user_comment_offset + user_comment_length <= len(data) and user_comment_length > 0:
                entry["user_comment"] = data[user_comment_offset : user_comment_offset + user_comment_length].decode(
                    "utf-16-le", errors="replace"
                )

            # Home Directory at offset 0x48
            homedir_offset = struct.unpack("<I", data[0x48:0x4C])[0] + 0xCC
            homedir_length = struct.unpack("<I", data[0x4C:0x50])[0]
            if homedir_offset + homedir_length <= len(data) and homedir_length > 0:
                entry["home_directory"] = data[homedir_offset : homedir_offset + homedir_length].decode(
                    "utf-16-le", errors="replace"
                )

            # Home Directory Connect at offset 0x54
            homedir_connect_offset = struct.unpack("<I", data[0x54:0x58])[0] + 0xCC
            homedir_connect_length = struct.unpack("<I", data[0x58:0x5C])[0]
            if homedir_connect_offset + homedir_connect_length <= len(data) and homedir_connect_length > 0:
                entry["home_directory_connect"] = data[
                    homedir_connect_offset : homedir_connect_offset + homedir_connect_length
                ].decode("utf-16-le", errors="replace")

            # Script Path at offset 0x60
            script_offset = struct.unpack("<I", data[0x60:0x64])[0] + 0xCC
            script_length = struct.unpack("<I", data[0x64:0x68])[0]
            if script_offset + script_length <= len(data) and script_length > 0:
                entry["script_path"] = data[script_offset : script_offset + script_length].decode("utf-16-le", errors="replace")

            # Profile Path at offset 0x6C
            profile_offset = struct.unpack("<I", data[0x6C:0x70])[0] + 0xCC
            profile_length = struct.unpack("<I", data[0x70:0x74])[0]
            if profile_offset + profile_length <= len(data) and profile_length > 0:
                entry["profile_path"] = data[profile_offset : profile_offset + profile_length].decode(
                    "utf-16-le", errors="replace"
                )

            # Workstations at offset 0x78
            workstations_offset = struct.unpack("<I", data[0x78:0x7C])[0] + 0xCC
            workstations_length = struct.unpack("<I", data[0x7C:0x80])[0]
            if workstations_offset + workstations_length <= len(data) and workstations_length > 0:
                entry["workstations"] = data[workstations_offset : workstations_offset + workstations_length].decode(
                    "utf-16-le", errors="replace"
                )

        except (struct.error, IndexError) as e:
            logger.debug(f"Error parsing V value: {e}")


================================================
FILE: regipy/plugins/security/__init__.py
================================================


================================================
FILE: regipy/plugins/security/domain_sid.py
================================================
"""
Windows machine domain name and SID extractor plugin
"""

import logging
from typing import Optional

from regipy.hive_types import SECURITY_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.security_utils import convert_sid
from regipy.structs import SID
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

DOMAIN_NAME_PATH = r"\Policy\PolPrDmN"
DOMAIN_SID_PATH = r"\Policy\PolMachineAccountS"


class DomainSidPlugin(Plugin):
    """
    Windows machine domain name and SID extractor
    """

    NAME = "domain_sid"
    DESCRIPTION = "Get the machine domain name and SID"
    COMPATIBLE_HIVE = SECURITY_HIVE_TYPE

    def run(self) -> None:
        logger.debug("Started Machine Domain SID Plugin...")

        name_key = self.registry_hive.get_key(DOMAIN_NAME_PATH)

        # Primary Domain Name or Workgroup Name (binary-encoded and length-prefixed)
        name_value = name_key.get_value()

        # Skip UNICODE_STRING struct header and strip trailing \x0000
        domain_name = name_value[8:].decode("utf-16-le", errors="replace").rstrip("\x00")

        sid_key = self.registry_hive.get_key(DOMAIN_SID_PATH)

        # Domain SID value (binary-encoded)
        sid_value = sid_key.get_value()

        domain_sid: Optional[str] = None
        machine_sid: Optional[str] = None

        # The default key value is 0x00000000 (REG_DWORD) when
        # the Windows machine is not in an AD domain.
        # Otherwise, it contains the domain machine SID data
        # in the standard binary format (REG_BINARY).
        if isinstance(sid_value, bytes):
            parsed_sid = SID.parse(sid_value)
            domain_sid = convert_sid(parsed_sid, strip_rid=True)
            machine_sid = convert_sid(parsed_sid)

        self.entries.append(
            {
                "domain_name": domain_name,
                "domain_sid": domain_sid,
                "machine_sid": machine_sid,
                "timestamp": convert_wintime(sid_key.header.last_modified, as_json=self.as_json),
            }
        )


================================================
FILE: regipy/plugins/software/__init__.py
================================================


================================================
FILE: regipy/plugins/software/appcompatflags.py
================================================
"""
AppCompatFlags plugin - Parses application compatibility flags and layers
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import SOFTWARE_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

APPCOMPAT_FLAGS_PATH = r"\Microsoft\Windows NT\CurrentVersion\AppCompatFlags"
APPCOMPAT_LAYERS_PATH = r"\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
APPCOMPAT_CUSTOM_PATH = r"\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Custom"


class AppCompatFlagsPlugin(Plugin):
    """
    Parses Application Compatibility Flags from SOFTWARE hive

    Provides information about:
    - Compatibility layers (e.g., RunAsAdmin, WinXP compatibility mode)
    - Custom compatibility shims applied to applications
    - Evidence of application execution

    Registry Keys:
    - Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags
    - Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers
    - Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom
    """

    NAME = "appcompat_flags"
    DESCRIPTION = "Parses application compatibility flags and layers"
    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE

    def run(self):
        logger.debug("Started AppCompatFlags Plugin...")

        self._parse_layers()
        self._parse_custom()

    def _parse_layers(self):
        """Parse compatibility layers applied to applications"""
        try:
            layers_key = self.registry_hive.get_key(APPCOMPAT_LAYERS_PATH)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find AppCompatFlags Layers at: {APPCOMPAT_LAYERS_PATH}")
            return

        entry = {
            "type": "layers",
            "key_path": APPCOMPAT_LAYERS_PATH,
            "last_write": convert_wintime(layers_key.header.last_modified, as_json=self.as_json),
            "applications": [],
        }

        for value in layers_key.iter_values():
            # Value name is the application path
            # Value data is the compatibility settings (e.g., "~ RUNASADMIN")
            app_entry = {
                "path": value.name,
                "layers": value.value,
            }

            # Parse the layers string
            if isinstance(value.value, str):
                layers = [layer.strip() for layer in value.value.split() if layer.strip() and layer.strip() != "~"]
                app_entry["parsed_layers"] = layers

            entry["applications"].append(app_entry)

        if entry["applications"]:
            self.entries.append(entry)

    def _parse_custom(self):
        """Parse custom compatibility shims"""
        try:
            custom_key = self.registry_hive.get_key(APPCOMPAT_CUSTOM_PATH)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find AppCompatFlags Custom at: {APPCOMPAT_CUSTOM_PATH}")
            return

        for subkey in custom_key.iter_subkeys():
            # Each subkey is an application name (e.g., "program.exe")
            app_name = subkey.name
            subkey_path = f"{APPCOMPAT_CUSTOM_PATH}\\{app_name}"

            entry = {
                "type": "custom",
                "key_path": subkey_path,
                "application": app_name,
                "last_write": convert_wintime(subkey.header.last_modified, as_json=self.as_json),
                "shims": [],
            }

            for value in subkey.iter_values():
                # Value names are shim database identifiers
                entry["shims"].append(
                    {
                        "name": value.name,
                        "value": value.value,
                    }
                )

            if entry["shims"]:
                self.entries.append(entry)


================================================
FILE: regipy/plugins/software/appinitdlls.py
================================================
"""
AppInit_DLLs plugin - Parses persistence via AppInit_DLLs
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import SOFTWARE_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

# 32-bit path
APPINIT_DLLS_PATH = r"\Microsoft\Windows NT\CurrentVersion\Windows"
# 64-bit path (WoW6432Node)
APPINIT_DLLS_WOW64_PATH = r"\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows"


class AppInitDLLsPlugin(Plugin):
    """
    Parses AppInit_DLLs persistence mechanism from SOFTWARE hive

    AppInit_DLLs is a registry value that causes Windows to load specified DLLs
    into every user-mode process that links to User32.dll. This is a known
    persistence mechanism used by malware.

    Registry Keys:
    - Microsoft\\Windows NT\\CurrentVersion\\Windows
    - Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows (64-bit systems)
    """

    NAME = "appinit_dlls"
    DESCRIPTION = "Parses AppInit_DLLs persistence entries"
    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE

    def run(self):
        logger.debug("Started AppInit_DLLs Plugin...")

        self._parse_appinit_dlls(APPINIT_DLLS_PATH, "x64")
        self._parse_appinit_dlls(APPINIT_DLLS_WOW64_PATH, "x86")

    def _parse_appinit_dlls(self, path: str, architecture: str):
        """Parse AppInit_DLLs at the given path"""
        try:
            windows_key = self.registry_hive.get_key(path)
        except RegistryKeyNotFoundException:
            logger.debug(f"Could not find AppInit_DLLs at: {path}")
            return

        entry = {
            "key_path": path,
            "architecture": architecture,
            "last_write": convert_wintime(windows_key.header.last_modified, as_json=self.as_json),
            "appinit_dlls": None,
            "load_appinit_dlls": None,
            "require_signed_appinit_dlls": None,
        }

        for value in windows_key.iter_values():
            name = value.name
            val = value.value

            if name == "AppInit_DLLs":
                entry["appinit_dlls"] = val
            elif name == "LoadAppInit_DLLs":
                entry["load_appinit_dlls"] = val == 1
            elif name == "RequireSignedAppInit_DLLs":
                entry["require_signed_appinit_dlls"] = val == 1

        # Only add entry if AppInit_DLLs has content or loading is enabled
        if entry["appinit_dlls"] or entry["load_appinit_dlls"]:
            self.entries.append(entry)


================================================
FILE: regipy/plugins/software/apppaths.py
================================================
"""
App Paths plugin - Parses application paths registry entries
"""

import logging

from regipy.exceptions import RegistryKeyNotFoundException
from regipy.hive_types import SOFTWARE_HIVE_TYPE
from regipy.plugins.plugin import Plugin
from regipy.utils import convert_wintime

logger = logging.getLogger(__name__)

APP_PATHS_PATH = r"\Microsoft\Windows\CurrentVersion\App Paths"
APP_PATHS_WOW64_PATH = r"\Wow6432Node\Microsoft\Windows\CurrentVersion\App Paths"


class AppPathsPlugin(Plugin):
    """
    Parses App Paths from SOFTWARE hive

    App Paths allows applications to be launched by name without specifying
    the full path. Each subkey under App Paths is an executable name, and
    contains the path to the actual executable.

    This is useful for:
    - Finding installed applications
    - Detecting persistence (malware can hijack app paths)
    - Understanding application configurations

    Registry Keys:
    - Microsoft\\Windows\\CurrentVersion\\App Paths
    - Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths
    """

    NAME = "app_paths"
    DESCRIPTION = "Parses application paths registry entries"
    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE

    def run(self):
        logger.debug("Started App Paths Plugin...")

        self._parse_app_paths(APP_PATHS_PATH, "x64")
        self._parse_app_paths(APP_PATHS_WO
Download .txt
gitextract_zwilyk2_/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── ci.yml
│       ├── publish.yml
│       └── scorecard.yml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── SECURITY.md
├── docs/
│   ├── PLUGINS.md
│   └── README.rst
├── pyproject.toml
├── regipy/
│   ├── __init__.py
│   ├── cli.py
│   ├── cli_utils.py
│   ├── constants.py
│   ├── exceptions.py
│   ├── hive_types.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── amcache/
│   │   │   ├── __init__.py
│   │   │   └── amcache.py
│   │   ├── bcd/
│   │   │   ├── __init__.py
│   │   │   └── boot_entry_list.py
│   │   ├── ntuser/
│   │   │   ├── __init__.py
│   │   │   ├── appkeys.py
│   │   │   ├── classes_installer.py
│   │   │   ├── comdlg32.py
│   │   │   ├── installed_programs_ntuser.py
│   │   │   ├── muicache.py
│   │   │   ├── network_drives.py
│   │   │   ├── persistence.py
│   │   │   ├── putty.py
│   │   │   ├── recentdocs.py
│   │   │   ├── runmru.py
│   │   │   ├── shellbags_ntuser.py
│   │   │   ├── sysinternals.py
│   │   │   ├── tsclient.py
│   │   │   ├── typed_paths.py
│   │   │   ├── typed_urls.py
│   │   │   ├── user_assist.py
│   │   │   ├── winrar.py
│   │   │   ├── winscp_saved_sessions.py
│   │   │   ├── word_wheel_query.py
│   │   │   └── wsl.py
│   │   ├── plugin.py
│   │   ├── plugin_template.py
│   │   ├── sam/
│   │   │   ├── __init__.py
│   │   │   ├── local_sid.py
│   │   │   └── samparse.py
│   │   ├── security/
│   │   │   ├── __init__.py
│   │   │   └── domain_sid.py
│   │   ├── software/
│   │   │   ├── __init__.py
│   │   │   ├── appcompatflags.py
│   │   │   ├── appinitdlls.py
│   │   │   ├── apppaths.py
│   │   │   ├── classes_installer.py
│   │   │   ├── defender.py
│   │   │   ├── disablesr.py
│   │   │   ├── execpolicy.py
│   │   │   ├── image_file_execution_options.py
│   │   │   ├── installed_programs.py
│   │   │   ├── last_logon.py
│   │   │   ├── networklist.py
│   │   │   ├── persistence.py
│   │   │   ├── printdemon.py
│   │   │   ├── profilelist.py
│   │   │   ├── pslogging.py
│   │   │   ├── spp_clients.py
│   │   │   ├── susclient.py
│   │   │   ├── tracing.py
│   │   │   ├── uac.py
│   │   │   └── winver.py
│   │   ├── system/
│   │   │   ├── __init__.py
│   │   │   ├── active_controlset.py
│   │   │   ├── appcertdlls.py
│   │   │   ├── backuprestore.py
│   │   │   ├── bam.py
│   │   │   ├── bootkey.py
│   │   │   ├── codepage.py
│   │   │   ├── computer_name.py
│   │   │   ├── crash_dump.py
│   │   │   ├── diag_sr.py
│   │   │   ├── disablelastaccess.py
│   │   │   ├── external/
│   │   │   │   ├── ShimCacheParser.py
│   │   │   │   └── __init__.py
│   │   │   ├── host_domain_name.py
│   │   │   ├── lsa_packages.py
│   │   │   ├── mountdev.py
│   │   │   ├── network_data.py
│   │   │   ├── pagefile.py
│   │   │   ├── pending_file_rename.py
│   │   │   ├── previous_winver.py
│   │   │   ├── processor_architecture.py
│   │   │   ├── routes.py
│   │   │   ├── safeboot_configuration.py
│   │   │   ├── services.py
│   │   │   ├── shares.py
│   │   │   ├── shimcache.py
│   │   │   ├── shutdown.py
│   │   │   ├── timezone_data.py
│   │   │   ├── timezone_data2.py
│   │   │   ├── usb_devices.py
│   │   │   ├── usbstor.py
│   │   │   └── wdigest.py
│   │   ├── usrclass/
│   │   │   ├── __init__.py
│   │   │   └── shellbags_usrclass.py
│   │   ├── utils.py
│   │   ├── validated_plugins.json
│   │   └── validation_status.py
│   ├── py.typed
│   ├── recovery.py
│   ├── regdiff.py
│   ├── registry.py
│   ├── security_utils.py
│   ├── structs.py
│   └── utils.py
├── regipy_mcp_server/
│   ├── README.md
│   ├── claude_desktop_config.example.json
│   ├── server.py
│   └── test_local.py
├── regipy_tests/
│   ├── __init__.py
│   ├── cli_tests.py
│   ├── conftest.py
│   ├── data/
│   │   ├── BCD.xz
│   │   ├── NTUSER-WSL.DAT.xz
│   │   ├── NTUSER.DAT.xz
│   │   ├── NTUSER_BAGMRU.DAT.xz
│   │   ├── NTUSER_modified.DAT.xz
│   │   ├── NTUSER_with_winscp.DAT.xz
│   │   ├── SAM.xz
│   │   ├── SECURITY.xz
│   │   ├── SOFTWARE.xz
│   │   ├── SYSTEM.xz
│   │   ├── SYSTEM_2.xz
│   │   ├── SYSTEM_B.LOG1.xz
│   │   ├── SYSTEM_B.LOG2.xz
│   │   ├── SYSTEM_B.xz
│   │   ├── SYSTEM_WIN_10_1709.xz
│   │   ├── UsrClass.dat.LOG1.xz
│   │   ├── UsrClass.dat.LOG2.xz
│   │   ├── UsrClass.dat.xz
│   │   ├── amcache.hve.xz
│   │   ├── corrupted_system_hive.xz
│   │   ├── ntuser_software_partial.xz
│   │   ├── transactions_NTUSER.DAT.xz
│   │   ├── transactions_ntuser.dat.log1.xz
│   │   └── transactions_ntuser.dat.log2.xz
│   ├── profiling.py
│   ├── test_packaging.py
│   ├── test_utils.py
│   ├── tests.py
│   └── validation/
│       ├── plugin_validation.md
│       ├── plugin_validation.py
│       ├── utils.py
│       ├── validation.py
│       └── validation_tests/
│           ├── __init_.py
│           ├── active_control_set_validation.py
│           ├── amcache_validation.py
│           ├── app_paths_plugin_validation.py
│           ├── backuprestore_plugin_validation.py
│           ├── bam_validation.py
│           ├── boot_entry_list_plugin_validation.py
│           ├── boot_key_plugin_validation.py
│           ├── codepage_validation.py
│           ├── computer_name_plugin_validation.py
│           ├── crash_dump_validation.py
│           ├── diag_sr_validation.py
│           ├── disable_last_access_validation.py
│           ├── disablesr_plugin_validation.py
│           ├── domain_sid_plugin_validation.py
│           ├── execution_policy_plugin_validation.py
│           ├── host_domain_name_plugin_validation.py
│           ├── image_file_execution_options_validation.py
│           ├── installed_programs_ntuser_validation.py
│           ├── installed_programs_software_plugin_validation.py
│           ├── last_logon_plugin_validation.py
│           ├── local_sid_plugin_validation.py
│           ├── lsa_packages_plugin_validation.py
│           ├── mounted_devices_plugin_validation.py
│           ├── network_data_plugin_validation.py
│           ├── network_drives_plugin_validation.py
│           ├── networklist_plugin_validation.py
│           ├── ntuser_classes_installer_plugin_validation.py
│           ├── ntuser_persistence_validation.py
│           ├── ntuser_userassist_validation.py
│           ├── pagefile_plugin_validation.py
│           ├── previous_winver_plugin_validation.py
│           ├── print_demon_plugin_validation.py
│           ├── processor_architecture_validation.py
│           ├── profile_list_plugin_validation.py
│           ├── ras_tracing_plugin_validation.py
│           ├── routes_validation.py
│           ├── safeboot_configuration_validation.py
│           ├── samparse_plugin_validation.py
│           ├── services_plugin_validation.py
│           ├── shell_bag_ntuser_plugin_validation.py
│           ├── shell_bag_usrclass_plugin_validation.py
│           ├── shimcache_validation.py
│           ├── shutdown_validation.py
│           ├── software_classes_installer_plugin_validation.py
│           ├── software_persistence_validation.py
│           ├── spp_clients_plugin_validation.py
│           ├── susclient_plugin_validation.py
│           ├── terminal_services_history_validation.py
│           ├── timezone_data2_validation.py
│           ├── timezone_data_validation.py
│           ├── typed_paths_plugin_validation.py
│           ├── typed_urls_plugin_validation.py
│           ├── uac_status_plugin_validation.py
│           ├── usb_devices_plugin_validation.py
│           ├── usbstor_plugin_validation.py
│           ├── wdigest_plugin_validation.py
│           ├── windows_defender_plugin_validation.py
│           ├── winrar_plugin_validation.py
│           ├── winscp_saved_sessions_plugin_validation.py
│           ├── winver_plugin_validation.py
│           ├── word_wheel_query_ntuser_validation.py
│           └── wsl_plugin_validation.py
├── renovate.json
└── requirements.txt
Download .txt
SYMBOL INDEX (491 symbols across 160 files)

FILE: regipy/cli.py
  function parse_header (line 30) | def parse_header(hive_path, verbose):
  function registry_dump (line 101) | def registry_dump(
  function run_plugins (line 227) | def run_plugins(hive_path, output_path, plugins, hive_type, partial_hive...
  function list_plugins (line 274) | def list_plugins():
  function reg_diff (line 301) | def reg_diff(first_hive_path, second_hive_path, output_path, verbose):
  function parse_transaction_log (line 344) | def parse_transaction_log(hive_path, primary_log_path, secondary_log_pat...

FILE: regipy/cli_utils.py
  function get_filtered_subkeys (line 15) | def get_filtered_subkeys(
  function _normalize_subkey_fields (line 62) | def _normalize_subkey_fields(field) -> str:

FILE: regipy/exceptions.py
  class RegipyException (line 1) | class RegipyException(Exception):
  class RegipyGeneralException (line 9) | class RegipyGeneralException(RegipyException):
  class RegistryValueNotFoundException (line 17) | class RegistryValueNotFoundException(RegipyException):
  class NoRegistrySubkeysException (line 21) | class NoRegistrySubkeysException(RegipyException):
  class NoRegistryValuesException (line 25) | class NoRegistryValuesException(RegipyException):
  class RegistryKeyNotFoundException (line 29) | class RegistryKeyNotFoundException(RegipyException):
  class UnidentifiedHiveException (line 33) | class UnidentifiedHiveException(RegipyException):
  class RegistryRecoveryException (line 37) | class RegistryRecoveryException(RegipyException):
  class RegistryParsingException (line 41) | class RegistryParsingException(RegipyException):

FILE: regipy/plugins/amcache/amcache.py
  class AmCachePlugin (line 45) | class AmCachePlugin(Plugin):
    method parse_amcache_file_entry (line 50) | def parse_amcache_file_entry(self, subkey):
    method run (line 93) | def run(self):

FILE: regipy/plugins/bcd/boot_entry_list.py
  function _get_element_by_type (line 30) | def _get_element_by_type(obj_key: NKRecord, datatype: int) -> Union[str,...
  class BootEntryListPlugin (line 49) | class BootEntryListPlugin(Plugin):
    method run (line 58) | def run(self) -> None:

FILE: regipy/plugins/ntuser/appkeys.py
  class AppKeysPlugin (line 17) | class AppKeysPlugin(Plugin):
    method run (line 28) | def run(self):

FILE: regipy/plugins/ntuser/classes_installer.py
  class NtuserClassesInstallerPlugin (line 12) | class NtuserClassesInstallerPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/ntuser/comdlg32.py
  function parse_pidl_mru_value (line 23) | def parse_pidl_mru_value(data: bytes) -> Optional[str]:
  class ComDlg32Plugin (line 66) | class ComDlg32Plugin(Plugin):
    method run (line 76) | def run(self):
    method _parse_open_save_mru (line 88) | def _parse_open_save_mru(self, base_path: str, mru_type: str):
    method _parse_last_visited_mru (line 132) | def _parse_last_visited_mru(self, path: str):

FILE: regipy/plugins/ntuser/installed_programs_ntuser.py
  class InstalledProgramsNTUserPlugin (line 13) | class InstalledProgramsNTUserPlugin(Plugin):
    method _get_installed_software (line 18) | def _get_installed_software(self, subkey_path):
    method run (line 40) | def run(self):

FILE: regipy/plugins/ntuser/muicache.py
  class MUICachePlugin (line 20) | class MUICachePlugin(Plugin):
    method run (line 30) | def run(self):
    method _parse_muicache (line 40) | def _parse_muicache(self, path: str) -> bool:

FILE: regipy/plugins/ntuser/network_drives.py
  class NetworkDrivesPlugin (line 13) | class NetworkDrivesPlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/ntuser/persistence.py
  class NTUserPersistencePlugin (line 29) | class NTUserPersistencePlugin(Plugin):
    method run (line 34) | def run(self):

FILE: regipy/plugins/ntuser/putty.py
  class PuTTYPlugin (line 20) | class PuTTYPlugin(Plugin):
    method run (line 34) | def run(self):
    method _parse_sessions (line 41) | def _parse_sessions(self):
    method _parse_ssh_host_keys (line 102) | def _parse_ssh_host_keys(self):
    method _parse_jumplist (line 137) | def _parse_jumplist(self):
    method _get_protocol_name (line 162) | def _get_protocol_name(protocol_id):

FILE: regipy/plugins/ntuser/recentdocs.py
  function parse_mru_value (line 18) | def parse_mru_value(data: bytes) -> Optional[str]:
  class RecentDocsPlugin (line 42) | class RecentDocsPlugin(Plugin):
    method run (line 52) | def run(self):
    method _process_recent_docs_key (line 69) | def _process_recent_docs_key(self, key, key_path: str, extension: str ...

FILE: regipy/plugins/ntuser/runmru.py
  class RunMRUPlugin (line 17) | class RunMRUPlugin(Plugin):
    method run (line 27) | def run(self):

FILE: regipy/plugins/ntuser/shellbags_ntuser.py
  class ShellBagNtuserPlugin (line 14) | class ShellBagNtuserPlugin(Plugin):
    method _parse_mru (line 20) | def _parse_mru(mru_val):
    method _get_shell_item_type (line 33) | def _get_shell_item_type(shell_item):
    method _check_known_guids (line 71) | def _check_known_guids(guid):
    method _get_entry_string (line 79) | def _get_entry_string(fwps_record):
    method _create_entry (line 87) | def _create_entry(
    method _parse_shell_item_path_segment (line 125) | def _parse_shell_item_path_segment(self, shell_item):
    method iter_sk (line 248) | def iter_sk(self, key, reg_path, codepage=DEFAULT_CODEPAGE, base_path=...
    method run (line 364) | def run(self, codepage=DEFAULT_CODEPAGE):

FILE: regipy/plugins/ntuser/sysinternals.py
  class SysinternalsPlugin (line 17) | class SysinternalsPlugin(Plugin):
    method run (line 31) | def run(self):

FILE: regipy/plugins/ntuser/tsclient.py
  class TSClientPlugin (line 16) | class TSClientPlugin(Plugin):
    method run (line 21) | def run(self):

FILE: regipy/plugins/ntuser/typed_paths.py
  class TypedPathsPlugin (line 15) | class TypedPathsPlugin(Plugin):
    method run (line 20) | def run(self):

FILE: regipy/plugins/ntuser/typed_urls.py
  class TypedUrlsPlugin (line 15) | class TypedUrlsPlugin(Plugin):
    method run (line 20) | def run(self):

FILE: regipy/plugins/ntuser/user_assist.py
  class UserAssistPlugin (line 63) | class UserAssistPlugin(Plugin):
    method run (line 68) | def run(self):

FILE: regipy/plugins/ntuser/winrar.py
  class WinRARPlugin (line 15) | class WinRARPlugin(Plugin):
    method run (line 20) | def run(self):

FILE: regipy/plugins/ntuser/winscp_saved_sessions.py
  class WinSCPSavedSessionsPlugin (line 13) | class WinSCPSavedSessionsPlugin(Plugin):
    method _get_winscp_saved_sessions (line 18) | def _get_winscp_saved_sessions(self, subkey_path):
    method run (line 40) | def run(self):

FILE: regipy/plugins/ntuser/word_wheel_query.py
  class WordWheelQueryPlugin (line 15) | class WordWheelQueryPlugin(Plugin):
    method run (line 20) | def run(self):

FILE: regipy/plugins/ntuser/wsl.py
  class WSLPlugin (line 15) | class WSLPlugin(Plugin):
    method get_wsl_info (line 20) | def get_wsl_info(self, subkey, distribs=None):
    method run (line 70) | def run(self):

FILE: regipy/plugins/plugin.py
  class Plugin (line 11) | class Plugin:
    method __init_subclass__ (line 16) | def __init_subclass__(cls):
    method __init__ (line 19) | def __init__(self, registry_hive: RegistryHive, as_json=False, trim_va...
    method can_run (line 29) | def can_run(self):
    method run (line 36) | def run(self):
    method generate_timeline_artifacts (line 42) | def generate_timeline_artifacts(self):
    method detect_anomalies (line 49) | def detect_anomalies(self):

FILE: regipy/plugins/plugin_template.py
  class TemplatePlugin (line 9) | class TemplatePlugin(Plugin):
    method can_run (line 13) | def can_run(self):
    method run (line 17) | def run(self):

FILE: regipy/plugins/sam/local_sid.py
  class LocalSidPlugin (line 18) | class LocalSidPlugin(Plugin):
    method run (line 27) | def run(self) -> None:

FILE: regipy/plugins/sam/samparse.py
  function filetime_to_datetime (line 47) | def filetime_to_datetime(filetime: int) -> Optional[str]:
  function parse_account_flags (line 61) | def parse_account_flags(flags: int) -> list:
  class SAMParsePlugin (line 70) | class SAMParsePlugin(Plugin):
    method run (line 90) | def run(self):
    method _get_rid_to_name_mapping (line 142) | def _get_rid_to_name_mapping(self) -> dict:
    method _parse_f_value (line 169) | def _parse_f_value(self, data, entry: dict):
    method _parse_v_value (line 224) | def _parse_v_value(self, data, entry: dict):

FILE: regipy/plugins/security/domain_sid.py
  class DomainSidPlugin (line 20) | class DomainSidPlugin(Plugin):
    method run (line 29) | def run(self) -> None:

FILE: regipy/plugins/software/appcompatflags.py
  class AppCompatFlagsPlugin (line 19) | class AppCompatFlagsPlugin(Plugin):
    method run (line 38) | def run(self):
    method _parse_layers (line 44) | def _parse_layers(self):
    method _parse_custom (line 77) | def _parse_custom(self):

FILE: regipy/plugins/software/appinitdlls.py
  class AppInitDLLsPlugin (line 20) | class AppInitDLLsPlugin(Plugin):
    method run (line 37) | def run(self):
    method _parse_appinit_dlls (line 43) | def _parse_appinit_dlls(self, path: str, architecture: str):

FILE: regipy/plugins/software/apppaths.py
  class AppPathsPlugin (line 18) | class AppPathsPlugin(Plugin):
    method run (line 40) | def run(self):
    method _parse_app_paths (line 46) | def _parse_app_paths(self, path: str, architecture: str):

FILE: regipy/plugins/software/classes_installer.py
  class SoftwareClassesInstallerPlugin (line 12) | class SoftwareClassesInstallerPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/software/defender.py
  class WindowsDefenderPlugin (line 20) | class WindowsDefenderPlugin(Plugin):
    method run (line 39) | def run(self):
    method _parse_defender_config (line 46) | def _parse_defender_config(self):
    method _parse_defender_policy (line 89) | def _parse_defender_policy(self):
    method _parse_exclusions (line 129) | def _parse_exclusions(self):

FILE: regipy/plugins/software/disablesr.py
  class DisableSRPlugin (line 15) | class DisableSRPlugin(Plugin):
    method can_run (line 20) | def can_run(self):
    method run (line 23) | def run(self):

FILE: regipy/plugins/software/execpolicy.py
  class ExecutionPolicyPlugin (line 23) | class ExecutionPolicyPlugin(Plugin):
    method run (line 41) | def run(self):
    method _parse_powershell_policy (line 48) | def _parse_powershell_policy(self):
    method _parse_powershell_group_policy (line 73) | def _parse_powershell_group_policy(self):
    method _parse_wsh_settings (line 99) | def _parse_wsh_settings(self):

FILE: regipy/plugins/software/image_file_execution_options.py
  class ImageFileExecutionOptions (line 12) | class ImageFileExecutionOptions(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/software/installed_programs.py
  class InstalledProgramsSoftwarePlugin (line 14) | class InstalledProgramsSoftwarePlugin(Plugin):
    method _get_installed_software (line 19) | def _get_installed_software(self, subkey_path):
    method run (line 41) | def run(self):

FILE: regipy/plugins/software/last_logon.py
  class LastLogonPlugin (line 14) | class LastLogonPlugin(Plugin):
    method run (line 19) | def run(self):

FILE: regipy/plugins/software/networklist.py
  function format_mac_address (line 28) | def format_mac_address(val) -> Optional[str]:
  function parse_network_date (line 35) | def parse_network_date(data: bytes) -> Optional[str]:
  class NetworkListPlugin (line 60) | class NetworkListPlugin(Plugin):
    method run (line 76) | def run(self):
    method _parse_profiles (line 82) | def _parse_profiles(self):
    method _parse_signatures (line 118) | def _parse_signatures(self):

FILE: regipy/plugins/software/persistence.py
  class SoftwarePersistencePlugin (line 24) | class SoftwarePersistencePlugin(Plugin):
    method run (line 29) | def run(self):

FILE: regipy/plugins/software/printdemon.py
  class PrintDemonPlugin (line 12) | class PrintDemonPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/software/profilelist.py
  class ProfileListPlugin (line 13) | class ProfileListPlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/software/pslogging.py
  class PowerShellLoggingPlugin (line 22) | class PowerShellLoggingPlugin(Plugin):
    method run (line 41) | def run(self):
    method _parse_main_policy (line 49) | def _parse_main_policy(self):
    method _parse_scriptblock_logging (line 75) | def _parse_scriptblock_logging(self):
    method _parse_module_logging (line 100) | def _parse_module_logging(self):
    method _parse_transcription (line 135) | def _parse_transcription(self):

FILE: regipy/plugins/software/spp_clients.py
  class SppClientsPlugin (line 15) | class SppClientsPlugin(Plugin):
    method can_run (line 20) | def can_run(self):
    method run (line 23) | def run(self):

FILE: regipy/plugins/software/susclient.py
  class SusclientPlugin (line 15) | class SusclientPlugin(Plugin):
    method can_run (line 20) | def can_run(self):
    method run (line 23) | def run(self):
  function get_SN (line 41) | def get_SN(data):

FILE: regipy/plugins/software/tracing.py
  class RASTracingPlugin (line 13) | class RASTracingPlugin(Plugin):
    method _get_installed_software (line 18) | def _get_installed_software(self, subkey_path):
    method run (line 29) | def run(self):

FILE: regipy/plugins/software/uac.py
  class UACStatusPlugin (line 12) | class UACStatusPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/software/winver.py
  class WinVersionPlugin (line 33) | class WinVersionPlugin(Plugin):
    method can_run (line 38) | def can_run(self):
    method run (line 41) | def run(self):

FILE: regipy/plugins/system/active_controlset.py
  class ActiveControlSetPlugin (line 12) | class ActiveControlSetPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/system/appcertdlls.py
  class AppCertDLLsPlugin (line 13) | class AppCertDLLsPlugin(Plugin):
    method run (line 31) | def run(self):

FILE: regipy/plugins/system/backuprestore.py
  class BackupRestorePlugin (line 18) | class BackupRestorePlugin(Plugin):
    method can_run (line 23) | def can_run(self):
    method run (line 26) | def run(self):

FILE: regipy/plugins/system/bam.py
  class BAMPlugin (line 15) | class BAMPlugin(Plugin):
    method run (line 20) | def run(self):

FILE: regipy/plugins/system/bootkey.py
  function _collect_bootkey (line 22) | def _collect_bootkey(lsa_key: NKRecord) -> str:
  function _descramble_bootkey (line 60) | def _descramble_bootkey(key: str) -> bytes:
  class BootKeyPlugin (line 71) | class BootKeyPlugin(Plugin):
    method run (line 81) | def run(self):

FILE: regipy/plugins/system/codepage.py
  class CodepagePlugin (line 15) | class CodepagePlugin(Plugin):
    method can_run (line 20) | def can_run(self):
    method run (line 23) | def run(self):

FILE: regipy/plugins/system/computer_name.py
  class ComputerNamePlugin (line 13) | class ComputerNamePlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/system/crash_dump.py
  class CrashDumpPlugin (line 22) | class CrashDumpPlugin(Plugin):
    method can_run (line 27) | def can_run(self):
    method run (line 30) | def run(self):

FILE: regipy/plugins/system/diag_sr.py
  class DiagSRPlugin (line 15) | class DiagSRPlugin(Plugin):
    method can_run (line 20) | def can_run(self):
    method run (line 23) | def run(self):

FILE: regipy/plugins/system/disablelastaccess.py
  class DisableLastAccessPlugin (line 21) | class DisableLastAccessPlugin(Plugin):
    method can_run (line 26) | def can_run(self):
    method run (line 29) | def run(self):

FILE: regipy/plugins/system/external/ShimCacheParser.py
  class CacheEntryNt5 (line 69) | class CacheEntryNt5:
    method __init__ (line 70) | def __init__(self, is_32_bit, data=None):
    method update (line 82) | def update(self, data):
    method size (line 95) | def size(self):
  class CacheEntryNt6 (line 103) | class CacheEntryNt6:
    method __init__ (line 104) | def __init__(self, is_32_bit, data=None):
    method update (line 118) | def update(self, data):
    method size (line 133) | def size(self):
  function convert_filetime (line 142) | def convert_filetime(dw_low_date_time, dw_high_date_time):
  function unique_list (line 154) | def unique_list(li):
  function get_shimcache_entries (line 163) | def get_shimcache_entries(cachebin, as_json=False):
  function read_win8_entries (line 244) | def read_win8_entries(bin_data, ver_magic, as_json=False):
  function read_win10_entries (line 295) | def read_win10_entries(bin_data, ver_magic, creators_update=False, as_js...
  function read_nt5_entries (line 340) | def read_nt5_entries(bin_data, entry, as_json=False):
  function read_nt6_entries (line 399) | def read_nt6_entries(bin_data, entry, as_json=False):
  function read_winxp_entries (line 431) | def read_winxp_entries(bin_data, as_json=False):
  function parse_output (line 475) | def parse_output(output):

FILE: regipy/plugins/system/host_domain_name.py
  class HostDomainNamePlugin (line 12) | class HostDomainNamePlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/system/lsa_packages.py
  class LSAPackagesPlugin (line 17) | class LSAPackagesPlugin(Plugin):
    method run (line 36) | def run(self):
    method _get_lm_compat_desc (line 97) | def _get_lm_compat_desc(level: int) -> str:

FILE: regipy/plugins/system/mountdev.py
  function parse_device_data (line 18) | def parse_device_data(data: bytes) -> dict:
  class MountedDevicesPlugin (line 85) | class MountedDevicesPlugin(Plugin):
    method run (line 102) | def run(self):

FILE: regipy/plugins/system/network_data.py
  class NetworkDataPlugin (line 14) | class NetworkDataPlugin(Plugin):
    method get_network_info (line 19) | def get_network_info(self, subkey, interfaces=None):
    method run (line 95) | def run(self):

FILE: regipy/plugins/system/pagefile.py
  class PagefilePlugin (line 17) | class PagefilePlugin(Plugin):
    method run (line 33) | def run(self):

FILE: regipy/plugins/system/pending_file_rename.py
  class PendingFileRenamePlugin (line 17) | class PendingFileRenamePlugin(Plugin):
    method run (line 36) | def run(self):
    method _parse_operations (line 60) | def _parse_operations(self, data, value_name: str) -> list:

FILE: regipy/plugins/system/previous_winver.py
  class PreviousWinVersionPlugin (line 33) | class PreviousWinVersionPlugin(Plugin):
    method can_run (line 38) | def can_run(self):
    method run (line 41) | def run(self):

FILE: regipy/plugins/system/processor_architecture.py
  class ProcessorArchitecturePlugin (line 19) | class ProcessorArchitecturePlugin(Plugin):
    method can_run (line 24) | def can_run(self):
    method run (line 27) | def run(self):

FILE: regipy/plugins/system/routes.py
  class RoutesPlugin (line 13) | class RoutesPlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/system/safeboot_configuration.py
  class SafeBootConfigurationPlugin (line 14) | class SafeBootConfigurationPlugin(Plugin):
    method _get_safeboot_entries (line 19) | def _get_safeboot_entries(self, subkey_path):
    method run (line 41) | def run(self):

FILE: regipy/plugins/system/services.py
  class ServicesPlugin (line 17) | class ServicesPlugin(Plugin):
    method run (line 22) | def run(self):

FILE: regipy/plugins/system/shares.py
  class SharesPlugin (line 17) | class SharesPlugin(Plugin):
    method run (line 33) | def run(self):
    method _parse_shares (line 45) | def _parse_shares(self, shares_key, key_path: str):
    method _get_share_type (line 80) | def _get_share_type(type_value: int) -> str:

FILE: regipy/plugins/system/shimcache.py
  class ShimCachePlugin (line 12) | class ShimCachePlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/system/shutdown.py
  class ShutdownPlugin (line 13) | class ShutdownPlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/system/timezone_data.py
  class TimezoneDataPlugin (line 12) | class TimezoneDataPlugin(Plugin):
    method run (line 17) | def run(self):

FILE: regipy/plugins/system/timezone_data2.py
  class TimezoneDataPlugin2 (line 14) | class TimezoneDataPlugin2(Plugin):
    method run (line 19) | def run(self):

FILE: regipy/plugins/system/usb_devices.py
  function strip_resource_ref (line 19) | def strip_resource_ref(val) -> Optional[str]:
  class USBDevicesPlugin (line 26) | class USBDevicesPlugin(Plugin):
    method run (line 40) | def run(self):
    method _parse_usb_key (line 52) | def _parse_usb_key(self, usb_key, base_path: str):

FILE: regipy/plugins/system/usbstor.py
  class USBSTORPlugin (line 21) | class USBSTORPlugin(Plugin):
    method run (line 26) | def run(self):

FILE: regipy/plugins/system/wdigest.py
  class WDIGESTPlugin (line 13) | class WDIGESTPlugin(Plugin):
    method run (line 18) | def run(self):

FILE: regipy/plugins/usrclass/shellbags_usrclass.py
  class ShellBagUsrclassPlugin (line 15) | class ShellBagUsrclassPlugin(Plugin):
    method _parse_mru (line 21) | def _parse_mru(mru_val):
    method _get_shell_item_type (line 34) | def _get_shell_item_type(shell_item):
    method _check_known_guids (line 72) | def _check_known_guids(guid):
    method _get_entry_string (line 80) | def _get_entry_string(fwps_record):
    method _parse_shell_item_path_segment (line 88) | def _parse_shell_item_path_segment(self, shell_item):
    method iter_sk (line 215) | def iter_sk(self, key, reg_path, codepage=DEFAULT_CODEPAGE, base_path=...
    method run (line 306) | def run(self, codepage=DEFAULT_CODEPAGE):

FILE: regipy/plugins/utils.py
  function extract_values (line 23) | def extract_values(
  function dump_hive_to_json (line 56) | def dump_hive_to_json(
  function run_relevant_plugins (line 88) | def run_relevant_plugins(

FILE: regipy/plugins/validation_status.py
  function is_plugin_validated (line 28) | def is_plugin_validated(plugin_name: str) -> bool:
  function get_validated_plugins (line 33) | def get_validated_plugins() -> set[str]:
  function get_unvalidated_plugins (line 38) | def get_unvalidated_plugins(plugin_names: list[str]) -> list[str]:
  function warn_unvalidated_plugin (line 43) | def warn_unvalidated_plugin(plugin_name: str) -> None:

FILE: regipy/recovery.py
  function _parse_hvle_block (line 16) | def _parse_hvle_block(hive_path, transaction_log_stream, log_size, expec...
  function _parse_dirt_block (line 77) | def _parse_dirt_block(hive_path, transaction_log, hbins_data_size):
  function _parse_transaction_log (line 121) | def _parse_transaction_log(registry_hive, hive_path, transaction_log_path):
  function apply_transaction_logs (line 149) | def apply_transaction_logs(

FILE: regipy/regdiff.py
  function get_subkeys_and_timestamps (line 12) | def get_subkeys_and_timestamps(registry_hive):
  function get_values_from_tuples (line 21) | def get_values_from_tuples(value_tuples, value_name_list):
  function get_timestamp_for_subkeys (line 27) | def get_timestamp_for_subkeys(registry_hive, subkey_list):
  function _get_name_value_tuples (line 37) | def _get_name_value_tuples(subkey: NKRecord) -> set[tuple[str, Any]]:
  function compare_hives (line 55) | def compare_hives(first_hive_path, second_hive_path, verbose=False):

FILE: regipy/registry.py
  class Cell (line 63) | class Cell:
  class VKRecord (line 74) | class VKRecord:
  class LIRecord (line 87) | class LIRecord:
  class Value (line 92) | class Value:
  class Subkey (line 100) | class Subkey:
  class RIRecord (line 111) | class RIRecord:
    method __init__ (line 115) | def __init__(self, stream):
  class RegistryHive (line 119) | class RegistryHive:
    method __init__ (line 122) | def __init__(self, hive_path, hive_type=None, partial_hive_path=None):
    method recurse_subkeys (line 164) | def recurse_subkeys(
    method get_hbin_at_offset (line 250) | def get_hbin_at_offset(self, offset=0):
    method get_key (line 259) | def get_key(self, key_path):
    method get_control_sets (line 297) | def get_control_sets(self, registry_path):
  class HBin (line 315) | class HBin:
    method __init__ (line 316) | def __init__(self, stream):
    method iter_cells (line 323) | def iter_cells(self, stream):
  class NKRecord (line 344) | class NKRecord:
    method __init__ (line 349) | def __init__(self, cell, stream):
    method get_subkey (line 367) | def get_subkey(self, key_name, raise_on_missing=True):
    method iter_subkeys (line 382) | def iter_subkeys(self):
    method _parse_subkeys (line 414) | def _parse_subkeys(stream, signature=None):
    method read_value (line 442) | def read_value(vk, stream):
    method _parse_indirect_block (line 460) | def _parse_indirect_block(stream, value):
    method iter_values (line 481) | def iter_values(self, as_json=False, max_len=MAX_LEN, trim_values=True):
    method get_value (line 602) | def get_value(
    method get_values (line 627) | def get_values(self, as_json=False, trim_values=False):
    method get_security_key_info (line 630) | def get_security_key_info(self):
    method get_class_name (line 661) | def get_class_name(self) -> str:
    method __dict__ (line 675) | def __dict__(self):

FILE: regipy/security_utils.py
  function convert_sid (line 8) | def convert_sid(sid: Any, strip_rid: bool = False) -> str:
  function get_acls (line 15) | def get_acls(s):

FILE: regipy/utils.py
  function calculate_sha1 (line 42) | def calculate_sha1(file_path):
  function calculate_xor32_checksum (line 53) | def calculate_xor32_checksum(b: bytes) -> int:
  function boomerang_stream (line 69) | def boomerang_stream(stream: TextIOWrapper) -> Generator[TextIOWrapper, ...
  function convert_filetime (line 79) | def convert_filetime(dw_low_date_time, dw_high_date_time):
  function convert_filetime2 (line 96) | def convert_filetime2(dte):
  function convert_wintime (line 109) | def convert_wintime(wintime: int, as_json=False) -> Union[dt.datetime, s...
  function get_subkey_values_from_list (line 126) | def get_subkey_values_from_list(registry_hive, entries_list, as_json=Fal...
  function identify_hive_type (line 156) | def identify_hive_type(name: str) -> str:
  function try_decode_binary (line 178) | def try_decode_binary(data, as_json=False, max_len=MAX_LEN, trim_values=...
  function _setup_logging (line 194) | def _setup_logging(verbose):
  function trim_registry_data_for_error_msg (line 201) | def trim_registry_data_for_error_msg(s: str, max_len: int = MAX_LEN_ERR_...

FILE: regipy_mcp_server/server.py
  function _load_hives_from_directory (line 35) | def _load_hives_from_directory(directory: str) -> dict[str, RegistryHive]:
  function _initialize_hives (line 72) | def _initialize_hives(hive_dir: Optional[str] = None):
  function _get_hives_by_type (line 95) | def _get_hives_by_type(hive_type: str) -> list[tuple[str, RegistryHive]]:
  function _serialize_datetime (line 100) | def _serialize_datetime(obj):
  function _serialize_plugin_results (line 107) | def _serialize_plugin_results(results):
  function set_hive_directory (line 120) | def set_hive_directory(directory: str) -> str:
  function list_available_hives (line 156) | def list_available_hives() -> str:
  function list_available_plugins (line 185) | def list_available_plugins(hive_type: Optional[str] = None) -> str:
  function run_plugin (line 237) | def run_plugin(plugin_name: str) -> dict:
  function run_all_plugins_for_hive (line 293) | def run_all_plugins_for_hive(hive_type: str) -> dict:
  function list_relevant_plugins (line 327) | def list_relevant_plugins(question: str) -> dict:
  function get_registry_key (line 370) | def get_registry_key(key_path: str, hive_type: Optional[str] = None) -> ...

FILE: regipy_tests/cli_tests.py
  function test_cli_registry_parse_header (line 9) | def test_cli_registry_parse_header(ntuser_hive):
  function test_cli_registry_dump (line 16) | def test_cli_registry_dump(ntuser_hive):
  function test_cli_run_plugins (line 55) | def test_cli_run_plugins(ntuser_hive):

FILE: regipy_tests/conftest.py
  function extract_lzma (line 9) | def extract_lzma(path):
  function temp_output_file (line 17) | def temp_output_file():
  function test_data_dir (line 24) | def test_data_dir():
  function ntuser_hive (line 29) | def ntuser_hive(test_data_dir):
  function software_hive (line 36) | def software_hive(test_data_dir):
  function system_hive (line 43) | def system_hive(test_data_dir):
  function sam_hive (line 50) | def sam_hive(test_data_dir):
  function security_hive (line 57) | def security_hive(test_data_dir):
  function amcache_hive (line 64) | def amcache_hive(test_data_dir):
  function bcd_hive (line 71) | def bcd_hive(test_data_dir):
  function second_hive_path (line 78) | def second_hive_path(test_data_dir):
  function transaction_ntuser (line 85) | def transaction_ntuser(test_data_dir):
  function transaction_log (line 92) | def transaction_log(test_data_dir):
  function transaction_system (line 99) | def transaction_system(test_data_dir):
  function system_tr_log_1 (line 106) | def system_tr_log_1(test_data_dir):
  function system_tr_log_2 (line 113) | def system_tr_log_2(test_data_dir):
  function transaction_usrclass (line 120) | def transaction_usrclass(test_data_dir):
  function usrclass_tr_log_1 (line 127) | def usrclass_tr_log_1(test_data_dir):
  function usrclass_tr_log_2 (line 134) | def usrclass_tr_log_2(test_data_dir):
  function ntuser_software_partial (line 141) | def ntuser_software_partial(test_data_dir):
  function corrupted_system_hive (line 148) | def corrupted_system_hive(test_data_dir):
  function system_devprop (line 155) | def system_devprop(test_data_dir):
  function ntuser_hive_2 (line 162) | def ntuser_hive_2(test_data_dir):
  function shellbags_ntuser (line 169) | def shellbags_ntuser(test_data_dir):
  function system_hive_with_filetime (line 176) | def system_hive_with_filetime(test_data_dir):

FILE: regipy_tests/profiling.py
  function profiling (line 20) | def profiling():
  function get_file_from_tests (line 33) | def get_file_from_tests(file_name):

FILE: regipy_tests/test_packaging.py
  function test_all_packages_included_in_pyproject (line 17) | def test_all_packages_included_in_pyproject():
  function test_all_packages_importable (line 50) | def test_all_packages_importable():

FILE: regipy_tests/test_utils.py
  function _make_mock_value (line 8) | def _make_mock_value(name, value):
  function _make_mock_key (line 16) | def _make_mock_key(values):
  class TestExtractValues (line 23) | class TestExtractValues:
    method test_simple_rename (line 26) | def test_simple_rename(self):
    method test_multiple_simple_renames (line 35) | def test_multiple_simple_renames(self):
    method test_callable_converter (line 59) | def test_callable_converter(self):
    method test_callable_converter_false (line 74) | def test_callable_converter_false(self):
    method test_lookup_converter (line 89) | def test_lookup_converter(self):
    method test_lookup_converter_unknown (line 105) | def test_lookup_converter_unknown(self):
    method test_unmapped_values_ignored (line 121) | def test_unmapped_values_ignored(self):
    method test_preserves_existing_entry_values (line 136) | def test_preserves_existing_entry_values(self):
    method test_empty_value_map (line 149) | def test_empty_value_map(self):
    method test_empty_registry_key (line 158) | def test_empty_registry_key(self):
    method test_mixed_simple_and_callable (line 167) | def test_mixed_simple_and_callable(self):
    method test_converter_with_bytes (line 194) | def test_converter_with_bytes(self):
    method test_converter_returns_none (line 215) | def test_converter_returns_none(self):
    method test_converter_with_integer_values (line 230) | def test_converter_with_integer_values(self):

FILE: regipy_tests/tests.py
  function test_parse_header (line 14) | def test_parse_header(ntuser_hive):
  function test_parse_root_key (line 30) | def test_parse_root_key(ntuser_hive):
  function test_find_keys_ntuser (line 68) | def test_find_keys_ntuser(ntuser_hive):
  function test_find_keys_partial_ntuser_hive (line 80) | def test_find_keys_partial_ntuser_hive(ntuser_software_partial):
  function test_regdiff (line 96) | def test_regdiff(ntuser_hive, second_hive_path):
  function test_ntuser_emojis (line 103) | def test_ntuser_emojis(transaction_ntuser):
  function test_recurse_ntuser (line 111) | def test_recurse_ntuser(ntuser_hive):
  function test_recurse_partial_ntuser (line 147) | def test_recurse_partial_ntuser(ntuser_software_partial):
  function test_recurse_ntuser_without_fetching_values (line 158) | def test_recurse_ntuser_without_fetching_values(ntuser_hive):
  function test_recurse_amcache (line 166) | def test_recurse_amcache(amcache_hive):
  function test_ntuser_apply_transaction_logs (line 200) | def test_ntuser_apply_transaction_logs(transaction_ntuser, transaction_l...
  function test_system_apply_transaction_logs (line 213) | def test_system_apply_transaction_logs(transaction_system, system_tr_log...
  function test_system_hive_devprop_structure (line 229) | def test_system_hive_devprop_structure(system_devprop):
  function test_system_apply_transaction_logs_2 (line 241) | def test_system_apply_transaction_logs_2(transaction_usrclass, usrclass_...
  function test_hive_serialization (line 257) | def test_hive_serialization(ntuser_hive, temp_output_file):
  function test_get_key (line 268) | def test_get_key(software_hive):
  function test_get_subkey_errors (line 278) | def test_get_subkey_errors(software_hive):
  function test_parse_security_info (line 291) | def test_parse_security_info(ntuser_hive):
  function test_parse_filetime_value (line 332) | def test_parse_filetime_value(system_hive_with_filetime):
  function test_ntuser_filtered_timestamps_do_not_fetch_values (line 342) | def test_ntuser_filtered_timestamps_do_not_fetch_values(ntuser_hive):
  function test_ntuser_filtered_timestamps_fetch_values (line 357) | def test_ntuser_filtered_timestamps_fetch_values(ntuser_hive):
  function test_ntuser_filtered_timestamps_no_filter (line 374) | def test_ntuser_filtered_timestamps_no_filter(ntuser_hive):

FILE: regipy_tests/validation/plugin_validation.py
  class PluginValidationCaseFailureException (line 37) | class PluginValidationCaseFailureException(Exception):
  function load_hive (line 46) | def load_hive(hive_file_name):
  function validate_case (line 52) | def validate_case(plugin_validation_case: ValidationCase, registry_hive:...
  function run_validations_for_hive_file (line 69) | def run_validations_for_hive_file(hive_file_name, validation_cases) -> l...
  function main (line 77) | def main():

FILE: regipy_tests/validation/utils.py
  function extract_lzma (line 5) | def extract_lzma(path):

FILE: regipy_tests/validation/validation.py
  class ValidationResult (line 11) | class ValidationResult:
  class ValidationCase (line 19) | class ValidationCase:
    method __init_subclass__ (line 42) | def __init_subclass__(cls):
    method __init__ (line 45) | def __init__(self, input_hive: RegistryHive) -> None:
    method validate (line 48) | def validate(self):
    method debug (line 87) | def debug(self):

FILE: regipy_tests/validation/validation_tests/active_control_set_validation.py
  class ActiveControlSetPluginValidationCase (line 5) | class ActiveControlSetPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/amcache_validation.py
  class AmCachePluginValidationCase (line 5) | class AmCachePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/app_paths_plugin_validation.py
  class AppPathsPluginValidationCase (line 5) | class AppPathsPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/backuprestore_plugin_validation.py
  function test_backup_restore_plugin_output (line 5) | def test_backup_restore_plugin_output(c: ValidationCase):
  class BackupRestorePluginValidationCase (line 42) | class BackupRestorePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/bam_validation.py
  class BamValidationCase (line 5) | class BamValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/boot_entry_list_plugin_validation.py
  class BootEntryListPluginValidationCase (line 5) | class BootEntryListPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/boot_key_plugin_validation.py
  class BootKeyPluginValidationCase (line 5) | class BootKeyPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/codepage_validation.py
  class CodepagePluginValidationCase (line 5) | class CodepagePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/computer_name_plugin_validation.py
  class ComputerNamePluginValidationCase (line 5) | class ComputerNamePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/crash_dump_validation.py
  class CrashDumpPluginValidationCase (line 5) | class CrashDumpPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/diag_sr_validation.py
  class DiagSRPluginValidationCase (line 5) | class DiagSRPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/disable_last_access_validation.py
  class DisableLastAccessPluginValidationCase (line 5) | class DisableLastAccessPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/disablesr_plugin_validation.py
  class DisableSRPluginValidationCase (line 5) | class DisableSRPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/domain_sid_plugin_validation.py
  class DomainSidPluginValidationCase (line 5) | class DomainSidPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/execution_policy_plugin_validation.py
  class ExecutionPolicyPluginValidationCase (line 5) | class ExecutionPolicyPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/host_domain_name_plugin_validation.py
  class HostDomainNamePluginValidationCase (line 5) | class HostDomainNamePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/image_file_execution_options_validation.py
  class ImageFileExecutionOptionsValidationCase (line 7) | class ImageFileExecutionOptionsValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/installed_programs_ntuser_validation.py
  class InstalledProgramsNTUserPluginValidationCase (line 7) | class InstalledProgramsNTUserPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/installed_programs_software_plugin_validation.py
  class InstalledProgramsSoftwarePluginValidationCase (line 5) | class InstalledProgramsSoftwarePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/last_logon_plugin_validation.py
  class LastLogonPluginValidationCase (line 5) | class LastLogonPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/local_sid_plugin_validation.py
  class LocalSidPluginValidationCase (line 5) | class LocalSidPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/lsa_packages_plugin_validation.py
  class LSAPackagesPluginValidationCase (line 5) | class LSAPackagesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/mounted_devices_plugin_validation.py
  class MountedDevicesPluginValidationCase (line 5) | class MountedDevicesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/network_data_plugin_validation.py
  class NetworkDataPluginValidationCase (line 5) | class NetworkDataPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/network_drives_plugin_validation.py
  class NetworkDrivesPluginValidationCase (line 5) | class NetworkDrivesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/networklist_plugin_validation.py
  class NetworkListPluginValidationCase (line 5) | class NetworkListPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/ntuser_classes_installer_plugin_validation.py
  class NtuserClassesInstallerPluginValidationCase (line 5) | class NtuserClassesInstallerPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/ntuser_persistence_validation.py
  class NTUserPersistenceValidationCase (line 5) | class NTUserPersistenceValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/ntuser_userassist_validation.py
  class NTUserUserAssistValidationCase (line 5) | class NTUserUserAssistValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/pagefile_plugin_validation.py
  class PagefilePluginValidationCase (line 5) | class PagefilePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/previous_winver_plugin_validation.py
  class PreviousWinVersionPluginValidationCase (line 5) | class PreviousWinVersionPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/print_demon_plugin_validation.py
  class PrintDemonPluginValidationCase (line 5) | class PrintDemonPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/processor_architecture_validation.py
  class ProcessorArchitecturePluginValidationCase (line 5) | class ProcessorArchitecturePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/profile_list_plugin_validation.py
  class ProfileListPluginValidationCase (line 5) | class ProfileListPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/ras_tracing_plugin_validation.py
  class RASTracingPluginValidationCase (line 5) | class RASTracingPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/routes_validation.py
  class RoutesPluginValidationCase (line 5) | class RoutesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/safeboot_configuration_validation.py
  function test_safeboot_config_result (line 5) | def test_safeboot_config_result(c: ValidationCase):
  class SafeBootConfigurationPluginValidationCase (line 193) | class SafeBootConfigurationPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/samparse_plugin_validation.py
  class SAMParsePluginValidationCase (line 5) | class SAMParsePluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/services_plugin_validation.py
  function test_service (line 5) | def test_service(c: ValidationCase):
  class ServicesPluginValidationCase (line 45) | class ServicesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/shell_bag_ntuser_plugin_validation.py
  class ShellBagNtuserPluginValidationCase (line 7) | class ShellBagNtuserPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/shell_bag_usrclass_plugin_validation.py
  class ShellBagUsrclassPluginValidationCase (line 5) | class ShellBagUsrclassPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/shimcache_validation.py
  class AmCacheValidationCase (line 5) | class AmCacheValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/shutdown_validation.py
  class ShutdownPluginValidationCase (line 5) | class ShutdownPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/software_classes_installer_plugin_validation.py
  function test_no_hidden_entries (line 5) | def test_no_hidden_entries(c: ValidationCase):
  class SoftwareClassesInstallerPluginValidationCase (line 9) | class SoftwareClassesInstallerPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/software_persistence_validation.py
  class SoftwarePersistenceValidationCase (line 5) | class SoftwarePersistenceValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/spp_clients_plugin_validation.py
  class SppClientsPluginValidationCase (line 5) | class SppClientsPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/susclient_plugin_validation.py
  class SusclientPluginValidationCase (line 5) | class SusclientPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/terminal_services_history_validation.py
  class TSClientPluginValidationCase (line 5) | class TSClientPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/timezone_data2_validation.py
  function test_tz2_plugin_output (line 5) | def test_tz2_plugin_output(c: ValidationCase):
  class TimezoneDataPlugin2ValidationCase (line 22) | class TimezoneDataPlugin2ValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/timezone_data_validation.py
  function test_timezone_data (line 5) | def test_timezone_data(c: ValidationCase):
  class TimezoneDataPluginValidationCase (line 26) | class TimezoneDataPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/typed_paths_plugin_validation.py
  class TypedPathsPluginValidationCase (line 5) | class TypedPathsPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/typed_urls_plugin_validation.py
  class TypedUrlsPluginValidationCase (line 5) | class TypedUrlsPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/uac_status_plugin_validation.py
  class UACStatusPluginValidationCase (line 5) | class UACStatusPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/usb_devices_plugin_validation.py
  class USBDevicesPluginValidationCase (line 5) | class USBDevicesPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/usbstor_plugin_validation.py
  class USBSTORPluginValidationCase (line 5) | class USBSTORPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/wdigest_plugin_validation.py
  class WDIGESTPluginValidationCase (line 5) | class WDIGESTPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/windows_defender_plugin_validation.py
  class WindowsDefenderPluginValidationCase (line 5) | class WindowsDefenderPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/winrar_plugin_validation.py
  class WinRARPluginValidationCase (line 5) | class WinRARPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/winscp_saved_sessions_plugin_validation.py
  class WinSCPSavedSessionsPluginValidationCase (line 5) | class WinSCPSavedSessionsPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/winver_plugin_validation.py
  class WinVersionPluginValidationCase (line 5) | class WinVersionPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/word_wheel_query_ntuser_validation.py
  class WordWheelQueryPluginValidationCase (line 5) | class WordWheelQueryPluginValidationCase(ValidationCase):

FILE: regipy_tests/validation/validation_tests/wsl_plugin_validation.py
  class WSLPluginValidationCase (line 5) | class WSLPluginValidationCase(ValidationCase):
Condensed preview — 223 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (604K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 361,
    "preview": "version: 2\nupdates:\n  # Python dependencies\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 3654,
    "preview": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  lint:\n    runs-on: ubuntu-l"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 2159,
    "preview": "name: Publish to PyPI\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: write  # Needed to attach assets "
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "chars": 1454,
    "preview": "name: Scorecard supply-chain security\n\non:\n  # For Branch-Protection check. Only the default branch is supported.\n  bran"
  },
  {
    "path": ".gitignore",
    "chars": 1249,
    "preview": "*.ipynb\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n.idea\n.vscode\n\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 625,
    "preview": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.5.0\n    hooks:\n      - id: trailing-whitespa"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 3293,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CLAUDE.md",
    "chars": 10862,
    "preview": "# CLAUDE.md - regipy\n\n> OS-independent Python library for parsing offline Windows registry hives\n\n## Project Overview\n\nr"
  },
  {
    "path": "LICENSE",
    "chars": 1056,
    "preview": "MIT License\n\nCopyright (c) 2019\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
  },
  {
    "path": "MANIFEST.in",
    "chars": 109,
    "preview": "# Include the README\ninclude *.md\n\n# Include the license file\ninclude LICENSE.txt\n\nrecursive-include docs *\n\n"
  },
  {
    "path": "README.md",
    "chars": 11457,
    "preview": "# regipy\n\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mkorman90/regipy/badge)](https://"
  },
  {
    "path": "SECURITY.md",
    "chars": 906,
    "preview": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 6.x.x   | ✅         |\n| < 6."
  },
  {
    "path": "docs/PLUGINS.md",
    "chars": 794,
    "preview": "## regipy Plugins\n\n* The plugin system is a robust and extensive feature that auto-detects the hive type and execute the"
  },
  {
    "path": "docs/README.rst",
    "chars": 7063,
    "preview": "regipy\n==========\nRegipy is a python library for parsing offline registry hives!\n\n**Requires Python 3.9 or higher.**\n\nFe"
  },
  {
    "path": "pyproject.toml",
    "chars": 4052,
    "preview": "[build-system]\nrequires = [\"setuptools>=61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"regip"
  },
  {
    "path": "regipy/__init__.py",
    "chars": 88,
    "preview": "from .registry import *  # noqa: F401, F403\n\n__title__ = \"regipy\"\n__version__ = \"6.2.1\"\n"
  },
  {
    "path": "regipy/cli.py",
    "chars": 11666,
    "preview": "import csv\nimport json\nimport logging\nimport os\nimport time\nfrom dataclasses import asdict\n\nimport click\nfrom tabulate i"
  },
  {
    "path": "regipy/cli_utils.py",
    "chars": 2482,
    "preview": "import binascii\nimport datetime as dt\nimport logging\nfrom collections.abc import Iterator\n\nimport pytz\nfrom click import"
  },
  {
    "path": "regipy/constants.py",
    "chars": 27230,
    "preview": "# ShellBags Known GUIDs\nKNOWN_GUIDS = {\n    \"008ca0b1-55b4-4c56-b8a8-4de4b299d3be\": \"Account Pictures\",\n    \"00bcfc5a-ed"
  },
  {
    "path": "regipy/exceptions.py",
    "chars": 741,
    "preview": "class RegipyException(Exception):\n    \"\"\"\n    This is the parent exception for all regipy exceptions\n    \"\"\"\n\n    pass\n\n"
  },
  {
    "path": "regipy/hive_types.py",
    "chars": 548,
    "preview": "NTUSER_HIVE_TYPE = \"ntuser\"\nSYSTEM_HIVE_TYPE = \"system\"\nAMCACHE_HIVE_TYPE = \"amcache\"\nSOFTWARE_HIVE_TYPE = \"software\"\nSA"
  },
  {
    "path": "regipy/plugins/__init__.py",
    "chars": 3866,
    "preview": "# flake8: noqa\nfrom .amcache.amcache import AmCachePlugin\nfrom .bcd.boot_entry_list import BootEntryListPlugin\nfrom .ntu"
  },
  {
    "path": "regipy/plugins/amcache/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/amcache/amcache.py",
    "chars": 3883,
    "preview": "import logging\n\nfrom inflection import underscore\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regip"
  },
  {
    "path": "regipy/plugins/bcd/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/bcd/boot_entry_list.py",
    "chars": 3352,
    "preview": "\"\"\"\nWindows Boot Configuration Data (BCD) boot entry list plugin\n\"\"\"\n\nimport logging\nimport uuid\nfrom typing import Unio"
  },
  {
    "path": "regipy/plugins/ntuser/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/ntuser/appkeys.py",
    "chars": 1959,
    "preview": "\"\"\"\nAppKeys plugin - Parses application-specific keyboard shortcuts\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import R"
  },
  {
    "path": "regipy/plugins/ntuser/classes_installer.py",
    "chars": 1209,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import NTUSER_HI"
  },
  {
    "path": "regipy/plugins/ntuser/comdlg32.py",
    "chars": 5832,
    "preview": "\"\"\"\nComDlg32 plugin - Parses Open/Save dialog history (OpenSavePidlMRU, OpenSaveMRU)\n\"\"\"\n\nimport logging\nfrom typing imp"
  },
  {
    "path": "regipy/plugins/ntuser/installed_programs_ntuser.py",
    "chars": 1457,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regi"
  },
  {
    "path": "regipy/plugins/ntuser/muicache.py",
    "chars": 2394,
    "preview": "\"\"\"\nMUICache plugin - Parses MUI Cache entries (application display names)\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions i"
  },
  {
    "path": "regipy/plugins/ntuser/network_drives.py",
    "chars": 1143,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/ntuser/persistence.py",
    "chars": 1824,
    "preview": "import logging\n\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.util"
  },
  {
    "path": "regipy/plugins/ntuser/putty.py",
    "chars": 6005,
    "preview": "\"\"\"\nPuTTY plugin - Parses PuTTY SSH client configuration and session history\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions"
  },
  {
    "path": "regipy/plugins/ntuser/recentdocs.py",
    "chars": 3582,
    "preview": "\"\"\"\nRecentDocs plugin - Parses recently opened documents from the registry\n\"\"\"\n\nimport logging\nfrom typing import Option"
  },
  {
    "path": "regipy/plugins/ntuser/runmru.py",
    "chars": 2361,
    "preview": "\"\"\"\nRunMRU plugin - Parses Run dialog history\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundExce"
  },
  {
    "path": "regipy/plugins/ntuser/shellbags_ntuser.py",
    "chars": 16141,
    "preview": "import logging\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE"
  },
  {
    "path": "regipy/plugins/ntuser/sysinternals.py",
    "chars": 1813,
    "preview": "\"\"\"\nSysinternals plugin - Parses Sysinternals tools EULA acceptance records\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions "
  },
  {
    "path": "regipy/plugins/ntuser/tsclient.py",
    "chars": 1098,
    "preview": "import logging\n\nfrom regipy import (\n    NoRegistrySubkeysException,\n    RegistryKeyNotFoundException,\n    convert_winti"
  },
  {
    "path": "regipy/plugins/ntuser/typed_paths.py",
    "chars": 1008,
    "preview": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom"
  },
  {
    "path": "regipy/plugins/ntuser/typed_urls.py",
    "chars": 986,
    "preview": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom"
  },
  {
    "path": "regipy/plugins/ntuser/user_assist.py",
    "chars": 5155,
    "preview": "import codecs\nimport logging\n\nfrom construct import Bytes, Const, ConstError, Int32ul, Int64ul, Struct\n\nfrom regipy.exce"
  },
  {
    "path": "regipy/plugins/ntuser/winrar.py",
    "chars": 2822,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/ntuser/winscp_saved_sessions.py",
    "chars": 1460,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regi"
  },
  {
    "path": "regipy/plugins/ntuser/word_wheel_query.py",
    "chars": 1578,
    "preview": "import logging\n\nfrom construct import CString, GreedyRange, Int32ul\n\nfrom regipy.exceptions import RegistryKeyNotFoundEx"
  },
  {
    "path": "regipy/plugins/ntuser/wsl.py",
    "chars": 3935,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/plugin.py",
    "chars": 1339,
    "preview": "import logging\nfrom typing import Any\n\nfrom regipy.registry import RegistryHive\n\nPLUGINS = set()\n\nlogger = logging.getLo"
  },
  {
    "path": "regipy/plugins/plugin_template.py",
    "chars": 535,
    "preview": "import logging\n\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = loggin"
  },
  {
    "path": "regipy/plugins/sam/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/sam/local_sid.py",
    "chars": 1336,
    "preview": "\"\"\"\nWindows machine local SID extractor plugin\n\"\"\"\n\nimport logging\n\nfrom regipy.hive_types import SAM_HIVE_TYPE\nfrom reg"
  },
  {
    "path": "regipy/plugins/sam/samparse.py",
    "chars": 12449,
    "preview": "\"\"\"\nSAM Parse plugin - Parses user account information from SAM hive\n\"\"\"\n\nimport contextlib\nimport logging\nimport struct"
  },
  {
    "path": "regipy/plugins/security/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/security/domain_sid.py",
    "chars": 2067,
    "preview": "\"\"\"\nWindows machine domain name and SID extractor plugin\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom regipy.hi"
  },
  {
    "path": "regipy/plugins/software/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/software/appcompatflags.py",
    "chars": 3833,
    "preview": "\"\"\"\nAppCompatFlags plugin - Parses application compatibility flags and layers\n\"\"\"\n\nimport logging\n\nfrom regipy.exception"
  },
  {
    "path": "regipy/plugins/software/appinitdlls.py",
    "chars": 2539,
    "preview": "\"\"\"\nAppInit_DLLs plugin - Parses persistence via AppInit_DLLs\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import Registr"
  },
  {
    "path": "regipy/plugins/software/apppaths.py",
    "chars": 2729,
    "preview": "\"\"\"\nApp Paths plugin - Parses application paths registry entries\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import Regi"
  },
  {
    "path": "regipy/plugins/software/classes_installer.py",
    "chars": 1192,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_"
  },
  {
    "path": "regipy/plugins/software/defender.py",
    "chars": 5588,
    "preview": "\"\"\"\nWindows Defender plugin - Parses Windows Defender configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import R"
  },
  {
    "path": "regipy/plugins/software/disablesr.py",
    "chars": 1193,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_T"
  },
  {
    "path": "regipy/plugins/software/execpolicy.py",
    "chars": 4044,
    "preview": "\"\"\"\nExecution Policy plugin - Parses PowerShell and script execution policies\n\"\"\"\n\nimport logging\n\nfrom regipy.exception"
  },
  {
    "path": "regipy/plugins/software/image_file_execution_options.py",
    "chars": 1141,
    "preview": "import logging\n\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.ut"
  },
  {
    "path": "regipy/plugins/software/installed_programs.py",
    "chars": 1608,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom re"
  },
  {
    "path": "regipy/plugins/software/last_logon.py",
    "chars": 980,
    "preview": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom"
  },
  {
    "path": "regipy/plugins/software/networklist.py",
    "chars": 5520,
    "preview": "\"\"\"\nNetworkList plugin - Parses network connection history\n\"\"\"\n\nimport logging\nimport struct\nfrom datetime import dateti"
  },
  {
    "path": "regipy/plugins/software/persistence.py",
    "chars": 1221,
    "preview": "import logging\n\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.ut"
  },
  {
    "path": "regipy/plugins/software/printdemon.py",
    "chars": 1142,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_"
  },
  {
    "path": "regipy/plugins/software/profilelist.py",
    "chars": 1725,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_T"
  },
  {
    "path": "regipy/plugins/software/pslogging.py",
    "chars": 5120,
    "preview": "\"\"\"\nPowerShell Logging plugin - Parses PowerShell logging configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions impo"
  },
  {
    "path": "regipy/plugins/software/spp_clients.py",
    "chars": 1328,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_T"
  },
  {
    "path": "regipy/plugins/software/susclient.py",
    "chars": 1480,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_T"
  },
  {
    "path": "regipy/plugins/software/tracing.py",
    "chars": 1049,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_"
  },
  {
    "path": "regipy/plugins/software/uac.py",
    "chars": 2244,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_"
  },
  {
    "path": "regipy/plugins/software/winver.py",
    "chars": 1736,
    "preview": "import logging\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom "
  },
  {
    "path": "regipy/plugins/system/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/system/active_controlset.py",
    "chars": 597,
    "preview": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin"
  },
  {
    "path": "regipy/plugins/system/appcertdlls.py",
    "chars": 1755,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/backuprestore.py",
    "chars": 1438,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/bam.py",
    "chars": 2531,
    "preview": "import logging\n\nfrom construct import Int64ul\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hi"
  },
  {
    "path": "regipy/plugins/system/bootkey.py",
    "chars": 2619,
    "preview": "\"\"\"\nThe boot key is an encryption key that is stored in\nthe Windows SYSTEM registry hive.\n\nThis key is used by several W"
  },
  {
    "path": "regipy/plugins/system/codepage.py",
    "chars": 1243,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/computer_name.py",
    "chars": 1124,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryValueNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_T"
  },
  {
    "path": "regipy/plugins/system/crash_dump.py",
    "chars": 1690,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/diag_sr.py",
    "chars": 1297,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/disablelastaccess.py",
    "chars": 1658,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/external/ShimCacheParser.py",
    "chars": 17473,
    "preview": "# Andrew Davis, andrew.davis@mandiant.com\n# Copyright 2012 Mandiant\n#\n# Mandiant licenses this file to you under the Apa"
  },
  {
    "path": "regipy/plugins/system/external/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/system/host_domain_name.py",
    "chars": 1243,
    "preview": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.util"
  },
  {
    "path": "regipy/plugins/system/lsa_packages.py",
    "chars": 4284,
    "preview": "\"\"\"\nLSA Packages plugin - Parses LSA security package configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import R"
  },
  {
    "path": "regipy/plugins/system/mountdev.py",
    "chars": 4733,
    "preview": "\"\"\"\nMountedDevices plugin - Parses mounted device information\n\"\"\"\n\nimport logging\nimport re\n\nfrom regipy.exceptions impo"
  },
  {
    "path": "regipy/plugins/system/network_data.py",
    "chars": 5495,
    "preview": "import logging\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom "
  },
  {
    "path": "regipy/plugins/system/pagefile.py",
    "chars": 3025,
    "preview": "\"\"\"\nPagefile plugin - Parses pagefile configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFou"
  },
  {
    "path": "regipy/plugins/system/pending_file_rename.py",
    "chars": 3318,
    "preview": "\"\"\"\nPending File Rename plugin - Parses pending file rename operations\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions impor"
  },
  {
    "path": "regipy/plugins/system/previous_winver.py",
    "chars": 2310,
    "preview": "import logging\nimport re\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundExcep"
  },
  {
    "path": "regipy/plugins/system/processor_architecture.py",
    "chars": 1344,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/routes.py",
    "chars": 642,
    "preview": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.util"
  },
  {
    "path": "regipy/plugins/system/safeboot_configuration.py",
    "chars": 1725,
    "preview": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SYSTEM_HI"
  },
  {
    "path": "regipy/plugins/system/services.py",
    "chars": 2703,
    "preview": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.exceptions import (\n    RegistryKeyNotFoundException,\n    Reg"
  },
  {
    "path": "regipy/plugins/system/shares.py",
    "chars": 3327,
    "preview": "\"\"\"\nShares plugin - Parses network share configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNot"
  },
  {
    "path": "regipy/plugins/system/shimcache.py",
    "chars": 1015,
    "preview": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plug"
  },
  {
    "path": "regipy/plugins/system/shutdown.py",
    "chars": 1175,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYP"
  },
  {
    "path": "regipy/plugins/system/timezone_data.py",
    "chars": 807,
    "preview": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin"
  },
  {
    "path": "regipy/plugins/system/timezone_data2.py",
    "chars": 1808,
    "preview": "import logging\nimport struct\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import S"
  },
  {
    "path": "regipy/plugins/system/usb_devices.py",
    "chars": 3969,
    "preview": "\"\"\"\nUSB Devices plugin - Parses USB device history (Enum\\\\USB)\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom reg"
  },
  {
    "path": "regipy/plugins/system/usbstor.py",
    "chars": 5733,
    "preview": "import logging\n\nfrom regipy.exceptions import NoRegistrySubkeysException, RegistryKeyNotFoundException\nfrom regipy.hive_"
  },
  {
    "path": "regipy/plugins/system/wdigest.py",
    "chars": 1188,
    "preview": "import logging\n\nfrom regipy.exceptions import RegistryValueNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_T"
  },
  {
    "path": "regipy/plugins/usrclass/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/plugins/usrclass/shellbags_usrclass.py",
    "chars": 14075,
    "preview": "import logging\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import USRCLASS_HIVE_TY"
  },
  {
    "path": "regipy/plugins/utils.py",
    "chars": 4088,
    "preview": "import json\nimport logging\nfrom dataclasses import asdict\nfrom typing import Any, Callable, Union\n\nfrom regipy import NK"
  },
  {
    "path": "regipy/plugins/validated_plugins.json",
    "chars": 1426,
    "preview": "[\n    \"active_control_set\",\n    \"amcache\",\n    \"app_paths\",\n    \"background_activity_moderator\",\n    \"backuprestore_plug"
  },
  {
    "path": "regipy/plugins/validation_status.py",
    "chars": 1570,
    "preview": "\"\"\"\nPlugin validation status tracking.\n\nThis module tracks which plugins have been validated with test cases.\nPlugins wi"
  },
  {
    "path": "regipy/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy/recovery.py",
    "chars": 8100,
    "preview": "import logging\nimport os\nfrom io import BytesIO\n\nfrom construct import Int32ul\n\nfrom regipy import boomerang_stream\nfrom"
  },
  {
    "path": "regipy/regdiff.py",
    "chars": 6611,
    "preview": "import logging\nimport os\nfrom typing import Any\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy."
  },
  {
    "path": "regipy/registry.py",
    "chars": 27075,
    "preview": "import binascii\nimport datetime as dt\nimport logging\nfrom dataclasses import asdict, dataclass, field\nfrom io import Byt"
  },
  {
    "path": "regipy/security_utils.py",
    "chars": 941,
    "preview": "from typing import Any\n\nfrom construct import Int64ub\n\nfrom regipy.structs import ACE, ACL, SID\n\n\ndef convert_sid(sid: A"
  },
  {
    "path": "regipy/structs.py",
    "chars": 8216,
    "preview": "from construct import (\n    Array,\n    Bytes,\n    Const,\n    Enum,\n    FlagsEnum,\n    Int8ul,\n    Int16ul,\n    Int32ul,\n"
  },
  {
    "path": "regipy/utils.py",
    "chars": 6574,
    "preview": "import binascii\nimport datetime as dt\nimport hashlib\nimport logging\nimport struct\nimport sys\nfrom collections.abc import"
  },
  {
    "path": "regipy_mcp_server/README.md",
    "chars": 5066,
    "preview": "# Regipy MCP Server\n\n**Windows Registry Analysis for Claude Desktop**\n\nEnable Claude to analyze Windows registry hives a"
  },
  {
    "path": "regipy_mcp_server/claude_desktop_config.example.json",
    "chars": 299,
    "preview": "{\n  \"mcpServers\": {\n    \"regipy\": {\n      \"command\": \"C:\\\\Users\\\\marti\\\\anaconda3\\\\envs\\\\regipy\\\\python.exe\",\n      \"arg"
  },
  {
    "path": "regipy_mcp_server/server.py",
    "chars": 14563,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nRegipy MCP Server - Windows Registry Analysis for Claude Desktop\n\nThis server enables Claude "
  },
  {
    "path": "regipy_mcp_server/test_local.py",
    "chars": 2533,
    "preview": "#!/usr/bin/env python3\n\"\"\"\nLocal test script for Regipy MCP Server\nTests the server functions directly without Claude De"
  },
  {
    "path": "regipy_tests/__init__.py",
    "chars": 4541,
    "preview": "# flake8: noqa\nfrom .validation.validation_tests import shimcache_validation\nfrom .validation.validation_tests import nt"
  },
  {
    "path": "regipy_tests/cli_tests.py",
    "chars": 2289,
    "preview": "import json\nfrom tempfile import mktemp\n\nfrom click.testing import CliRunner\n\nfrom regipy.cli import parse_header, regis"
  },
  {
    "path": "regipy_tests/conftest.py",
    "chars": 4776,
    "preview": "import lzma\nimport os\nfrom pathlib import Path\nfrom tempfile import mktemp\n\nimport pytest\n\n\ndef extract_lzma(path):\n    "
  },
  {
    "path": "regipy_tests/profiling.py",
    "chars": 4806,
    "preview": "# flake8: noqa\nimport cProfile\nimport io\nimport lzma\nimport os\nimport pstats\nfrom contextlib import contextmanager\nfrom "
  },
  {
    "path": "regipy_tests/test_packaging.py",
    "chars": 2238,
    "preview": "\"\"\"\nTests to verify package configuration and prevent packaging issues.\n\nThese tests ensure that all Python packages are"
  },
  {
    "path": "regipy_tests/test_utils.py",
    "chars": 6860,
    "preview": "\"\"\"Unit tests for regipy.plugins.utils module\"\"\"\n\nfrom unittest.mock import MagicMock\n\nfrom regipy.plugins.utils import "
  },
  {
    "path": "regipy_tests/tests.py",
    "chars": 13756,
    "preview": "import json\nimport os\nfrom tempfile import mkdtemp\n\nfrom regipy import NoRegistrySubkeysException\nfrom regipy.cli_utils "
  },
  {
    "path": "regipy_tests/validation/plugin_validation.md",
    "chars": 16228,
    "preview": "\n# Regipy plugin validation results\n\n## Plugins with validation\n\n| plugin_name                   | plugin_description   "
  },
  {
    "path": "regipy_tests/validation/plugin_validation.py",
    "chars": 8391,
    "preview": "import json\nimport os\nimport sys\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom dataclas"
  },
  {
    "path": "regipy_tests/validation/utils.py",
    "chars": 213,
    "preview": "import lzma\nfrom tempfile import mktemp\n\n\ndef extract_lzma(path):\n    tempfile_path = mktemp()\n    with open(tempfile_pa"
  },
  {
    "path": "regipy_tests/validation/validation.py",
    "chars": 2988,
    "preview": "from dataclasses import dataclass\nfrom typing import Callable, Optional, Union\n\nfrom regipy.plugins.plugin import Plugin"
  },
  {
    "path": "regipy_tests/validation/validation_tests/__init_.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "regipy_tests/validation/validation_tests/active_control_set_validation.py",
    "chars": 927,
    "preview": "from regipy.plugins.system.active_controlset import ActiveControlSetPlugin\nfrom regipy_tests.validation.validation impor"
  },
  {
    "path": "regipy_tests/validation/validation_tests/amcache_validation.py",
    "chars": 666,
    "preview": "from regipy.plugins.amcache.amcache import AmCachePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/app_paths_plugin_validation.py",
    "chars": 715,
    "preview": "from regipy.plugins.software.apppaths import AppPathsPlugin\nfrom regipy_tests.validation.validation import ValidationCas"
  },
  {
    "path": "regipy_tests/validation/validation_tests/backuprestore_plugin_validation.py",
    "chars": 1543,
    "preview": "from regipy.plugins.system.backuprestore import BackupRestorePlugin\nfrom regipy_tests.validation.validation import Valid"
  },
  {
    "path": "regipy_tests/validation/validation_tests/bam_validation.py",
    "chars": 642,
    "preview": "from regipy.plugins.system.bam import BAMPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass Ba"
  },
  {
    "path": "regipy_tests/validation/validation_tests/boot_entry_list_plugin_validation.py",
    "chars": 3630,
    "preview": "from regipy.plugins.bcd.boot_entry_list import BootEntryListPlugin\nfrom regipy_tests.validation.validation import Valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/boot_key_plugin_validation.py",
    "chars": 546,
    "preview": "from regipy.plugins.system.bootkey import BootKeyPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/codepage_validation.py",
    "chars": 582,
    "preview": "from regipy.plugins.system.codepage import CodepagePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/computer_name_plugin_validation.py",
    "chars": 465,
    "preview": "from regipy.plugins.system.computer_name import ComputerNamePlugin\nfrom regipy_tests.validation.validation import Valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/crash_dump_validation.py",
    "chars": 979,
    "preview": "from regipy.plugins.system.crash_dump import CrashDumpPlugin\nfrom regipy_tests.validation.validation import ValidationCa"
  },
  {
    "path": "regipy_tests/validation/validation_tests/diag_sr_validation.py",
    "chars": 773,
    "preview": "from regipy.plugins.system.diag_sr import DiagSRPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nc"
  },
  {
    "path": "regipy_tests/validation/validation_tests/disable_last_access_validation.py",
    "chars": 754,
    "preview": "from regipy.plugins.system.disablelastaccess import DisableLastAccessPlugin\nfrom regipy_tests.validation.validation impo"
  },
  {
    "path": "regipy_tests/validation/validation_tests/disablesr_plugin_validation.py",
    "chars": 401,
    "preview": "from regipy.plugins.software.disablesr import DisableSRPlugin\nfrom regipy_tests.validation.validation import ValidationC"
  },
  {
    "path": "regipy_tests/validation/validation_tests/domain_sid_plugin_validation.py",
    "chars": 471,
    "preview": "from regipy.plugins.security.domain_sid import DomainSidPlugin\nfrom regipy_tests.validation.validation import Validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/execution_policy_plugin_validation.py",
    "chars": 834,
    "preview": "from regipy.plugins.software.execpolicy import ExecutionPolicyPlugin\nfrom regipy_tests.validation.validation import Vali"
  },
  {
    "path": "regipy_tests/validation/validation_tests/host_domain_name_plugin_validation.py",
    "chars": 634,
    "preview": "from regipy.plugins.system.host_domain_name import HostDomainNamePlugin\nfrom regipy_tests.validation.validation import V"
  },
  {
    "path": "regipy_tests/validation/validation_tests/image_file_execution_options_validation.py",
    "chars": 2765,
    "preview": "from regipy.plugins.software.image_file_execution_options import (\n    ImageFileExecutionOptions,\n)\nfrom regipy_tests.va"
  },
  {
    "path": "regipy_tests/validation/validation_tests/installed_programs_ntuser_validation.py",
    "chars": 1259,
    "preview": "from regipy.plugins.ntuser.installed_programs_ntuser import (\n    InstalledProgramsNTUserPlugin,\n)\nfrom regipy_tests.val"
  },
  {
    "path": "regipy_tests/validation/validation_tests/installed_programs_software_plugin_validation.py",
    "chars": 820,
    "preview": "from regipy.plugins.software.installed_programs import InstalledProgramsSoftwarePlugin\nfrom regipy_tests.validation.vali"
  },
  {
    "path": "regipy_tests/validation/validation_tests/last_logon_plugin_validation.py",
    "chars": 598,
    "preview": "from regipy.plugins.software.last_logon import LastLogonPlugin\nfrom regipy_tests.validation.validation import Validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/local_sid_plugin_validation.py",
    "chars": 424,
    "preview": "from regipy.plugins.sam.local_sid import LocalSidPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/lsa_packages_plugin_validation.py",
    "chars": 926,
    "preview": "from regipy.plugins.system.lsa_packages import LSAPackagesPlugin\nfrom regipy_tests.validation.validation import Validati"
  },
  {
    "path": "regipy_tests/validation/validation_tests/mounted_devices_plugin_validation.py",
    "chars": 569,
    "preview": "from regipy.plugins.system.mountdev import MountedDevicesPlugin\nfrom regipy_tests.validation.validation import Validatio"
  },
  {
    "path": "regipy_tests/validation/validation_tests/network_data_plugin_validation.py",
    "chars": 3711,
    "preview": "from regipy.plugins.system.network_data import NetworkDataPlugin\nfrom regipy_tests.validation.validation import Validati"
  },
  {
    "path": "regipy_tests/validation/validation_tests/network_drives_plugin_validation.py",
    "chars": 470,
    "preview": "from regipy.plugins.ntuser.network_drives import NetworkDrivesPlugin\nfrom regipy_tests.validation.validation import Vali"
  },
  {
    "path": "regipy_tests/validation/validation_tests/networklist_plugin_validation.py",
    "chars": 899,
    "preview": "from regipy.plugins.software.networklist import NetworkListPlugin\nfrom regipy_tests.validation.validation import Validat"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_classes_installer_plugin_validation.py",
    "chars": 629,
    "preview": "from regipy.plugins.ntuser.classes_installer import NtuserClassesInstallerPlugin\nfrom regipy_tests.validation.validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_persistence_validation.py",
    "chars": 766,
    "preview": "from regipy.plugins.ntuser.persistence import NTUserPersistencePlugin\nfrom regipy_tests.validation.validation import Val"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_userassist_validation.py",
    "chars": 881,
    "preview": "from regipy.plugins.ntuser.user_assist import UserAssistPlugin\nfrom regipy_tests.validation.validation import Validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/pagefile_plugin_validation.py",
    "chars": 702,
    "preview": "from regipy.plugins.system.pagefile import PagefilePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/previous_winver_plugin_validation.py",
    "chars": 1838,
    "preview": "from regipy.plugins.system.previous_winver import PreviousWinVersionPlugin\nfrom regipy_tests.validation.validation impor"
  },
  {
    "path": "regipy_tests/validation/validation_tests/print_demon_plugin_validation.py",
    "chars": 2152,
    "preview": "from regipy.plugins.software.printdemon import PrintDemonPlugin\nfrom regipy_tests.validation.validation import Validatio"
  },
  {
    "path": "regipy_tests/validation/validation_tests/processor_architecture_validation.py",
    "chars": 913,
    "preview": "from regipy.plugins.system.processor_architecture import ProcessorArchitecturePlugin\nfrom regipy_tests.validation.valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/profile_list_plugin_validation.py",
    "chars": 4263,
    "preview": "from regipy.plugins.software.profilelist import ProfileListPlugin\nfrom regipy_tests.validation.validation import Validat"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ras_tracing_plugin_validation.py",
    "chars": 645,
    "preview": "from regipy.plugins.software.tracing import RASTracingPlugin\nfrom regipy_tests.validation.validation import ValidationCa"
  },
  {
    "path": "regipy_tests/validation/validation_tests/routes_validation.py",
    "chars": 1510,
    "preview": "from regipy.plugins.system.routes import RoutesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ncl"
  },
  {
    "path": "regipy_tests/validation/validation_tests/safeboot_configuration_validation.py",
    "chars": 5418,
    "preview": "from regipy.plugins.system.safeboot_configuration import SafeBootConfigurationPlugin\nfrom regipy_tests.validation.valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/samparse_plugin_validation.py",
    "chars": 1496,
    "preview": "from regipy.plugins.sam.samparse import SAMParsePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nc"
  },
  {
    "path": "regipy_tests/validation/validation_tests/services_plugin_validation.py",
    "chars": 1459,
    "preview": "from regipy.plugins.system.services import ServicesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shell_bag_ntuser_plugin_validation.py",
    "chars": 1147,
    "preview": "import datetime as dt\n\nfrom regipy.plugins.ntuser.shellbags_ntuser import ShellBagNtuserPlugin\nfrom regipy_tests.validat"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shell_bag_usrclass_plugin_validation.py",
    "chars": 973,
    "preview": "from regipy.plugins.usrclass.shellbags_usrclass import ShellBagUsrclassPlugin\nfrom regipy_tests.validation.validation im"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shimcache_validation.py",
    "chars": 501,
    "preview": "from regipy.plugins.system.shimcache import ShimCachePlugin\nfrom regipy_tests.validation.validation import ValidationCas"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shutdown_validation.py",
    "chars": 602,
    "preview": "from regipy.plugins.system.shutdown import ShutdownPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/software_classes_installer_plugin_validation.py",
    "chars": 766,
    "preview": "from regipy.plugins.software.classes_installer import SoftwareClassesInstallerPlugin\nfrom regipy_tests.validation.valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/software_persistence_validation.py",
    "chars": 2379,
    "preview": "from regipy.plugins.software.persistence import SoftwarePersistencePlugin\nfrom regipy_tests.validation.validation import"
  },
  {
    "path": "regipy_tests/validation/validation_tests/spp_clients_plugin_validation.py",
    "chars": 546,
    "preview": "from regipy.plugins.software.spp_clients import SppClientsPlugin\nfrom regipy_tests.validation.validation import Validati"
  },
  {
    "path": "regipy_tests/validation/validation_tests/susclient_plugin_validation.py",
    "chars": 591,
    "preview": "from regipy.plugins.software.susclient import SusclientPlugin\nfrom regipy_tests.validation.validation import ValidationC"
  },
  {
    "path": "regipy_tests/validation/validation_tests/terminal_services_history_validation.py",
    "chars": 421,
    "preview": "from regipy.plugins.ntuser.tsclient import TSClientPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/timezone_data2_validation.py",
    "chars": 1516,
    "preview": "from regipy.plugins.system.timezone_data2 import TimezoneDataPlugin2\nfrom regipy_tests.validation.validation import Vali"
  },
  {
    "path": "regipy_tests/validation/validation_tests/timezone_data_validation.py",
    "chars": 1244,
    "preview": "from regipy.plugins.system.timezone_data import TimezoneDataPlugin\nfrom regipy_tests.validation.validation import Valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/typed_paths_plugin_validation.py",
    "chars": 1178,
    "preview": "from regipy.plugins.ntuser.typed_paths import TypedPathsPlugin\nfrom regipy_tests.validation.validation import Validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/typed_urls_plugin_validation.py",
    "chars": 496,
    "preview": "from regipy.plugins.ntuser.typed_urls import TypedUrlsPlugin\nfrom regipy_tests.validation.validation import ValidationCa"
  },
  {
    "path": "regipy_tests/validation/validation_tests/uac_status_plugin_validation.py",
    "chars": 518,
    "preview": "from regipy.plugins.software.uac import UACStatusPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/usb_devices_plugin_validation.py",
    "chars": 1158,
    "preview": "from regipy.plugins.system.usb_devices import USBDevicesPlugin\nfrom regipy_tests.validation.validation import Validation"
  },
  {
    "path": "regipy_tests/validation/validation_tests/usbstor_plugin_validation.py",
    "chars": 1042,
    "preview": "from regipy.plugins.system.usbstor import USBSTORPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/wdigest_plugin_validation.py",
    "chars": 673,
    "preview": "from regipy.plugins.system.wdigest import WDIGESTPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/windows_defender_plugin_validation.py",
    "chars": 553,
    "preview": "from regipy.plugins.software.defender import WindowsDefenderPlugin\nfrom regipy_tests.validation.validation import Valida"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winrar_plugin_validation.py",
    "chars": 1514,
    "preview": "from regipy.plugins.ntuser.winrar import WinRARPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ncl"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winscp_saved_sessions_plugin_validation.py",
    "chars": 602,
    "preview": "from regipy.plugins.ntuser.winscp_saved_sessions import WinSCPSavedSessionsPlugin\nfrom regipy_tests.validation.validatio"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winver_plugin_validation.py",
    "chars": 1013,
    "preview": "from regipy.plugins.software.winver import WinVersionPlugin\nfrom regipy_tests.validation.validation import ValidationCas"
  },
  {
    "path": "regipy_tests/validation/validation_tests/word_wheel_query_ntuser_validation.py",
    "chars": 493,
    "preview": "from regipy.plugins.ntuser.word_wheel_query import WordWheelQueryPlugin\nfrom regipy_tests.validation.validation import V"
  },
  {
    "path": "regipy_tests/validation/validation_tests/wsl_plugin_validation.py",
    "chars": 4475,
    "preview": "from regipy.plugins.ntuser.wsl import WSLPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WS"
  },
  {
    "path": "renovate.json",
    "chars": 41,
    "preview": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "requirements.txt",
    "chars": 149,
    "preview": "construct==2.10.70\nattrs>=21.4.0\nclick==8.1.8\ninflection==0.5.1\npytz\ntabulate==0.9.0\npytest==8.4.2\nlibfwsi-python==20240"
  }
]

// ... and 24 more files (download for full content)

About this extraction

This page contains the full source code of the mkorman90/regipy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 223 files (544.5 KB), approximately 142.3k tokens, and a symbol index with 491 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!