[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Python dependencies\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 5\n    commit-message:\n      prefix: \"deps\"\n\n  # GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    commit-message:\n      prefix: \"ci\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n    branches: [master]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install ruff\n        run: pip install ruff\n\n      - name: Run ruff linter\n        run: ruff check .\n\n      - name: Run ruff formatter check\n        run: ruff format --check .\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Cache pip dependencies\n        uses: actions/cache@v5\n        with:\n          path: ~/.cache/pip\n          key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}\n          restore-keys: |\n            ${{ runner.os }}-pip-${{ matrix.python-version }}-\n            ${{ runner.os }}-pip-\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -e \".[full,dev]\"\n\n      - name: Run tests with pytest\n        run: pytest -v regipy_tests/tests.py\n\n      - name: Run CLI tests\n        run: pytest -v regipy_tests/cli_tests.py\n\n      - name: Run packaging tests\n        run: pytest -v regipy_tests/test_packaging.py\n\n      - name: Run plugin validation\n        run: PYTHONPATH=. python regipy_tests/validation/plugin_validation.py\n\n  validation-docs:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -e \".[full,dev]\"\n\n      - name: Generate plugin validation documentation\n        run: PYTHONPATH=. python regipy_tests/validation/plugin_validation.py\n\n      - name: Upload validation documentation\n        uses: actions/upload-artifact@v6\n        with:\n          name: plugin-validation-docs\n          path: regipy_tests/validation/plugin_validation.md\n\n  type-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -e \".[full,dev]\"\n\n      - name: Run mypy\n        run: mypy regipy/ --ignore-missing-imports\n        continue-on-error: true  # Allow failures while adding type hints incrementally\n\n  security:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -e \".[full,dev]\"\n          pip install pip-audit cyclonedx-bom\n\n      - name: Run pip-audit (vulnerability scan)\n        run: pip-audit --skip-editable\n\n      - name: Generate SBOM (CycloneDX)\n        run: |\n          cyclonedx-py environment -o sbom.json --of JSON\n          cyclonedx-py environment -o sbom.xml --of XML\n\n      - name: Upload SBOM\n        uses: actions/upload-artifact@v6\n        with:\n          name: sbom\n          path: |\n            sbom.json\n            sbom.xml\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish to PyPI\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: write  # Needed to attach assets to release\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install build dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install build cyclonedx-bom\n\n      - name: Build package\n        run: python -m build\n\n      - name: Generate SBOM (CycloneDX)\n        run: |\n          pip install -e \".[full]\"\n          cyclonedx-py environment -o sbom.json --of JSON\n          cyclonedx-py environment -o sbom.xml --of XML\n\n      - name: Upload build artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: dist\n          path: dist/\n\n      - name: Upload SBOM artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: sbom\n          path: |\n            sbom.json\n            sbom.xml\n\n  publish:\n    needs: build\n    runs-on: ubuntu-latest\n    environment: pypi\n    permissions:\n      id-token: write  # Required for trusted publishing\n      contents: write  # Required to attach assets to release\n\n    steps:\n      - name: Download build artifacts\n        uses: actions/download-artifact@v7\n        with:\n          name: dist\n          path: dist/\n\n      - name: Download SBOM artifacts\n        uses: actions/download-artifact@v7\n        with:\n          name: sbom\n          path: sbom/\n\n      # Uses trusted publishing if configured, otherwise falls back to token\n      - name: Publish to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          # Fallback to API token if trusted publishing is not configured\n          # To use trusted publishing, configure it at https://pypi.org/manage/account/publishing/\n          password: ${{ secrets.PYPI_API_TOKEN }}\n          skip-existing: true\n\n      - name: Attach SBOM to GitHub Release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: |\n            sbom/sbom.json\n            sbom/sbom.xml\n"
  },
  {
    "path": ".github/workflows/scorecard.yml",
    "content": "name: Scorecard supply-chain security\n\non:\n  # For Branch-Protection check. Only the default branch is supported.\n  branch_protection_rule:\n  # To ensure Maintained check is refreshed periodically.\n  schedule:\n    - cron: '21 5 * * 2'\n  push:\n    branches: [ \"master\" ]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecard analysis\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write   # Needed to upload results to code scanning\n      id-token: write          # Needed for publishing to OSSF API\n      # contents: read         # Uncomment for private repos\n      # actions: read          # Uncomment for private repos\n\n    steps:\n      - name: \"Checkout code\"\n        uses: actions/checkout@v6\n        with:\n          persist-credentials: false\n\n      - name: \"Run analysis\"\n        uses: ossf/scorecard-action@v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          publish_results: true\n          # repo_token: ${{ secrets.SCORECARD_TOKEN }} # Optional: for private repos or enabling Branch-Protection on public ones\n\n      - name: \"Upload artifact\"\n        uses: actions/upload-artifact@v6\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      - name: \"Upload to code-scanning\"\n        uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".gitignore",
    "content": "*.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.claude/*\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n.DS_Store\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.5.0\n    hooks:\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-yaml\n      - id: check-added-large-files\n      - id: check-merge-conflict\n\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: v0.8.4\n    hooks:\n      - id: ruff\n        args: [--fix]\n      - id: ruff-format\n\n  - repo: https://github.com/pre-commit/mirrors-mypy\n    rev: v1.13.0\n    hooks:\n      - id: mypy\n        additional_dependencies: []\n        args: [--ignore-missing-imports]\n        pass_filenames: false\n        entry: mypy regipy/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [6.1.0] - 2025-12-27\n\n### Added\n\n- **22 new plugins** covering additional forensic artifacts:\n  - **NTUSER plugins**: `recentdocs`, `comdlg32`, `runmru`, `muicache`, `appkeys`, `sysinternals`, `putty`\n  - **SOFTWARE plugins**: `app_paths`, `appinit_dlls`, `appcert_dlls`, `appcompat_flags`, `windows_defender`, `powershell_logging`, `execution_policy`, `networklist`\n  - **SYSTEM plugins**: `usb_devices`, `mounted_devices`, `shares`, `pagefile`, `lsa_packages`, `pending_file_rename`\n  - **SAM plugins**: `samparse` - Parses user accounts with login times, password info, account flags\n\n- **Plugin validation system** - Plugins are now tracked for validation status\n  - Validation status stored in `validated_plugins.json`, generated by the test framework and shipped with the package\n  - `is_plugin_validated()` function to check validation status\n  - Unvalidated plugins log a warning when executed\n\n- **`--include-unvalidated` CLI flag** for `regipy-plugins-run` command\n  - By default, only validated plugins are executed\n  - Use this flag to include plugins that don't have validation test cases\n\n- **`include_unvalidated` parameter** for `run_relevant_plugins()` function\n  - Default: `False` (only validated plugins run)\n  - Set to `True` to include unvalidated plugins\n\n### Changed\n\n- **Default plugin behavior**: Only validated plugins run by default. This is a safer default as unvalidated plugins may return incomplete or inaccurate data.\n- Updated README with comprehensive plugin list organized by hive type\n- Updated plugin validation documentation\n\n## [6.0.0] - 2025-12-25\n\n### Breaking Changes\n\n- **Minimum Python version raised to 3.9** - Dropped support for Python 3.6, 3.7, and 3.8\n- **Removed `attrs` dependency** - All data classes (`Cell`, `VKRecord`, `LIRecord`, `Value`, `Subkey`) now use Python's built-in `dataclasses` module instead of `attrs`\n  - If you used `attr.asdict()` on these classes, switch to `dataclasses.asdict()`\n  - If you used `attr.fields()` or other attrs introspection, switch to `dataclasses.fields()`\n- **Removed `setup.py`** - Package now uses `pyproject.toml` exclusively (PEP 517/518)\n\n### Added\n\n- `pyproject.toml` with full PEP 621 metadata\n- `py.typed` marker for PEP 561 type checking support\n- Pre-commit configuration with ruff and mypy hooks\n- Consolidated CI workflow with test matrix for Python 3.9-3.13\n- Development documentation in README\n\n### Changed\n\n- Migrated from `flake8` to `ruff` for linting and formatting\n- Modernized Python syntax throughout codebase (f-strings, type hints, import sorting)\n- Consolidated GitHub Actions workflows into unified `ci.yml` and `publish.yml`\n- Updated all GitHub Actions to latest versions (v4/v5)\n\n### Removed\n\n- `setup.py` (replaced by `pyproject.toml`)\n- `.flake8` configuration (replaced by ruff config in `pyproject.toml`)\n- Legacy GitHub workflow files (`python-package.yml`, `python-publish.yml`, `tests.yml`)\n\n## [5.2.0] and earlier\n\nSee [GitHub Releases](https://github.com/mkorman90/regipy/releases) for previous versions.\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md - regipy\n\n> OS-independent Python library for parsing offline Windows registry hives\n\n## Project Overview\n\nregipy 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.\n\n### Core Capabilities\n\n- Parse offline registry hives without Windows dependencies\n- Recursive traversal of keys and values from any path\n- Transaction log recovery (dirty hive reconstruction)\n- Hive comparison/diffing (like RegShot)\n- Extensible plugin system for artifact extraction\n- Timeline generation for forensic analysis\n\n## Architecture\n\n```\nregipy/\n├── registry.py          # Core RegistryHive class - entry point for all parsing\n├── structs.py           # Binary struct definitions (REGF header, NK/VK records, etc.)\n├── hive_types.py        # Hive type constants (NTUSER, SYSTEM, SOFTWARE, SAM, etc.)\n├── exceptions.py        # Custom exceptions (RegistryKeyNotFoundException, etc.)\n├── utils.py             # Helpers: convert_wintime, boomerang_stream, etc.\n├── recovery.py          # Transaction log application logic\n├── plugins/\n│   ├── plugin.py        # Base Plugin class - all plugins inherit from this\n│   ├── utils.py         # run_relevant_plugins() - auto-detects hive and runs matching plugins\n│   ├── ntuser/          # NTUSER.DAT plugins (persistence, typed_urls, user_assist, etc.)\n│   ├── system/          # SYSTEM hive plugins (computer_name, shimcache, bam, bootkey, etc.)\n│   ├── software/        # SOFTWARE hive plugins (installed_programs, profilelist, etc.)\n│   ├── sam/             # SAM hive plugins (local_sid, etc.)\n│   ├── security/        # SECURITY hive plugins\n│   ├── amcache/         # Amcache.hve plugins\n│   └── usrclass/        # UsrClass.dat plugins (shellbags)\n├── cli.py               # CLI entry points\nregipy_tests/\n├── data/                # Test hive files (often .xz compressed)\n├── validation/          # ValidationCase framework for plugin testing\ndocs/\n└── PLUGINS.md           # Plugin development guide\n```\n\n## Key Classes and Patterns\n\n### RegistryHive\n\nThe main entry point. Handles hive parsing, key navigation, and value retrieval.\n\n```python\nfrom regipy.registry import RegistryHive\n\nreg = RegistryHive('/path/to/NTUSER.DAT')\n\n# Navigate to a key\nkey = reg.get_key(r'Software\\Microsoft\\Windows\\CurrentVersion\\Run')\n\n# Get values\nvalues = key.get_values(as_json=True)\n\n# Iterate subkeys\nfor sk in key.iter_subkeys():\n    print(sk.name, sk.header.last_modified)\n\n# Recursive traversal\nfor entry in reg.recurse_subkeys(as_json=True):\n    print(entry)\n\n# Control sets (SYSTEM hive)\nfor path in reg.get_control_sets(r'Control\\ComputerName\\ComputerName'):\n    # Yields: ControlSet001\\Control\\..., ControlSet002\\Control\\..., etc.\n    pass\n```\n\n### Plugin System\n\nPlugins inherit from `Plugin` base class and define:\n- `NAME`: Snake_case identifier\n- `DESCRIPTION`: Human-readable description  \n- `COMPATIBLE_HIVE`: Hive type constant from `hive_types.py`\n- `run()`: Extraction logic, appends results to `self.entries`\n\n```python\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nclass MyPlugin(Plugin):\n    NAME = 'my_plugin'\n    DESCRIPTION = 'Extract something useful'\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n    \n    def run(self):\n        try:\n            key = self.registry_hive.get_key(r'Software\\MyKey')\n            for value in key.get_values(as_json=self.as_json):\n                self.entries.append(value)\n        except RegistryKeyNotFoundException:\n            pass  # Key doesn't exist in this hive\n```\n\n### Timestamp Handling\n\nAlways use `convert_wintime()` for Windows FILETIME conversion:\n\n```python\nfrom regipy.utils import convert_wintime\n\ntimestamp = convert_wintime(key.header.last_modified, as_json=True)\n# Returns ISO format string when as_json=True, datetime object otherwise\n```\n\n### Transaction Log Recovery\n\n```python\nfrom regipy.recovery import apply_transaction_logs\n\napply_transaction_logs(\n    hive_path='/path/to/NTUSER.DAT',\n    transaction_log_path='/path/to/NTUSER.DAT.LOG1',\n    restored_hive_path='/path/to/recovered.DAT'\n)\n```\n\n## Hive Types\n\nDefined in `hive_types.py`:\n\n| Constant | Typical Files |\n|----------|---------------|\n| `NTUSER_HIVE_TYPE` | NTUSER.DAT |\n| `SYSTEM_HIVE_TYPE` | SYSTEM |\n| `SOFTWARE_HIVE_TYPE` | SOFTWARE |\n| `SAM_HIVE_TYPE` | SAM |\n| `SECURITY_HIVE_TYPE` | SECURITY |\n| `USRCLASS_HIVE_TYPE` | UsrClass.dat |\n| `AMCACHE_HIVE_TYPE` | Amcache.hve |\n| `BCD_HIVE_TYPE` | BCD |\n\n## CLI Tools\n\n- `regipy-parse-header` - Display hive header, validate checksums\n- `regipy-dump` - Export entire hive to JSON (or timeline with `-t`)\n- `regipy-plugins-run` - Auto-detect hive type and run relevant plugins\n- `regipy-diff` - Compare two hives, output differences to CSV\n- `regipy-process-transaction-logs` - Apply transaction logs to recover dirty hive\n\n## Development Guidelines\n\n### Adding a New Plugin\n\n1. Create file in appropriate `plugins/<hive_type>/` directory\n2. Inherit from `Plugin`, set `NAME`, `DESCRIPTION`, `COMPATIBLE_HIVE`\n3. Implement `run()` method - append results to `self.entries`\n4. Use try/except for `RegistryKeyNotFoundException` (key may not exist)\n5. Add validation case in `regipy_tests/validation/`\n\n### Validation Cases\n\nRequired for all new plugins:\n\n```python\nfrom regipy_tests.validation.validation import ValidationCase\nfrom regipy.plugins.system.my_plugin import MyPlugin\n\nclass MyPluginValidationCase(ValidationCase):\n    plugin = MyPlugin\n    test_hive_file_name = \"SYSTEM_WIN_10.xz\"  # Must be in regipy_tests/data/\n    \n    # Option 1: Check for presence of specific entries\n    expected_entries = [\n        {\"field\": \"value\", ...}\n    ]\n    \n    # Option 2: Exact match\n    exact_expected_result = [...]\n    \n    expected_entries_count = 5  # Optional count validation\n```\n\n### Code Style\n\n- Use `logging` module (not `logbook` in newer code)\n- Prefer `as_json=True` parameter for JSON-serializable output\n- Handle corrupted values gracefully (`is_corrupted` field)\n- Document Windows-specific quirks in comments\n\n## Installation Variants\n\n```bash\npip install regipy[full]  # All dependencies including compiled ones\npip install regipy        # Minimal dependencies\npip install -e .[full]    # Development install\n```\n\n## Testing\n\n```bash\npytest regipy_tests/\n```\n\nTest hives are stored as `.xz` compressed files in `regipy_tests/data/`.\n\n## Common Forensic Artifacts by Hive\n\n**NTUSER.DAT**: User activity\n- Run/RunOnce keys (persistence)\n- TypedURLs (browser history)\n- UserAssist (program execution)\n- RecentDocs, MRU lists\n\n**SYSTEM**: System configuration\n- ComputerName\n- Shimcache/AppCompatCache (execution history)\n- BAM/DAM (background activity)\n- Services, network interfaces\n\n**SOFTWARE**: Installed software\n- Uninstall keys\n- ProfileList (user profiles)\n- Installed programs\n\n**Amcache.hve**: Application compatibility\n- File execution with SHA1 hashes\n- Driver information\n\n**UsrClass.dat**: Shell data\n- Shellbags (folder access history)\n\n## MCP Server Integration\n\nregipy includes an MCP (Model Context Protocol) server that enables natural language forensic analysis through Claude Desktop or other MCP-compatible AI assistants.\n\n### What it Does\n\nThe MCP server bridges Claude and regipy's plugin ecosystem, allowing investigators to:\n- Ask forensic questions in plain English instead of remembering CLI syntax\n- Auto-detect hive types from a directory of collected registry files\n- Leverage all 75+ plugins without knowing which plugin extracts which artifact\n- Get correlated results across multiple hives in a single conversational query\n\n### Example Workflow\n\nInstead of:\n```bash\nregipy-plugins-run SYSTEM -o system_output.json\nregipy-plugins-run NTUSER.DAT -o ntuser_output.json\n# manually correlate results...\n```\n\nYou can ask:\n> \"What persistence mechanisms exist on this machine?\"\n\nClaude will automatically run both `software_persistence` and `ntuser_persistence` plugins and synthesize the results.\n\n### Setup\n\n1. Install regipy with MCP support\n2. Configure Claude Desktop to use the regipy MCP server\n3. Point at your evidence directory\n4. Start investigating conversationally\n\n### Design Philosophy\n\nThe 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:\n- New plugins automatically become available to Claude without prompt updates\n- Claude can chain multiple plugins when a question spans artifacts\n- Investigators can follow their instincts with follow-up questions\n\nFor 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)\n\n## External References\n\n### Microsoft Documentation\n\n- [Registry Hives (Win32)](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-hives) - Official overview of hive concepts, file formats, and locations\n- [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\n- [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)\n\n### Community Resources\n\n- [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)\n- [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\n\n### Registry File Locations\n\n| Hive | File Path |\n|------|-----------|\n| SYSTEM | `%SystemRoot%\\System32\\config\\SYSTEM` |\n| SOFTWARE | `%SystemRoot%\\System32\\config\\SOFTWARE` |\n| SAM | `%SystemRoot%\\System32\\config\\SAM` |\n| SECURITY | `%SystemRoot%\\System32\\config\\SECURITY` |\n| DEFAULT | `%SystemRoot%\\System32\\config\\DEFAULT` |\n| NTUSER.DAT | `%UserProfile%\\NTUSER.DAT` |\n| UsrClass.dat | `%LocalAppData%\\Microsoft\\Windows\\UsrClass.dat` |\n| Amcache.hve | `%SystemRoot%\\AppCompat\\Programs\\Amcache.hve` |\n\n### Transaction Logs\n\nRegistry 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:\n\n```bash\nregipy-process-transaction-logs NTUSER.DAT -p ntuser.dat.log1 -s ntuser.dat.log2 -o recovered.DAT\n```\n\nOr programmatically via `regipy.recovery.apply_transaction_logs()`.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "# Include the README\ninclude *.md\n\n# Include the license file\ninclude LICENSE.txt\n\nrecursive-include docs *\n\n"
  },
  {
    "path": "README.md",
    "content": "# regipy\n\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mkorman90/regipy/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mkorman90/regipy)\n\n> **⚠️ Breaking Changes in v6.0.0**\n>\n> Version 6.0.0 includes significant modernization changes:\n> - **Python 3.9+ required** - Dropped support for Python 3.6, 3.7, and 3.8\n> - **`attrs` library removed** - Data classes now use Python's built-in `dataclasses` module\n> - If your code imports internal classes (`Cell`, `VKRecord`, `Value`, `Subkey`) and uses `attrs` functions like `attr.asdict()`, switch to `dataclasses.asdict()`\n>\n> See the [CHANGELOG](CHANGELOG.md) for full details.\n\nRegipy is a python library for parsing offline registry hives (Hive files with REGF header). regipy has a lot of capabilities:\n* Use as a library:\n    * Recurse over the registry hive, from root or a given path and get all subkeys and values\n    * Read specific subkeys and values\n    * Apply transaction logs on a registry hive\n* Command Line Tools\n    * Dump an entire registry hive to json\n    * Apply transaction logs on a registry hive\n    * Compare registry hives\n    * Execute plugins from a robust plugin system (i.e: amcache, shimcache, extract computer name...)\n\n**Requires Python 3.9 or higher.**\n\n## Installation\n\nRegipy latest version can be installed from pypi:\n\n```bash\npip install regipy[full]\n```\n\nNOTE: ``regipy[full]`` installs dependencies that require compilation tools and might take some time.\nIt is possible to install a version with relaxed dependencies, by omitting the ``[full]``.\n\nAlso, it is possible to install from source by cloning the repository and executing:\n```bash\npip install --editable .[full]\n```\n\n\n## CLI\n\n#### Parse the header:\n```bash\nregipy-parse-header ~/Documents/TestEvidence/Registry/SYSTEM\n```\nExample output:\n```\n╒════════════════════════╤══════════╕\n│ signature              │ b'regf'  │\n├────────────────────────┼──────────┤\n│ primary_sequence_num   │ 11639    │\n├────────────────────────┼──────────┤\n│ secondary_sequence_num │ 11638    │\n├────────────────────────┼──────────┤\n│ last_modification_time │ 0        │\n├────────────────────────┼──────────┤\n│ major_version          │ 1        │\n├────────────────────────┼──────────┤\n│ minor_version          │ 5        │\n├────────────────────────┼──────────┤\n│ file_type              │ 0        │\n├────────────────────────┼──────────┤\n│ file_format            │ 1        │\n├────────────────────────┼──────────┤\n│ root_key_offset        │ 32       │\n├────────────────────────┼──────────┤\n│ hive_bins_data_size    │ 10534912 │\n├────────────────────────┼──────────┤\n│ clustering_factor      │ 1        │\n├────────────────────────┼──────────┤\n│ file_name              │ SYSTEM   │\n├────────────────────────┼──────────┤\n│ checksum               │ 0        │\n╘════════════════════════╧══════════╛\n[2019-02-09 13:46:12.111654] WARNING: regipy.cli: Hive is not clean! You should apply transaction logs\n```\n* When parsing the header of a hive, also checksum validation and transaction validations are done\n\n\n#### Dump entire hive to disk (this might take some time)\n```bash\nregipy-dump ~/Documents/TestEvidence/Registry/NTUSER-CCLEANER.DAT -o /tmp/output.json\n```\nregipy-dump util can also output a timeline instead of a JSON, by adding the `-t` flag\n\n\n#### Run relevant plugins on Hive\n```bash\nregipy-plugins-run ~/Documents/TestEvidence/Registry/SYSTEM -o /tmp/plugins_output.json\n```\nThe hive type will be detected automatically and the relevant plugins will be executed.\n[**See the plugins section for more information**](docs/PLUGINS.md)\n\n#### Compare registry hives\nCompare registry hives of the same type and output to CSV (if `-o` is not specified output will be printed to screen)\n```bash\nregipy-diff NTUSER.dat NTUSER_modified.dat -o /tmp/diff.csv\n```\nExample output:\n```\n[2019-02-11 19:49:18.824245] INFO: regipy.cli: Comparing NTUSER.DAT vs NTUSER_modified.DAT\n╒══════════════╤══════════════╤════════════════════════════════════════════════════════════════════════════════╤════════════════════════════════════════════════╕\n│ difference   │ first_hive   │ second_hive                                                                    │ description                                    │\n╞══════════════╪══════════════╪════════════════════════════════════════════════════════════════════════════════╪════════════════════════════════════════════════╡\n│ new_subkey   │              │ 2019-02-11T19:46:31.832134+00:00                                               │ \\Software\\Microsoft\\legitimate_subkey          │\n├──────────────┼──────────────┼────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────────────────────┤\n│ new_value    │              │ not_a_malware: c:\\temp\\legitimate_binary.exe @ 2019-02-11 19:45:25.516346+00:00 │ \\Software\\Microsoft\\Windows\\CurrentVersion\\Run │\n╘══════════════╧══════════════╧════════════════════════════════════════════════════════════════════════════════╧════════════════════════════════════════════════╛\n[2019-02-11 19:49:18.825328] INFO: regipy.cli: Detected 2 differences\n```\n\n## Recover a registry hive, using transaction logs:\n```bash\nregipy-process-transaction-logs NTUSER.DAT -p ntuser.dat.log1 -s ntuser.dat.log2 -o recovered_NTUSER.dat\n```\nAfter recovering, compare the hives with registry-diff to see what changed\n\n## Using as a library\n\n#### Initiate the registry hive object\n```python\nfrom regipy.registry import RegistryHive\nreg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/Vibranium-NTUSER.DAT')\n```\n\n#### Iterate recursively over the entire hive, from root key\n```python\nfor entry in reg.recurse_subkeys(as_json=True):\n    print(entry)\n```\n\n#### Iterate over a key and get all subkeys and their modification time:\n```python\nfor sk in reg.get_key('Software').iter_subkeys():\n    print(sk.name, convert_wintime(sk.header.last_modified).isoformat())\n\nAdobe 2019-02-03T22:05:32.525965\nAppDataLow 2019-02-03T22:05:32.526047\nMcAfee 2019-02-03T22:05:32.526140\nMicrosoft 2019-02-03T22:05:32.526282\nNetscape 2019-02-03T22:05:32.526352\nODBC 2019-02-03T22:05:32.526521\nPolicies 2019-02-03T22:05:32.526592\n```\n\n#### Get the values of a key:\n```python\nreg.get_key('Software\\Microsoft\\Internet Explorer\\BrowserEmulation').get_values(as_json=True)\n[{'name': 'CVListTTL',\n  'value': 0,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'UnattendLoaded',\n  'value': 0,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'TLDUpdates',\n  'value': 0,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'CVListXMLVersionLow',\n  'value': 2097211,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'CVListXMLVersionHigh',\n  'value': None,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'CVListLastUpdateTime',\n  'value': None,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'IECompatVersionHigh',\n  'value': None,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'IECompatVersionLow',\n  'value': 2097211,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False},\n {'name': 'StaleCompatCache',\n  'value': 0,\n  'value_type': 'REG_DWORD',\n  'is_corrupted': False}]\n```\n\n#### Use as a plugin:\n```python\nfrom regipy.plugins.ntuser.ntuser_persistence import NTUserPersistencePlugin\nNTUserPersistencePlugin(reg, as_json=True).run()\n\n{\n\t'Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run': {\n\t\t'timestamp': '2019-02-03T22:10:52.655462',\n\t\t'values': [{\n\t\t\t'name': 'Sidebar',\n\t\t\t'value': '%ProgramFiles%\\\\Windows Sidebar\\\\Sidebar.exe /autoRun',\n\t\t\t'value_type': 'REG_EXPAND_SZ',\n\t\t\t'is_corrupted': False\n\t\t}]\n\t}\n}\n```\n\n#### Run all relevant plugins for a specific hive\n```python\nfrom regipy.plugins.utils import run_relevant_plugins\nreg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/SYSTEM')\nrun_relevant_plugins(reg, as_json=True)\n\n{\n\t'routes': {},\n\t'computer_name': [{\n\t\t'control_set': 'ControlSet001\\\\Control\\\\ComputerName\\\\ComputerName',\n\t\t'computer_name': 'DESKTOP-5EG84UG',\n\t\t'timestamp': '2019-02-03T22:19:28.853219'\n\t}]\n}\n```\n\n## Validation cases\n[Validation cases report](regipy_tests/validation/plugin_validation.md)\n\nAll new plugins should have one or more basic validation cases (which can be expanded in the future), for example:\n```python\nfrom regipy.plugins.system.bam import BAMPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NTUserUserAssistValidationCase(ValidationCase):\n    # define your plugin class\n    plugin = BAMPlugin\n    # define the test file name, which should be present in `regipy_tests/data`\n    test_hive_file_name = \"SYSTEM_WIN_10_1709.xz\"\n\n    # Use `expected_entries` to test for presence of a few samples from the plugin results\n    expected_entries = [\n        {\n            \"sequence_number\": 9,\n            \"version\": 1,\n            \"sid\": \"S-1-5-90-0-1\",\n            \"executable\": \"\\\\Device\\\\HarddiskVolume2\\\\Windows\\\\System32\\\\dwm.exe\",\n            \"timestamp\": \"2020-04-19T09:09:35.731816+00:00\",\n            \"key_path\": \"\\\\ControlSet001\\\\Services\\\\bam\\\\state\\\\UserSettings\\\\S-1-5-90-0-1\",\n        }\n    ]\n\n    # OR use `exact_expected_result` to test for an exact result:\n    exact_expected_result = [\n        {\n            \"sequence_number\": 9,\n            \"version\": 1,\n            \"sid\": \"S-1-5-90-0-1\",\n            \"executable\": \"\\\\Device\\\\HarddiskVolume2\\\\Windows\\\\System32\\\\dwm.exe\",\n            \"timestamp\": \"2020-04-19T09:09:35.731816+00:00\",\n            \"key_path\": \"\\\\ControlSet001\\\\Services\\\\bam\\\\state\\\\UserSettings\\\\S-1-5-90-0-1\",\n        },\n        {\n            \"sequence_number\": 8,\n            \"version\": 1,\n            \"sid\": \"S-1-5-90-0-1\",\n            \"executable\": \"\\\\Device\\\\HarddiskVolume2\\\\Windows\\\\System32\\\\cmd.exe\",\n            \"timestamp\": \"2020-04-19T09:09:34.544224+00:00\",\n            \"key_path\": \"\\\\ControlSet001\\\\Services\\\\bam\\\\state\\\\UserSettings\\\\S-1-5-90-0-1\",\n        }\n    ]\n\n    expected_entries_count = 2\n```\n\n## Development\n\n### Setting up for development\n\n```bash\n# Clone the repository\ngit clone https://github.com/mkorman90/regipy.git\ncd regipy\n\n# Install in development mode with all dependencies\npip install -e \".[full,dev]\"\n\n# Install pre-commit hooks\npre-commit install\n```\n\n### Running tests\n\n```bash\n# Run all tests\npytest\n\n# Run specific test files\npytest regipy_tests/tests.py\npytest regipy_tests/cli_tests.py\n\n# Run plugin validation\nPYTHONPATH=. python regipy_tests/validation/plugin_validation.py\n```\n\n### Code quality\n\n```bash\n# Run linter\nruff check .\n\n# Run formatter\nruff format .\n\n# Run type checker\nmypy regipy/\n```\n\n### Testing GitHub Actions Locally\n\nTo test CI workflow changes locally before pushing, use [act](https://github.com/nektos/act):\n\n```bash\n# Install act (Fedora)\nsudo dnf install act-cli\n\n# Install act (macOS)\nbrew install act\n\n# Install act (other)\n# See https://nektosact.com/installation/index.html\n```\n\nMake sure Docker is running, then:\n\n```bash\n# List available jobs\nact -l\n\n# Run the lint job\nact -j lint\n\n# Run all jobs for a push event\nact push\n\n# Run the test job with a specific Python version\nact -j test\n\n# Test the build job from publish workflow (simulates a release)\nact release -j build --eventpath /dev/stdin <<< '{\"action\": \"published\"}'\n```\n\nNote: Some jobs may require secrets. You can provide them with:\n\n```bash\nact -j publish --secret PYPI_API_TOKEN=your_token\n```\n\n## License\n\nMIT\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\n| Version | Supported |\n| ------- | --------- |\n| 6.x.x   | ✅         |\n| < 6.0   | ❌         |\n\n## Reporting a Vulnerability\n\nPlease report vulnerabilities via [GitHub Security Advisories](https://github.com/mkorman90/regipy/security/advisories/new).\n\n## Supply Chain Security\n\nThis project implements several supply chain security measures:\n\n- **SBOM**: Each release includes a [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (`sbom.json`, `sbom.xml`) attached to the GitHub release\n- **Dependency Scanning**: [pip-audit](https://github.com/pypa/pip-audit) runs on every CI build to detect known vulnerabilities\n- **Dependabot**: Automated dependency updates are enabled for both Python packages and GitHub Actions\n- **Trusted Publishing**: PyPI releases use [trusted publishing](https://docs.pypi.org/trusted-publishers/) via GitHub Actions OIDC\n"
  },
  {
    "path": "docs/PLUGINS.md",
    "content": "## regipy Plugins\n\n* The plugin system is a robust and extensive feature that auto-detects the hive type and execute the relevant plugins.\n\nTo see a comprehensive list, please refer to the [Validation cases report](../validation/plugin_validation.md)\n\n## Contributing new plugins\nAdding a new plugin is very straight forward:\n1. Copy the `regipy/plugins/plugin_template.py` file to the relevant folder (according to hive type)\n2. Update the code:\n   * Update the `NAME` parameter and the Class name accordingly (NAME in snake case, Class name in camel case)\n   * Feel free to use/add any utility function to `regipy/utils.py` \n   * Import your class in `regipy/plugins/__init__.py`\n3. Add a [validation case](../README.md#validation-cases). This is mandatory and replaces the old regipy tests.\n"
  },
  {
    "path": "docs/README.rst",
    "content": "regipy\n==========\nRegipy is a python library for parsing offline registry hives!\n\n**Requires Python 3.9 or higher.**\n\nFeatures:\n\n* Use as a library\n* Recurse over the registry hive, from root or a given path and get all subkeys and values\n* Read specific subkeys and values\n* Apply transaction logs on a registry hive\n* Command Line Tools:\n    * Dump an entire registry hive to json\n    * Apply transaction logs on a registry hive\n    * Compare registry hives\n    * Execute plugins from a robust plugin system (i.e: amcache, shimcache, extract computer name...)\n\n:Project page: https://github.com/mkorman90/regipy\n\nUsing as a library:\n--------------------\n.. code:: python\n\n    from regipy.registry import RegistryHive\n    reg = RegistryHive('/Users/martinkorman/Documents/TestEvidence/Registry/Vibranium-NTUSER.DAT')\n\n    # Iterate over a registry hive recursively:\n    for entry in reg.recurse_subkeys(as_json=True):\n        print(entry)\n\n    # Iterate over a key and get all subkeys and their modification time:\n    for sk in reg.get_key('Software').get_subkeys():\n        print(sk.name, convert_wintime(sk.header.last_modified).isoformat())\n\n    # Get values from a specific registry key:\n    reg.get_key('Software\\Microsoft\\Internet Explorer\\BrowserEmulation').get_values(as_json=True)\n\n    # Use plugins:\n    from regipy.plugins.ntuser.ntuser_persistence import NTUserPersistencePlugin\n    NTUserPersistencePlugin(reg, as_json=True).run()\n\n    # Run all validated plugins on a registry hive:\n    run_relevant_plugins(reg, as_json=True)\n\n    # Include unvalidated plugins (may return incomplete/inaccurate data):\n    run_relevant_plugins(reg, as_json=True, include_unvalidated=True)\n\nInstall\n^^^^^^^\nInstall regipy and the command line tools dependencies:\n\n``pip install regipy[cli]``\n\n\nNOTE: using pip with ``regipy[cli]`` instead of the plain ``regipy`` is a\nsignificant change from version 1.9.x\n\nFor using regipy as a library, install only ``regipy`` which comes with fewer\ndependencies:\n\n``pip install regipy``\n\nPlugin Validation\n^^^^^^^^^^^^^^^^^\nRegipy plugins are validated using test cases to ensure they return accurate data.\nBy default, only validated plugins are executed when using ``run_relevant_plugins()``\nor the CLI tools.\n\nTo include unvalidated plugins (which may return incomplete or inaccurate data):\n\n.. code:: python\n\n    # As a library:\n    run_relevant_plugins(reg, as_json=True, include_unvalidated=True)\n\n.. code:: bash\n\n    # CLI:\n    regipy-plugins-run /path/to/hive -o output.json --include-unvalidated\n\nUnvalidated plugins will log a warning when executed. Use them at your own discretion.\n\nAvailable Plugins\n^^^^^^^^^^^^^^^^^\n\n**NTUSER Plugins:**\n\n* ``user_assist`` - Parses User Assist execution history\n* ``typed_urls`` - Retrieves typed URLs from IE history\n* ``typed_paths`` - Retrieves typed file paths\n* ``ntuser_persistence`` - Retrieves persistence entries (Run, RunOnce, etc.)\n* ``shellbag_plugin`` - Parses Shellbag items\n* ``network_drives_plugin`` - Retrieves mapped network drives\n* ``terminal_services_history`` - Parses RDP/Terminal Server client data\n* ``winrar_plugin`` - Parses WinRAR archive history\n* ``winscp_saved_sessions`` - Retrieves WinSCP saved sessions\n* ``word_wheel_query`` - Parses Windows Search word wheel query\n* ``wsl`` - Gets WSL distribution information\n* ``installed_programs_ntuser`` - Retrieves installed programs\n* ``ntuser_classes_installer`` - Parses class installer information\n* ``recentdocs`` - Parses recently opened documents\n* ``comdlg32`` - Parses Open/Save dialog MRU lists\n* ``runmru`` - Parses Run dialog MRU list\n* ``muicache`` - Parses MUI Cache (application display names)\n* ``appkeys`` - Parses application keyboard shortcuts\n* ``sysinternals`` - Parses Sysinternals tools EULA acceptance\n* ``putty`` - Parses PuTTY sessions and SSH host keys\n\n**SOFTWARE Plugins:**\n\n* ``installed_programs_software`` - Retrieves installed programs\n* ``profilelist_plugin`` - Parses user profile information\n* ``uac_plugin`` - Gets User Access Control settings\n* ``winver_plugin`` - Gets OS version information\n* ``last_logon_plugin`` - Gets last logon information\n* ``image_file_execution_options`` - Retrieves IFEO entries\n* ``print_demon_plugin`` - Gets installed printer ports\n* ``ras_tracing`` - Gets tracing/debugging configuration\n* ``disablesr_plugin`` - Gets system restore disable status\n* ``spp_clients_plugin`` - Determines volumes monitored by VSS\n* ``susclient_plugin`` - Extracts Windows Update client info\n* ``software_classes_installer`` - Parses class installer information\n* ``software_plugin`` - Retrieves persistence entries\n* ``app_paths`` - Parses application paths registry entries\n* ``appinit_dlls`` - Parses AppInit_DLLs persistence entries\n* ``appcert_dlls`` - Parses AppCertDLLs persistence entries\n* ``appcompat_flags`` - Parses application compatibility flags\n* ``windows_defender`` - Parses Windows Defender configuration\n* ``powershell_logging`` - Parses PowerShell logging configuration\n* ``execution_policy`` - Parses PowerShell execution policies\n* ``networklist`` - Parses network connection history\n\n**SYSTEM Plugins:**\n\n* ``shimcache`` - Parses Shimcache/AppCompatCache\n* ``services`` - Enumerates services and parameters\n* ``usbstor_plugin`` - Parses connected USB storage devices\n* ``background_activity_moderator`` - Gets BAM execution data\n* ``network_data`` - Gets network interface configuration\n* ``routes`` - Gets network routes\n* ``computer_name`` - Gets the computer name\n* ``timezone_data`` / ``timezone_data2`` - Gets timezone configuration\n* ``safeboot_configuration`` - Gets safeboot configuration\n* ``active_control_set`` - Gets the active control set\n* ``backuprestore_plugin`` - Gets backup/restore exclusion entries\n* ``processor_architecture`` - Gets processor architecture info\n* ``previous_winver_plugin`` - Gets previous Windows version info\n* ``codepage`` - Gets system codepage information\n* ``host_domain_name`` - Gets host and domain name\n* ``crash_dump`` - Gets crash control information\n* ``diag_sr`` - Gets diagnostic system restore information\n* ``disable_last_access`` - Gets LastAccessTime disable status\n* ``wdigest`` - Gets WDIGEST authentication configuration\n* ``bootkey`` - Extracts the Windows boot key\n* ``shutdown`` - Gets shutdown time data\n* ``usb_devices`` - Parses USB device connection history\n* ``mounted_devices`` - Parses mounted device information\n* ``shares`` - Parses network share configuration\n* ``pagefile`` - Parses pagefile configuration\n* ``lsa_packages`` - Parses LSA security packages\n* ``pending_file_rename`` - Parses pending file rename operations\n\n**SAM Plugins:**\n\n* ``local_sid`` - Extracts the machine local SID\n* ``samparse`` - Parses user accounts from SAM hive\n\n**SECURITY Plugins:**\n\n* ``domain_sid`` - Extracts domain name and SID\n\n**AMCACHE Plugins:**\n\n* ``amcache`` - Parses Amcache application execution history\n\n**BCD Plugins:**\n\n* ``boot_entry_list`` - Lists Windows BCD boot entries\n\n**USRCLASS Plugins:**\n\n* ``usrclass_shellbag_plugin`` - Parses USRCLASS Shellbag items\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=61.0\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"regipy\"\nversion = \"6.2.1\"\ndescription = \"Python Registry Parser\"\nreadme = \"docs/README.rst\"\nlicense = {text = \"MIT\"}\nauthors = [\n    {name = \"Martin G. Korman\", email = \"martin@centauri.co.il\"}\n]\nkeywords = [\"Python\", \"Python3\", \"registry\", \"windows registry\", \"registry parser\"]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Natural Language :: English\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Topic :: Software Development :: Libraries\",\n    \"Topic :: Utilities\",\n]\nrequires-python = \">=3.9\"\ndependencies = [\n    \"construct>=2.10\",\n    \"inflection>=0.5.1\",\n    \"pytz\",\n]\n\n[project.optional-dependencies]\ncli = [\n    \"click>=7.0.0\",\n    \"tabulate\",\n]\nfull = [\n    \"click>=7.0.0\",\n    \"tabulate\",\n    \"libfwsi-python>=20240315\",\n    \"libfwps-python>=20240310\",\n]\ndev = [\n    \"pytest>=8.0\",\n    \"pytest-cov\",\n    \"ruff\",\n    \"mypy\",\n    \"pre-commit\",\n    \"tabulate\",  # Required for plugin validation\n    \"tomli; python_version < '3.11'\",  # For test_packaging.py on Python 3.9/3.10\n]\n\n[project.scripts]\nregipy-parse-header = \"regipy.cli:parse_header\"\nregipy-dump = \"regipy.cli:registry_dump\"\nregipy-plugins-run = \"regipy.cli:run_plugins\"\nregipy-plugins-list = \"regipy.cli:list_plugins\"\nregipy-diff = \"regipy.cli:reg_diff\"\nregipy-process-transaction-logs = \"regipy.cli:parse_transaction_log\"\n\n[project.urls]\nHomepage = \"https://github.com/mkorman90/regipy/\"\nRepository = \"https://github.com/mkorman90/regipy/\"\nIssues = \"https://github.com/mkorman90/regipy/issues\"\n\n[tool.setuptools]\npackages = [\"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\"]\ninclude-package-data = true\n\n[tool.setuptools.package-data]\n\"regipy.plugins\" = [\"validated_plugins.json\"]\n\n[tool.ruff]\ntarget-version = \"py39\"\nline-length = 128\n\n[tool.ruff.lint]\nselect = [\n    \"E\",      # pycodestyle errors\n    \"W\",      # pycodestyle warnings\n    \"F\",      # Pyflakes\n    \"I\",      # isort\n    \"UP\",     # pyupgrade (modernize syntax)\n    \"B\",      # flake8-bugbear\n    \"C4\",     # flake8-comprehensions\n    \"SIM\",    # flake8-simplify\n]\nignore = [\n    \"E501\",   # line too long (handled by formatter)\n    \"B008\",   # do not perform function calls in argument defaults\n    \"B904\",   # raise from err (too noisy for existing code)\n    \"SIM108\", # use ternary operator instead of if-else\n    \"SIM115\", # use context manager for opening files\n]\n\n[tool.ruff.lint.per-file-ignores]\n\"regipy_tests/*\" = [\"B007\"]  # Unused loop variables in tests are fine\n\"regipy/cli.py\" = [\"B007\"]  # subkey_count is used after the loop\n\"regipy/cli_utils.py\" = [\"B007\"]  # Loop counter used for progress\n\"regipy/plugins/utils.py\" = [\"B007\"]  # Loop counter pattern\n\n[tool.ruff.lint.isort]\nknown-first-party = [\"regipy\"]\n\n[tool.ruff.format]\nquote-style = \"double\"\nindent-style = \"space\"\nskip-magic-trailing-comma = false\nline-ending = \"auto\"\n\n[tool.mypy]\npython_version = \"3.9\"\nwarn_return_any = true\nwarn_unused_ignores = true\nwarn_redundant_casts = true\ndisallow_untyped_defs = false  # Start lenient, can increase strictness later\ncheck_untyped_defs = true\nignore_missing_imports = true\n\n[tool.pytest.ini_options]\ntestpaths = [\"regipy_tests\"]\npython_files = [\"tests.py\", \"test_*.py\", \"*_tests.py\"]\naddopts = \"-v\"\n\n[tool.coverage.run]\nsource = [\"regipy\"]\nbranch = true\n\n[tool.coverage.report]\nexclude_lines = [\n    \"pragma: no cover\",\n    \"if TYPE_CHECKING:\",\n    \"if __name__ == .__main__.:\",\n]\n"
  },
  {
    "path": "regipy/__init__.py",
    "content": "from .registry import *  # noqa: F401, F403\n\n__title__ = \"regipy\"\n__version__ = \"6.2.1\"\n"
  },
  {
    "path": "regipy/cli.py",
    "content": "import csv\nimport json\nimport logging\nimport os\nimport time\nfrom dataclasses import asdict\n\nimport click\nfrom tabulate import tabulate\n\nfrom regipy.cli_utils import _normalize_subkey_fields, get_filtered_subkeys\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.plugins.plugin import PLUGINS\nfrom regipy.plugins.utils import run_relevant_plugins\nfrom regipy.recovery import apply_transaction_logs\nfrom regipy.regdiff import compare_hives\nfrom regipy.registry import RegistryHive\nfrom regipy.utils import _setup_logging, calculate_xor32_checksum\n\nlogger = logging.getLogger(__name__)\n\n\n@click.command()\n@click.argument(\n    \"hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\"-v\", \"--verbose\", is_flag=True, default=True, help=\"Verbosity\")\ndef parse_header(hive_path, verbose):\n    _setup_logging(verbose=verbose)\n    registry_hive = RegistryHive(hive_path)\n\n    click.secho(tabulate(registry_hive.header.items(), tablefmt=\"fancy_grid\"))\n\n    if registry_hive.header.primary_sequence_num != registry_hive.header.secondary_sequence_num:\n        click.secho(\"Hive is not clean! You should apply transaction logs\", fg=\"red\")\n\n    calculated_checksum = calculate_xor32_checksum(registry_hive._stream.read(508))\n    if registry_hive.header.checksum != calculated_checksum:\n        click.secho(\"Hive is not clean! Header checksum does not match\", fg=\"red\")\n\n\n@click.command()\n@click.argument(\n    \"hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\n    \"-o\",\n    \"output_path\",\n    type=click.Path(exists=False, dir_okay=False, resolve_path=True),\n    required=False,\n)\n@click.option(\"-p\", \"--registry-path\", help=\"A registry path to start iterating from\")\n@click.option(\n    \"-t\",\n    \"--timeline\",\n    is_flag=True,\n    default=False,\n    help=\"Create a CSV timeline instead\",\n)\n@click.option(\n    \"-l\",\n    \"--hive-type\",\n    type=click.STRING,\n    required=False,\n    help=\"Specify a hive type, if it could not be identified for some reason\",\n)\n@click.option(\n    \"-r\",\n    \"--partial_hive_path\",\n    type=click.STRING,\n    required=False,\n    help='The path from which the partial hive actually starts, for example: -t ntuser -r \"/Software\" '\n    \"would mean this is actually a HKCU hive, starting from HKCU/Software\",\n)\n@click.option(\"-v\", \"--verbose\", is_flag=True, default=False, help=\"Verbosity\")\n@click.option(\n    \"-d\",\n    \"--do-not-fetch-values\",\n    is_flag=True,\n    default=False,\n    help=\"Not fetching the values for each subkey makes the iteration way faster. Values count will still be returned\",\n)\n@click.option(\n    \"-s\",\n    \"--start-date\",\n    type=click.STRING,\n    required=False,\n    help='If \"-s\" was specified, fetch only values for subkeys starting this timestamp in isoformat',\n)\n@click.option(\n    \"-e\",\n    \"--end-date\",\n    type=click.STRING,\n    required=False,\n    help='If \"-e\" was specified, fetch only values for subkeys until this timestamp in isoformat',\n)\ndef registry_dump(\n    hive_path,\n    output_path,\n    registry_path,\n    timeline,\n    hive_type,\n    partial_hive_path,\n    verbose,\n    do_not_fetch_values,\n    start_date,\n    end_date,\n):\n    _setup_logging(verbose=verbose)\n    registry_hive = RegistryHive(hive_path, hive_type=hive_type, partial_hive_path=partial_hive_path)\n\n    start_time = time.monotonic()\n\n    if registry_path:\n        try:\n            name_key_entry = registry_hive.get_key(registry_path)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Did not find the key: {ex}\")\n            return\n    else:\n        name_key_entry = registry_hive.root\n\n    if timeline and not output_path:\n        click.secho(\"You must provide an output path if choosing timeline output!\", fg=\"red\")\n        return\n\n    if output_path:\n        with open(output_path, \"w\") as output_file:\n            if timeline:\n                csvwriter = csv.DictWriter(\n                    output_file,\n                    delimiter=\",\",\n                    quotechar='\"',\n                    quoting=csv.QUOTE_MINIMAL,\n                    fieldnames=[\"timestamp\", \"subkey_name\", \"values_count\", \"values\"],\n                )\n                csvwriter.writeheader()\n\n            for subkey_count, entry in enumerate(\n                get_filtered_subkeys(\n                    registry_hive,\n                    name_key_entry,\n                    fetch_values=not do_not_fetch_values,\n                    start_date=start_date,\n                    end_date=end_date,\n                )\n            ):\n                if timeline:\n                    csvwriter.writerow(\n                        {\n                            \"subkey_name\": entry.path,\n                            \"timestamp\": entry.timestamp,\n                            \"values_count\": entry.values_count,\n                            \"values\": entry.values,\n                        }\n                    )\n                else:\n                    output_file.write(\n                        json.dumps(\n                            asdict(\n                                entry,\n                            ),\n                            separators=(\n                                \",\",\n                                \":\",\n                            ),\n                            default=_normalize_subkey_fields,\n                        )\n                    )\n                    output_file.write(\"\\n\")\n    else:\n        for subkey_count, entry in enumerate(\n            registry_hive.recurse_subkeys(name_key_entry, as_json=True, fetch_values=not do_not_fetch_values)\n        ):\n            click.secho(json.dumps(asdict(entry), indent=4))\n\n    click.secho(f\"Completed in {time.monotonic() - start_time}s ({subkey_count} subkeys enumerated)\")\n\n\n@click.command()\n@click.argument(\n    \"hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\n    \"-o\",\n    \"output_path\",\n    type=click.Path(exists=False, dir_okay=False, resolve_path=True),\n    required=True,\n    help=\"Output path for plugins result\",\n)\n@click.option(\n    \"-p\",\n    \"--plugins\",\n    type=click.STRING,\n    required=False,\n    help=\"A plugin or list of plugins to execute command separated\",\n)\n@click.option(\n    \"-t\",\n    \"--hive-type\",\n    type=click.STRING,\n    required=False,\n    help=\"Specify a hive type, if it could not be identified for some reason\",\n)\n@click.option(\n    \"-r\",\n    \"--partial_hive_path\",\n    type=click.STRING,\n    required=False,\n    help='The path from which the partial hive actually starts, for example: -t ntuser -r \"/Software\" '\n    \"would mean this is actually a HKCU hive, starting from HKCU/Software\",\n)\n@click.option(\"-v\", \"--verbose\", is_flag=True, default=False, help=\"Verbosity\")\n@click.option(\n    \"--include-unvalidated\",\n    is_flag=True,\n    default=False,\n    help=\"Include plugins that don't have validation test cases. \"\n    \"These plugins may return incomplete or inaccurate data. Use at your own risk.\",\n)\ndef run_plugins(hive_path, output_path, plugins, hive_type, partial_hive_path, verbose, include_unvalidated):\n    _setup_logging(verbose=verbose)\n    registry_hive = RegistryHive(hive_path, hive_type=hive_type, partial_hive_path=partial_hive_path)\n    click.secho(f\"Loaded {len(PLUGINS)} plugins\", fg=\"white\")\n\n    if plugins:\n        plugin_names = {x.NAME for x in PLUGINS}\n        plugins = plugins.split(\",\")\n        plugins = set(plugins)\n        if not plugins.issubset(plugin_names):\n            click.secho(\n                \"Invalid plugin names given: {}\".format(\",\".join(set(plugins) - plugin_names)),\n                fg=\"red\",\n            )\n            click.secho(\n                \"Use --help or -h to get list of plugins and their descriptions\",\n                fg=\"red\",\n            )\n            return\n\n    if include_unvalidated:\n        click.secho(\n            \"Warning: Including unvalidated plugins. These may return incomplete or inaccurate data.\",\n            fg=\"yellow\",\n        )\n\n    # Run relevant plugins\n    plugin_results = run_relevant_plugins(\n        registry_hive,\n        as_json=True,\n        plugins=plugins,\n        include_unvalidated=include_unvalidated,\n    )\n\n    # If output path was set, dump results to disk\n    if output_path:\n        with open(output_path, \"w\") as f:\n            f.write(json.dumps(plugin_results, indent=4))\n    else:\n        print(json.dumps(plugin_results, indent=4))\n    click.secho(\n        f\"Finished: {len(plugin_results)}/{len(PLUGINS)} plugins matched the hive type\",\n        fg=\"green\",\n    )\n\n\n@click.command()\ndef list_plugins():\n    click.secho(\n        tabulate(\n            [(x.NAME, x.COMPATIBLE_HIVE, x.DESCRIPTION) for x in PLUGINS],\n            headers=[\"Plugin Name\", \"Compatible hive\", \"Description\"],\n        )\n    )\n\n\n@click.command()\n@click.argument(\n    \"first_hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.argument(\n    \"second_hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\n    \"-o\",\n    \"output_path\",\n    type=click.Path(exists=False, dir_okay=False, resolve_path=True),\n    required=False,\n)\n@click.option(\"-v\", \"--verbose\", is_flag=True, default=False, help=\"Verbosity\")\ndef reg_diff(first_hive_path, second_hive_path, output_path, verbose):\n    _setup_logging(verbose=verbose)\n    REGDIFF_HEADERS = [\"difference\", \"first_hive\", \"second_hive\", \"description\"]\n\n    found_differences = compare_hives(first_hive_path, second_hive_path, verbose=verbose)\n    click.secho(f\"Comparing {os.path.basename(first_hive_path)} vs {os.path.basename(second_hive_path)}\")\n\n    if output_path:\n        with open(output_path, \"w\") as csvfile:\n            csvwriter = csv.writer(csvfile, delimiter=\"|\", quoting=csv.QUOTE_MINIMAL)\n            csvwriter.writerow(REGDIFF_HEADERS)\n            for difference in found_differences:\n                csvwriter.writerow(difference)\n    else:\n        click.secho(tabulate(found_differences, headers=REGDIFF_HEADERS, tablefmt=\"fancy_grid\"))\n    click.secho(f\"Detected {len(found_differences)} differences\", fg=\"green\")\n\n\n@click.command()\n@click.argument(\n    \"hive_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\n    \"-p\",\n    \"primary_log_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=True,\n)\n@click.option(\n    \"-s\",\n    \"secondary_log_path\",\n    type=click.Path(exists=True, dir_okay=False, resolve_path=True),\n    required=False,\n)\n@click.option(\n    \"-o\",\n    \"output_path\",\n    type=click.Path(exists=False, dir_okay=False, resolve_path=True),\n    required=False,\n)\n@click.option(\"-v\", \"--verbose\", is_flag=True, default=True, help=\"Verbosity\")\ndef parse_transaction_log(hive_path, primary_log_path, secondary_log_path, output_path, verbose):\n    _setup_logging(verbose=verbose)\n    logger.info(f\"Processing hive {hive_path} with transaction log {primary_log_path}\")\n    if secondary_log_path:\n        logger.info(f\"Processing hive {hive_path} with secondary transaction log {secondary_log_path}\")\n\n    restored_hive_path, recovered_dirty_pages_count = apply_transaction_logs(\n        hive_path,\n        primary_log_path,\n        secondary_log_path=secondary_log_path,\n        restored_hive_path=output_path,\n        verbose=verbose,\n    )\n    if recovered_dirty_pages_count:\n        click.secho(\n            f\"Recovered {recovered_dirty_pages_count} dirty pages. Restored hive is at {restored_hive_path}\",\n            fg=\"green\",\n        )\n"
  },
  {
    "path": "regipy/cli_utils.py",
    "content": "import binascii\nimport datetime as dt\nimport logging\nfrom collections.abc import Iterator\n\nimport pytz\nfrom click import progressbar\n\nfrom regipy import NKRecord, RegistryHive, Subkey\nfrom regipy.utils import MAX_LEN\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_filtered_subkeys(\n    registry_hive: RegistryHive,\n    name_key_entry: NKRecord,\n    start_date: str = None,\n    end_date: str = None,\n    verbose=False,\n    fetch_values=True,\n) -> Iterator[NKRecord]:\n    \"\"\"\n    Get records filtered by the specified timestamps\n    :param registry_hive: A RegistryHive object\n    :param name_key_entry: A list of paths as strings\n    :param start_date: Include only subkeys modified after the specified date\n                     in isoformat UTC, for example: 2020-02-18T14:15:00.000000\n    :param end_date: Include only subkeys modified before the specified date\n                     in isoformat UTC, for example: 2020-02-20T14:15:00.000000\n    \"\"\"\n    skipped_entries_count = 0\n    if start_date:\n        start_date = pytz.utc.localize(dt.datetime.fromisoformat(start_date))\n\n    if end_date:\n        end_date = pytz.utc.localize(dt.datetime.fromisoformat(end_date))\n\n    with progressbar(registry_hive.recurse_subkeys(name_key_entry, fetch_values=False)) as reg_subkeys:\n        for subkey_count, subkey in enumerate(reg_subkeys):\n            if start_date and subkey.timestamp < start_date:\n                skipped_entries_count += 1\n                logger.debug(f\"Skipping entry {subkey} which has a timestamp prior to start_date\")\n                continue\n\n            if end_date and subkey.timestamp > end_date:\n                skipped_entries_count += 1\n                logger.debug(f\"Skipping entry {subkey} which has a timestamp after the end_date\")\n                continue\n\n            nk = registry_hive.get_key(subkey.path)\n            yield Subkey(\n                subkey_name=subkey.subkey_name,\n                path=subkey.path,\n                timestamp=subkey.timestamp,\n                values=list(nk.iter_values(as_json=True)) if fetch_values else [],\n                values_count=subkey.values_count,\n            )\n        logger.info(f\"{skipped_entries_count} out of {subkey_count} subkeys were filtered out due to timestamp constrains\")\n\n\ndef _normalize_subkey_fields(field) -> str:\n    if isinstance(field, bytes):\n        return binascii.b2a_hex(field[:MAX_LEN])\n    elif isinstance(field, dt.datetime):\n        return field.isoformat()\n    return field\n"
  },
  {
    "path": "regipy/constants.py",
    "content": "# ShellBags Known GUIDs\nKNOWN_GUIDS = {\n    \"008ca0b1-55b4-4c56-b8a8-4de4b299d3be\": \"Account Pictures\",\n    \"00bcfc5a-ed94-4e48-96a1-3f6217f21990\": \"RoamingTiles\",\n    \"00c6d95f-329c-409a-81d7-c46c66ea7f33\": \"Default Location\",\n    \"00f2886f-cd64-4fc9-8ec5-30ef6cdbe8c3\": \"Scanners and Cameras\",\n    \"0139d44e-6afe-49f2-8690-3dafcae6ffb8\": \"Programs\",\n    \"0142e4d0-fb7a-11dc-ba4a-000ffe7ab428\": \"Biometric Devices (Biometrics)\",\n    \"018d5c66-4533-4307-9b53-224de2ed1fe6\": \"OneDrive\",\n    \"025a5937-a6be-4686-a844-36fe4bec8b6d\": \"Power Options\",\n    \"031e4825-7b94-4dc3-b131-e946b44c8dd5\": \"Users Libraries\",\n    \"04731b67-d933-450a-90e6-4acd2e9408fe\": \"Search Folder\",\n    \"0482af6c-08f1-4c34-8c90-e17ec98b1e17\": \"Public Account Pictures\",\n    \"054fae61-4dd8-4787-80b6-090220c4b700\": \"GameExplorer\",\n    \"05d7b0f4-2121-4eff-bf6b-ed3f69b894d9\": \"Taskbar (Notification Area Icons)\",\n    \"0762d272-c50a-4bb0-a382-697dcd729b80\": \"Users\",\n    \"087da31b-0dd3-4537-8e23-64a18591f88b\": \"Windows Security Center\",\n    \"0907616e-f5e6-48d8-9d61-a91c3d28106d\": \"Hyper-V Remote File Browsing\",\n    \"0ac0837c-bbf8-452a-850d-79d08e667ca7\": \"Computer\",\n    \"0afaced1-e828-11d1-9187-b532f1e9575d\": \"Folder Shortcut\",\n    \"0b2baaeb-0042-4dca-aa4d-3ee8648d03e5\": \"Pictures Library\",\n    \"0c15d503-d017-47ce-9016-7b3f978721cc\": \"Portable Device Values\",\n    \"0c39a5cf-1a7a-40c8-ba74-8900e6df5fcd\": \"Recent Items\",\n    \"0cd7a5c0-9f37-11ce-ae65-08002b2e1262\": \"Cabinet File\",\n    \"0d4c3db6-03a3-462f-a0e6-08924c41b5d4\": \"History\",\n    \"0df44eaa-ff21-4412-828e-260a8728e7f1\": \"Taskbar and Start Menu\",\n    \"0f214138-b1d3-4a90-bba9-27cbc0c5389a\": \"Sync Setup\",\n    \"11016101-e366-4d22-bc06-4ada335c892b\": \"Internet Explorer History and Feeds Shell Data Source for Windows Search\",\n    \"1206f5f1-0569-412c-8fec-3204630dfb70\": \"Credential Manager\",\n    \"13e7f612-f261-4391-bea2-39df4f3fa311\": \"Windows Desktop Search\",\n    \"15ca69b3-30ee-49c1-ace1-6b5ec372afb5\": \"Sample Playlists\",\n    \"15eae92e-f17a-4431-9f28-805e482dafd4\": \"Install New Programs (Get Programs)\",\n    \"1723d66a-7a12-443e-88c7-05e1bfe79983\": \"Previous Versions Delegate Folder\",\n    \"1777f761-68ad-4d8a-87bd-30b759fa33dd\": \"Favorites\",\n    \"17cd9488-1228-4b2f-88ce-4298e93e0966\": \"Default Programs (Set User Defaults)\",\n    \"18989b1d-99b5-455b-841c-ab7c74e4ddfc\": \"Videos\",\n    \"190337d1-b8ca-4121-a639-6d472d16972a\": \"Search Results\",\n    \"1a6fdba2-f42d-4358-a798-b74d745926c5\": \"Recorded TV\",\n    \"1a9ba3a0-143a-11cf-8350-444553540000\": \"Shell Favorite Folder\",\n    \"1ac14e77-02e7-4e5d-b744-2eb1ae5198b7\": \"System32\",\n    \"1b3ea5dc-b587-4786-b4ef-bd1dc332aeae\": \"Libraries\",\n    \"1cf1260c-4dd0-4ebb-811f-33c572699fde\": \"Music\",\n    \"1d2680c9-0e2a-469d-b787-065558bc7d43\": \"Fusion Cache\",\n    \"1e87508d-89c2-42f0-8a7e-645a0f50ca58\": \"Applications\",\n    \"1f3427c8-5c10-4210-aa03-2ee45287d668\": \"User Pinned\",\n    \"1f43a58c-ea28-43e6-9ec4-34574a16ebb7\": \"Windows Desktop Search MAPI Namespace Extension Class\",\n    \"1f4de370-d627-11d1-ba4f-00a0c91eedba\": \"Search Results - Computers (Computer Search Results Folder, Network Computers)\",\n    \"1fa9085f-25a2-489b-85d4-86326eedcd87\": \"Manage Wireless Networks\",\n    \"208d2c60-3aea-1069-a2d7-08002b30309d\": \"My Network Places\",\n    \"20d04fe0-3aea-1069-a2d8-08002b30309d\": \"My Computer\",\n    \"2112ab0a-c86a-4ffe-a368-0de96e47012e\": \"Music\",\n    \"21ec2020-3aea-1069-a2dd-08002b30309d\": \"Control Panel\",\n    \"2227a280-3aea-1069-a2de-08002b30309d\": \"Printers and Faxes\",\n    \"22877a6d-37a1-461a-91b0-dbda5aaebc99\": \"Recent Places\",\n    \"2400183a-6185-49fb-a2d8-4a392a602ba3\": \"Public Videos\",\n    \"241d7c96-f8bf-4f85-b01f-e2b043341a4b\": \"Workspaces Center (Remote Application and Desktop Connections)\",\n    \"24d89e24-2f19-4534-9dde-6a6671fbb8fe\": \"Documents\",\n    \"2559a1f0-21d7-11d4-bdaf-00c04f60b9f0\": \"Search\",\n    \"2559a1f1-21d7-11d4-bdaf-00c04f60b9f0\": \"Help and Support\",\n    \"2559a1f2-21d7-11d4-bdaf-00c04f60b9f0\": \"Windows Security\",\n    \"2559a1f3-21d7-11d4-bdaf-00c04f60b9f0\": \"Run...\",\n    \"2559a1f4-21d7-11d4-bdaf-00c04f60b9f0\": \"Internet\",\n    \"2559a1f5-21d7-11d4-bdaf-00c04f60b9f0\": \"E-mail\",\n    \"2559a1f6-21d7-11d4-bdaf-00c04f60b9f0\": \"OEM link\",\n    \"2559a1f7-21d7-11d4-bdaf-00c04f60b9f0\": \"Set Program Access and Defaults\",\n    \"259ef4b1-e6c9-4176-b574-481532c9bce8\": \"Game Controllers\",\n    \"267cf8a9-f4e3-41e6-95b1-af881be130ff\": \"Location Folder\",\n    \"26ee0668-a00a-44d7-9371-beb064c98683\": \"Control Panel\",\n    \"2728520d-1ec8-4c68-a551-316b684c4ea7\": \"Network Setup Wizard\",\n    \"27e2e392-a111-48e0-ab0c-e17705a05f85\": \"WPD Content Type Folder\",\n    \"28803f59-3a75-4058-995f-4ee5503b023c\": \"Bluetooth Devices\",\n    \"289978ac-a101-4341-a817-21eba7fd046d\": \"Sync Center Conflict Folder\",\n    \"289a9a43-be44-4057-a41b-587a76d7e7f9\": \"Sync Results\",\n    \"289af617-1cc3-42a6-926c-e6a863f0e3ba\": \"DLNA Media Servers Data Source\",\n    \"292108be-88ab-4f33-9a26-7748e62e37ad\": \"Videos library\",\n    \"2965e715-eb66-4719-b53f-1672673bbefa\": \"Results Folder\",\n    \"2a00375e-224c-49de-b8d1-440df7ef3ddc\": \"LocalizedResourcesDir\",\n    \"2b0f765d-c0e9-4171-908e-08a611b84ff6\": \"Cookies\",\n    \"2c36c0aa-5812-4b87-bfd0-4cd0dfb19b39\": \"Original Images\",\n    \"2e9e59c0-b437-4981-a647-9c34b9b90891\": \"Sync Setup Folder\",\n    \"2f6ce85c-f9ee-43ca-90c7-8a9bd53a2467\": \"File History Data Source\",\n    \"3080f90d-d7ad-11d9-bd98-0000947b0257\": \"Show Desktop\",\n    \"3080f90e-d7ad-11d9-bd98-0000947b0257\": \"Window Switcher\",\n    \"3214fab5-9757-4298-bb61-92a9deaa44ff\": \"Public Music\",\n    \"323ca680-c24d-4099-b94d-446dd2d7249e\": \"Common Places\",\n    \"328b0346-7eaf-4bbe-a479-7cb88a095f5b\": \"Layout Folder\",\n    \"335a31dd-f04b-4d76-a925-d6b47cf360df\": \"Backup and Restore Center\",\n    \"339719b5-8c47-4894-94c2-d8f77add44a6\": \"Pictures\",\n    \"33e28130-4e1e-4676-835a-98395c3bc3bb\": \"Pictures\",\n    \"352481e8-33be-4251-ba85-6007caedcf9d\": \"Temporary Internet Files\",\n    \"35786d3c-b075-49b9-88dd-029876e11c01\": \"Portable Devices\",\n    \"36011842-dccc-40fe-aa3d-6177ea401788\": \"Documents Search Results\",\n    \"36eef7db-88ad-4e81-ad49-0e313f0c35f8\": \"Windows Update\",\n    \"374de290-123f-4565-9164-39c4925e467b\": \"Downloads\",\n    \"088e3905-0323-4b02-9826-5d99428e115f\": \"Downloads\",\n    \"37efd44d-ef8d-41b1-940d-96973a50e9e0\": \"Desktop Gadgets\",\n    \"38a98528-6cbf-4ca9-8dc0-b1e1d10f7b1b\": \"Connect To\",\n    \"3add1653-eb32-4cb0-bbd7-dfa0abb5acca\": \"Pictures\",\n    \"3c5c43a3-9ce9-4a9b-9699-2ac0cf6cc4bf\": \"Configure Wireless Network\",\n    \"3d644c9b-1fb8-4f30-9b45-f670235f79c0\": \"Public Downloads\",\n    \"3e7efb4c-faf1-453d-89eb-56026875ef90\": \"Windows Marketplace\",\n    \"3eb685db-65f9-4cf6-a03a-e3ef65729f3d\": \"RoamingAppData\",\n    \"3f2a72a7-99fa-4ddb-a5a8-c604edf61d6b\": \"Music Library\",\n    \"3f6bc534-dfa1-4ab4-ae54-ef25a74e0107\": \"System Restore\",\n    \"3f98a740-839c-4af7-8c36-5badfb33d5fd\": \"Documents library\",\n    \"4026492f-2f69-46b8-b9bf-5654fc07e423\": \"Windows Firewall\",\n    \"40419485-c444-4567-851a-2dd7bfa1684d\": \"Phone and Modem\",\n    \"418c8b64-5463-461d-88e0-75e2afa3c6fa\": \"Explorer Browser Results Folder\",\n    \"4234d49b-0245-4df3-b780-3893943456e1\": \"Applications\",\n    \"4336a54d-038b-4685-ab02-99bb52d3fb8b\": \"Samples\",\n    \"43668bf8-c14e-49b2-97c9-747784d784b7\": \"Sync Center\",\n    \"437ff9c0-a07f-4fa0-af80-84b6c6440a16\": \"Command Folder\",\n    \"450d8fba-ad25-11d0-98a8-0800361b1103\": \"My Documents\",\n    \"4564b25e-30cd-4787-82ba-39e73a750b14\": \"Recent Items Instance Folder\",\n    \"45c6afa5-2c13-402f-bc5d-45cc8172ef6b\": \"Toshiba Bluetooth Stack\",\n    \"46137b78-0ec3-426d-8b89-ff7c3a458b5e\": \"Network Neighborhood\",\n    \"46e06680-4bf0-11d1-83ee-00a0c90dc849\": \"NETWORK_DOMAIN\",\n    \"48daf80b-e6cf-4f4e-b800-0e69d84ee384\": \"Libraries\",\n    \"48e7caab-b918-4e58-a94d-505519c795dc\": \"Start Menu Folder\",\n    \"491e922f-5643-4af4-a7eb-4e7a138d8174\": \"Videos\",\n    \"4bd8d571-6d19-48d3-be97-422220080e43\": \"Music\",\n    \"4bfefb45-347d-4006-a5be-ac0cb0567192\": \"Conflicts\",\n    \"4c5c32ff-bb9d-43b0-b5b4-2d72e54eaaa4\": \"Saved Games\",\n    \"4d9f7874-4e0c-4904-967b-40b0d20c3e4b\": \"Internet\",\n    \"4dcafe13-e6a7-4c28-be02-ca8c2126280d\": \"Pictures Search Results\",\n    \"5224f545-a443-4859-ba23-7b5a95bdc8ef\": \"People Near Me\",\n    \"52528a6b-b9e3-4add-b60d-588c2dba842d\": \"Homegroup\",\n    \"52a4f021-7b75-48a9-9f6b-4b87a210bc8f\": \"Quick Launch\",\n    \"5399e694-6ce5-4d6c-8fce-1d8870fdcba0\": \"Control Panel command object for Start menu and desktop\",\n    \"54a754c0-4bf1-11d1-83ee-00a0c90dc849\": \"NETWORK_SHARE\",\n    \"56784854-c6cb-462b-8169-88e350acb882\": \"Contacts\",\n    \"58e3c745-d971-4081-9034-86e34b30836a\": \"Speech Recognition Options\",\n    \"59031a47-3f72-44a7-89c5-5595fe6b30ee\": \"Shared Documents Folder (Users Files)\",\n    \"5b3749ad-b49f-49c1-83eb-15370fbd4882\": \"TreeProperties\",\n    \"5b934b42-522b-4c34-bbfe-37a3ef7b9c90\": \"This Device Folder\",\n    \"5c4f28b5-f869-4e84-8e60-f11db97c5cc7\": \"Generic (All folder items)\",\n    \"5cd7aee2-2219-4a67-b85d-6c9ce15660cb\": \"Programs\",\n    \"5ce4a5e9-e4eb-479d-b89f-130c02886155\": \"DeviceMetadataStore\",\n    \"5e6c858f-0e22-4760-9afe-ea3317b67173\": \"Profile\",\n    \"5e8fc967-829a-475c-93ea-51fce6d9ffce\": \"RealPlayer Cloud\",\n    \"5ea4f148-308c-46d7-98a9-49041b1dd468\": \"Mobility Center Control Panel\",\n    \"5f4eab9a-6833-4f61-899d-31cf46979d49\": \"Generic library\",\n    \"5fa947b5-650a-4374-8a9a-5efa4f126834\": \"OpenDrive\",\n    \"5fa96407-7e77-483c-ac93-691d05850de8\": \"Videos\",\n    \"5fcd4425-ca3a-48f4-a57c-b8a75c32acb1\": \"Hewlett-Packard Recovery (Protect.dll)\",\n    \"60632754-c523-4b62-b45c-4172da012619\": \"User Accounts\",\n    \"625b53c3-ab48-4ec1-ba1f-a1ef4146fc19\": \"Start Menu\",\n    \"62ab5d82-fdc1-4dc3-a9dd-070d1d495d97\": \"ProgramData\",\n    \"62d8ed13-c9d0-4ce8-a914-47dd628fb1b0\": \"Regional and Language Options\",\n    \"631958a6-ad0f-4035-a745-28ac066dc6ed\": \"Videos Library\",\n    \"6365d5a7-0f0d-45e5-87f6-0da56b6a4f7d\": \"Common Files\",\n    \"63da6ec0-2e98-11cf-8d82-444553540000\": \"Microsoft FTP Folder\",\n    \"640167b4-59b0-47a6-b335-a6b3c0695aea\": \"Portable Media Devices\",\n    \"645ff040-5081-101b-9f08-00aa002f954e\": \"Recycle Bin\",\n    \"64693913-1c21-4f30-a98f-4e52906d3b56\": \"CLSID_AppInstanceFolder\",\n    \"67718415-c450-4f3c-bf8a-b487642dc39b\": \"Windows Features\",\n    \"6785bfac-9d2d-4be5-b7e2-59937e8fb80a\": \"Other Users Folder\",\n    \"679f85cb-0220-4080-b29b-5540cc05aab6\": \"Home Folder\",\n    \"67ca7650-96e6-4fdd-bb43-a8e774f73a57\": \"Home Group Control Panel (Home Group)\",\n    \"692f0339-cbaa-47e6-b5b5-3b84db604e87\": \"Extensions Manager Folder\",\n    \"69d2cf90-fc33-4fb7-9a0c-ebb0f0fcb43c\": \"Slide Shows\",\n    \"6c8eec18-8d75-41b2-a177-8831d59d2d50\": \"Mouse\",\n    \"6dfd7c5c-2451-11d3-a299-00c04f8ef6af\": \"Folder Options\",\n    \"6f0cd92b-2e97-45d1-88ff-b0d186b8dedd\": \"Network Connections\",\n    \"7007acc7-3202-11d1-aad2-00805fc1270e\": \"Network Connections\",\n    \"708e1662-b832-42a8-bbe1-0a77121e3908\": \"Tree property value folder\",\n    \"71689ac1-cc88-45d0-8a22-2943c3e7dfb3\": \"Music Search Results\",\n    \"71d99464-3b6b-475c-b241-e15883207529\": \"Sync Results Folder\",\n    \"724ef170-a42d-4fef-9f26-b60e846fba4f\": \"Administrative tools\",\n    \"725be8f7-668e-4c7b-8f90-46bdb0936430\": \"Keyboard\",\n    \"72b36e70-8700-42d6-a7f7-c9ab3323ee51\": \"Search Connector Folder\",\n    \"74246bfc-4c96-11d0-abef-0020af6b0b7a\": \"Device Manager\",\n    \"767e6811-49cb-4273-87c2-20f355e1085b\": \"Camera Roll\",\n    \"76fc4e2d-d6ad-4519-a663-37bd56068185\": \"Printers\",\n    \"78cb147a-98ea-4aa6-b0df-c8681f69341c\": \"Windows CardSpace\",\n    \"78f3955e-3b90-4184-bd14-5397c15f1efc\": \"Performance Information and Tools\",\n    \"7a979262-40ce-46ff-aeee-7884ac3b6136\": \"Add Hardware\",\n    \"7a9d77bd-5403-11d2-8785-2e0420524153\": \"User Accounts (Users and Passwords)\",\n    \"7b0db17d-9cd2-4a93-9733-46cc89022e7c\": \"Documents\",\n    \"7b396e54-9ec5-4300-be0a-2482ebae1a26\": \"Gadgets\",\n    \"7b81be6a-ce2b-4676-a29e-eb907a5126c5\": \"Programs and Features\",\n    \"7bd29e00-76c1-11cf-9dd0-00a0c9034933\": \"Temporary Internet Files\",\n    \"7bd29e01-76c1-11cf-9dd0-00a0c9034933\": \"Temporary Internet Files\",\n    \"7be9d83c-a729-4d97-b5a7-1b7313c39e0a\": \"Programs Folder\",\n    \"7c5a40ef-a0fb-4bfc-874a-c0f2e0b9fa8e\": \"Program Files\",\n    \"7d1d3a04-debb-4115-95cf-2f29da2920da\": \"Searches\",\n    \"7d49d726-3c21-4f05-99aa-fdc2c9474656\": \"Documents\",\n    \"7e636bfe-dfa9-4d5e-b456-d7b39851d8a9\": \"Templates\",\n    \"7fde1a1e-8b31-49a5-93b8-6be14cfa4943\": \"Generic Search Results\",\n    \"80213e82-bcfd-4c4f-8817-bb27601267a9\": \"Compressed Folder (zip folder)\",\n    \"8060b2e3-c9d7-4a5d-8c6b-ce8eba111328\": \"Proximity CPL\",\n    \"80f3f1d5-feca-45f3-bc32-752c152e456e\": \"Tablet PC Settings\",\n    \"82a5ea35-d9cd-47c5-9629-e15d2f714e6e\": \"CommonStartup\",\n    \"82a74aeb-aeb4-465c-a014-d097ee346d63\": \"Control Panel\",\n    \"82ba0782-5b7a-4569-b5d7-ec83085f08cc\": \"TopViews\",\n    \"8343457c-8703-410f-ba8b-8b026e431743\": \"Feedback Tool\",\n    \"859ead94-2e85-48ad-a71a-0969cb56a6cd\": \"Sample Videos\",\n    \"85bbd920-42a0-1069-a2e4-08002b30309d\": \"Briefcase\",\n    \"863aa9fd-42df-457b-8e4d-0de1b8015c60\": \"Remote Printers\",\n    \"865e5e76-ad83-4dca-a109-50dc2113ce9a\": \"Programs Folder and Fast Items\",\n    \"871c5380-42a0-1069-a2ea-08002b30309d\": \"Internet Explorer (Homepage)\",\n    \"87630419-6216-4ff8-a1f0-143562d16d5c\": \"Mobile Broadband Profile Settings Editor\",\n    \"877ca5ac-cb41-4842-9c69-9136e42d47e2\": \"File Backup Index\",\n    \"87d66a43-7b11-4a28-9811-c86ee395acf7\": \"Indexing Options\",\n    \"88c6c381-2e85-11d0-94de-444553540000\": \"ActiveX Cache Folder\",\n    \"896664f7-12e1-490f-8782-c0835afd98fc\": \"Libraries delegate folder that appears in Users Files Folder\",\n    \"8983036c-27c0-404b-8f08-102d10dcfd74\": \"SendTo\",\n    \"89d83576-6bd1-4c86-9454-beb04e94c819\": \"MAPI Folder\",\n    \"8ad10c31-2adb-4296-a8f7-e4701232c972\": \"Resources\",\n    \"8e74d236-7f35-4720-b138-1fed0b85ea75\": \"OneDrive\",\n    \"8e908fc9-becc-40f6-915b-f4ca0e70d03d\": \"Network and Sharing Center\",\n    \"8fd8b88d-30e1-4f25-ac2b-553d3d65f0ea\": \"DXP\",\n    \"905e63b6-c1bf-494e-b29c-65b732d3d21a\": \"Program Files\",\n    \"9113a02d-00a3-46b9-bc5f-9c04daddd5d7\": \"Enhanced Storage Data Source\",\n    \"9274bd8d-cfd1-41c3-b35e-b13f55a758f4\": \"Printer Shortcuts\",\n    \"93412589-74d4-4e4e-ad0e-e0cb621440fd\": \"Font Settings\",\n    \"9343812e-1c37-4a49-a12e-4b2d810d956b\": \"Search Home\",\n    \"94d6ddcc-4a68-4175-a374-bd584a510b78\": \"Music\",\n    \"96437431-5a90-4658-a77c-25478734f03e\": \"Server Manager\",\n    \"96ae8d84-a250-4520-95a5-a47a7e3c548b\": \"Parental Controls\",\n    \"978e0ed7-92d6-4cec-9b59-3135b9c49ccf\": \"Music library\",\n    \"98d99750-0b8a-4c59-9151-589053683d73\": \"Windows Search Service Media Center Namespace Extension Handler\",\n    \"98ec0e18-2098-4d44-8644-66979315a281\": \"Microsoft Office Outlook\",\n    \"98f275b4-4fff-11e0-89e2-7b86dfd72085\": \"CLSID_StartMenuLauncherProviderFolder\",\n    \"992cffa0-f557-101a-88ec-00dd010ccc48\": \"Network Connections (Network and Dial-up Connections)\",\n    \"9a096bb5-9dc3-4d1c-8526-c3cbf991ea4e\": \"Internet Explorer RSS Feeds Folder\",\n    \"9b74b6a3-0dfd-4f11-9e78-5f7800f2e772\": \"The user's username (%USERNAME%)\",\n    \"9c60de1e-e5fc-40f4-a487-460851a8d915\": \"AutoPlay\",\n    \"9c73f5e5-7ae7-4e32-a8e8-8d23b85255bf\": \"Sync Center Folder\",\n    \"9db7a13c-f208-4981-8353-73cc61ae2783\": \"Previous Versions\",\n    \"9e3995ab-1f9c-4f13-b827-48b24b6c7174\": \"User Pinned\",\n    \"9e52ab10-f80d-49df-acb8-4330f5687855\": \"CDBurning\",\n    \"9f433b7c-5f96-4ce1-ac28-aeaa1cc04d7c\": \"Security Center\",\n    \"9fe63afd-59cf-4419-9775-abcc3849f861\": \"System Recovery (Recovery)\",\n    \"a00ee528-ebd9-48b8-944a-8942113d46ac\": \"CLSID_StartMenuCommandingProviderFolder\",\n    \"a0275511-0e86-4eca-97c2-ecd8f1221d08\": \"Infrared\",\n    \"a0953c92-50dc-43bf-be83-3742fed03c9c\": \"Videos\",\n    \"a302545d-deff-464b-abe8-61c8648d939b\": \"Libraries\",\n    \"a304259d-52b8-4526-8b1a-a1d6cecc8243\": \"iSCSI Initiator\",\n    \"a305ce99-f527-492b-8b1a-7e76fa98d6e4\": \"Installed Updates\",\n    \"a3918781-e5f2-4890-b3d9-a7e54332328c\": \"Application Shortcuts\",\n    \"a3c3d402-e56c-4033-95f7-4885e80b0111\": \"Previous Versions Results Delegate Folder\",\n    \"a3dd4f92-658a-410f-84fd-6fbbbef2fffe\": \"Internet Options\",\n    \"a4115719-d62e-491d-aa7c-e74b8be3b067\": \"Start Menu\",\n    \"a5110426-177d-4e08-ab3f-785f10b4439c\": \"Sony Ericsson File Manager\",\n    \"a520a1a4-1780-4ff6-bd18-167343c5af16\": \"AppDataLow\",\n    \"a52bba46-e9e1-435f-b3d9-28daa648c0f6\": \"OneDrive\",\n    \"a5a3563a-5755-4a6f-854e-afa3230b199f\": \"Library Folder\",\n    \"a5e46e3a-8849-11d1-9d8c-00c04fc99d61\": \"Microsoft Browser Architecture\",\n    \"a63293e8-664e-48db-a079-df759e0509f7\": \"Templates\",\n    \"a6482830-08eb-41e2-84c1-73920c2badb9\": \"Removable Storage Devices\",\n    \"a75d362e-50fc-4fb7-ac2c-a8beaa314493\": \"SidebarParts\",\n    \"a77f5d77-2e2b-44c3-a6a2-aba601054a51\": \"Programs\",\n    \"a8a91a66-3a7d-4424-8d24-04e180695c7a\": \"Device Center (Devices and Printers)\",\n    \"a8cdff1c-4878-43be-b5fd-f8091c1c60d0\": \"Documents\",\n    \"a990ae9f-a03b-4e80-94bc-9912d7504104\": \"Pictures\",\n    \"aaa8d5a5-f1d6-4259-baa8-78e7ef60835e\": \"RoamedTileImages\",\n    \"ab4f43ca-adcd-4384-b9af-3cecea7d6544\": \"Sitios Web\",\n    \"ab5fb87b-7ce2-4f83-915d-550846c9537b\": \"Camera Roll\",\n    \"ae50c081-ebd2-438a-8655-8a092e34987a\": \"Recent Items\",\n    \"aee2420f-d50e-405c-8784-363c582bf45a\": \"Device Pairing Folder\",\n    \"afdb1f70-2a4c-11d2-9039-00c04f8eeb3e\": \"Offline Files Folder\",\n    \"b155bdf8-02f0-451e-9a26-ae317cfd7779\": \"Delegate folder that appears in Computer\",\n    \"b250c668-f57d-4ee1-a63c-290ee7d1aa1f\": \"Sample Music\",\n    \"b28aa736-876b-46da-b3a8-84c5e30ba492\": \"Web sites\",\n    \"b2952b16-0e07-4e5a-b993-58c52cb94cae\": \"DB Folder\",\n    \"b2c761c6-29bc-4f19-9251-e6195265baf1\": \"Color Management\",\n    \"b3690e58-e961-423b-b687-386ebfd83239\": \"Pictures folder\",\n    \"b4bfcc3a-db2c-424c-b029-7fe99a87c641\": \"Desktop\",\n    \"b4fb3f98-c1ea-428d-a78a-d1f5659cba93\": \"Other Users Folder\",\n    \"b5947d7f-b489-4fde-9e77-23780cc610d1\": \"Virtual Machines\",\n    \"b689b0d0-76d3-4cbb-87f7-585d0e0ce070\": \"Games folder\",\n    \"b6ebfb86-6907-413c-9af7-4fc2abf07cc5\": \"Public Pictures\",\n    \"b7534046-3ecb-4c18-be4e-64cd4cb7d6ac\": \"Recycle Bin\",\n    \"b7bede81-df94-4682-a7d8-57a52620b86f\": \"Screenshots\",\n    \"b94237e7-57ac-4347-9151-b08c6c32d1f7\": \"CommonTemplates\",\n    \"b97d20bb-f46a-4c97-ba10-5e3608430854\": \"Startup\",\n    \"b98a2bea-7d42-4558-8bd1-832f41bac6fd\": \"Backup And Restore (Windows 7)\",\n    \"bb06c0e4-d293-4f75-8a90-cb05b6477eee\": \"System\",\n    \"bb64f8a7-bee7-4e1a-ab8d-7d8273f7fdb6\": \"Action Center Control Panel\",\n    \"bc476f4c-d9d7-4100-8d4e-e043f6dec409\": \"Microsoft Browser Architecture\",\n    \"bc48b32f-5910-47f5-8570-5074a8a5636a\": \"Sync Results Delegate Folder\",\n    \"bcb5256f-79f6-4cee-b725-dc34e402fd46\": \"ImplicitAppShortcuts\",\n    \"bcbd3057-ca5c-4622-b42d-bc56db0ae516\": \"Programs\",\n    \"bd7a2e7b-21cb-41b2-a086-b309680c6b7e\": \"Client Side Cache Folder\",\n    \"bd84b380-8ca2-1069-ab1d-08000948f534\": \"Microsoft Windows Font Folder\",\n    \"bd85e001-112e-431e-983b-7b15ac09fff1\": \"RecordedTV\",\n    \"bdbe736f-34f5-4829-abe8-b550e65146c4\": \"TopViews\",\n    \"bdeadf00-c265-11d0-bced-00a0c90ab50f\": \"Web Folders\",\n    \"be122a0e-4503-11da-8bde-f66bad1e3f3a\": \"Windows Anytime Upgrade\",\n    \"bf782cc9-5a52-4a17-806c-2a894ffeeac5\": \"Language Settings\",\n    \"bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968\": \"Links\",\n    \"c0542a90-4bf0-11d1-83ee-00a0c90dc849\": \"NETWORK_SERVER\",\n    \"c1bae2d0-10df-4334-bedd-7aa20b227a9d\": \"Common OEM Links\",\n    \"c1f8339f-f312-4c97-b1c6-ecdf5910c5c0\": \"Pictures library\",\n    \"c291a080-b400-4e34-ae3f-3d2b9637d56c\": \"UNCFATShellFolder Class\",\n    \"c2b136e2-d50e-405c-8784-363c582bf43e\": \"Device Center Initialization\",\n    \"c4900540-2379-4c75-844b-64e6faf8716b\": \"Sample Pictures\",\n    \"c4aa340d-f20f-4863-afef-f87ef2e6ba25\": \"Public Desktop\",\n    \"c4d98f09-6124-4fe0-9942-826416082da9\": \"Users libraries\",\n    \"c555438b-3c23-4769-a71f-b6d3d9b6053a\": \"Display\",\n    \"c57a6066-66a3-4d91-9eb9-41532179f0a5\": \"Application Suggested Locations\",\n    \"c58c4893-3be0-4b45-abb5-a63e4b8c8651\": \"Troubleshooting\",\n    \"c5abbf53-e17f-4121-8900-86626fc2c973\": \"Network Shortcuts\",\n    \"c870044b-f49e-4126-a9c3-b52a1ff411e8\": \"Ringtones\",\n    \"cac52c1a-b53d-4edc-92d7-6b2e8ac19434\": \"Games\",\n    \"cb1b7f8c-c50a-4176-b604-9e24dee8d4d1\": \"Welcome Center (Getting Started)\",\n    \"cce6191f-13b2-44fa-8d14-324728beef2c\": \"{Unknown CSIDL}\",\n    \"d0384e7d-bac3-4797-8f14-cba229b392b5\": \"Administrative Tools\",\n    \"d17d1d6d-cc3f-4815-8fe3-607e7d5d10b3\": \"Text to Speech\",\n    \"d2035edf-75cb-4ef1-95a7-410d9ee17170\": \"DLNA Content Directory Data Source\",\n    \"d20beec4-5ca8-4905-ae3b-bf251ea09b53\": \"Network\",\n    \"d20ea4e1-3957-11d2-a40b-0c5020524152\": \"Fonts\",\n    \"d20ea4e1-3957-11d2-a40b-0c5020524153\": \"Administrative Tools\",\n    \"d24f75aa-4f2b-4d07-a3c4-469b3d9030c4\": \"Offline Files\",\n    \"d34a6ca6-62c2-4c34-8a7c-14709c1ad938\": \"Common Places FS Folder\",\n    \"d426cfd0-87fc-4906-98d9-a23f5d515d61\": \"Windows Search Service Outlook Express Protocol Handler\",\n    \"d4480a50-ba28-11d1-8e75-00c04fa31a86\": \"Add Network Place\",\n    \"d450a8a1-9568-45c7-9c0e-b4f9fb4537bd\": \"Installed Updates\",\n    \"d555645e-d4f8-4c29-a827-d93c859c4f2a\": \"Ease of Access (Ease of Access Center)\",\n    \"d5b1944e-db4e-482e-b3f1-db05827f0978\": \"Softex OmniPass Encrypted Folder\",\n    \"d6277990-4c6a-11cf-8d87-00aa0060f5bf\": \"Scheduled Tasks\",\n    \"d65231b0-b2f1-4857-a4ce-a8e7c6ea7d27\": \"System32\",\n    \"d8559eb9-20c0-410e-beda-7ed416aecc2a\": \"Windows Defender\",\n    \"d9dc8a3b-b784-432e-a781-5a1130a75963\": \"History\",\n    \"d9ef8727-cac2-4e60-809e-86f80a666c91\": \"Secure Startup (BitLocker Drive Encryption)\",\n    \"da3f6866-35fe-4229-821a-26553a67fc18\": \"General (Generic) library\",\n    \"daf95313-e44d-46af-be1b-cbacea2c3065\": \"CLSID_StartMenuProviderFolder\",\n    \"de2b70ec-9bf7-4a93-bd3d-243f7881d492\": \"Contacts\",\n    \"de61d971-5ebc-4f02-a3a9-6c82895e5c04\": \"AddNewPrograms\",\n    \"de92c1c7-837f-4f69-a3bb-86e631204a23\": \"Playlists\",\n    \"de974d24-d9c6-4d3e-bf91-f4455120b917\": \"Common Files\",\n    \"debf2536-e1a8-4c59-b6a2-414586476aea\": \"GameExplorer\",\n    \"df7266ac-9274-4867-8d55-3bd661de872d\": \"Programs and Features\",\n    \"dfdf76a2-c82a-4d63-906a-5644ac457385\": \"Public\",\n    \"dffacdc5-679f-4156-8947-c5c76bc0b67f\": \"Delegate folder that appears in Users Files Folder\",\n    \"e17d4fc0-5564-11d1-83f2-00a0c90dc849\": \"Search Results Folder\",\n    \"e211b736-43fd-11d1-9efb-0000f8757fcd\": \"Scanners and Cameras\",\n    \"e2e7934b-dce5-43c4-9576-7fe4f75e7480\": \"Date and Time\",\n    \"e345f35f-9397-435c-8f95-4e922c26259e\": \"CLSID_StartMenuPathCompleteProviderFolder\",\n    \"e413d040-6788-4c22-957e-175d1c513a34\": \"Sync Center Conflict Delegate Folder\",\n    \"e555ab60-153b-4d17-9f04-a5fe99fc15ec\": \"Ringtones\",\n    \"e773f1af-3a65-4866-857d-846fc9c4598a\": \"Shell Storage Folder Viewer\",\n    \"e7de9b1a-7533-4556-9484-b26fb486475e\": \"Network Map\",\n    \"e7e4bc40-e76a-11ce-a9bb-00aa004ae837\": \"Shell DocObject Viewer\",\n    \"e88dcce0-b7b3-11d1-a9f0-00aa0060fa31\": \"Compressed Folder\",\n    \"e95a4861-d57a-4be1-ad0f-35267e261739\": \"Windows SideShow\",\n    \"e9950154-c418-419e-a90a-20c5287ae24b\": \"Sensors (Location and Other Sensors)\",\n    \"ea25fbd7-3bf7-409e-b97f-3352240903f4\": \"Videos Search Results\",\n    \"ecdb0924-4208-451e-8ee0-373c0956de16\": \"Work Folders\",\n    \"ed228fdf-9ea8-4870-83b1-96b02cfe0d52\": \"My Games\",\n    \"ed4824af-dce4-45a8-81e2-fc7965083634\": \"Public Documents\",\n    \"ed50fc29-b964-48a9-afb3-15ebb9b97f36\": \"PrintHood delegate folder\",\n    \"ed7ba470-8e54-465e-825c-99712043e01c\": \"All Tasks\",\n    \"ed834ed6-4b5a-4bfe-8f11-a626dcb6a921\": \"Personalization Control Panel\",\n    \"edc978d6-4d53-4b2f-a265-5805674be568\": \"Stream Backed Folder\",\n    \"ee32e446-31ca-4aba-814f-a5ebd2fd6d5e\": \"Offline Files\",\n    \"f02c1a0d-be21-4350-88b0-7367fc96ef3c\": \"Computers and Devices\",\n    \"f0d63f85-37ec-4097-b30d-61b4a8917118\": \"Photo Stream\",\n    \"f1390a9a-a3f4-4e5d-9c5f-98f3bd8d935c\": \"Sync Setup Delegate Folder\",\n    \"f1b32785-6fba-4fcf-9d55-7b8e7f157091\": \"LocalAppData\",\n    \"f2ddfc82-8f12-4cdd-b7dc-d4fe1425aa4d\": \"Sound\",\n    \"f38bf404-1d43-42f2-9305-67de0b28fc23\": \"Windows\",\n    \"f3ce0f7c-4901-4acc-8648-d5d44b04ef8f\": \"Users Files\",\n    \"f3f5824c-ad58-4728-af59-a1ebe3392799\": \"Sticky Notes Namespace Extension for Windows Desktop Search\",\n    \"f5175861-2688-11d0-9c5e-00aa00a45957\": \"Subscription Folder\",\n    \"f6b6e965-e9b2-444b-9286-10c9152edbc5\": \"History Vault\",\n    \"f7f1ed05-9f6d-47a2-aaae-29d317c6f066\": \"Common Files\",\n    \"f82df8f7-8b9f-442e-a48c-818ea735ff9b\": \"Pen and Input Devices\",\n    \"f8c2ab3b-17bc-41da-9758-339d7dbf2d88\": \"Previous Versions Results Folder\",\n    \"f90c627b-7280-45db-bc26-cce7bdd620a4\": \"All Tasks\",\n    \"f942c606-0914-47ab-be56-1321b8035096\": \"Storage Spaces\",\n    \"fb0c9c8a-6c50-11d1-9f1d-0000f8757fcd\": \"Scanners & Cameras\",\n    \"fbb3477e-c9e4-4b3b-a2ba-d3f5d3cd46f9\": \"Documents Library\",\n    \"fc9fb64a-1eb2-4ccf-af5e-1a497a9b5c2d\": \"My sharing folders\",\n    \"fcfeecae-ee1b-4849-ae50-685dcf7717ec\": \"Problem Reports and Solutions\",\n    \"fd228cb7-ae11-4ae3-864c-16f3910ab8fe\": \"Fonts\",\n    \"fdd39ad0-238f-46af-adb4-6c85480369c7\": \"Documents\",\n    \"d3162b92-9365-467a-956b-92703aca08af\": \"Documents\",\n    \"fe1290f0-cfbd-11cf-a330-00aa00c16e65\": \"Directory\",\n    \"ff393560-c2a7-11cf-bff4-444553540000\": \"History\",\n    \"0db7e03f-fc29-4dc6-9020-ff41b59e513a\": \"3D Objects\",\n    \"24ad3ad4-a569-4530-98e1-ab02f9417aa8\": \"Pictures\",\n    \"f86fa3ab-70d2-4fc7-9c99-fcbf05467f3a\": \"Videos\",\n    \"3dfdf296-dbec-4fb4-81d1-6a3438bcf4de\": \"Music\",\n    \"e31ea727-12ed-4702-820c-4b6445f28e1a\": \"Dropbox\",\n    \"4a8fcd9f-623c-4283-96f0-10f41846a98a\": \"Box Sync\",\n    \"fbf23b42-e3f0-101b-8488-00aa003e56f8\": \"Internet Explorer\",\n    \"00020d75-0000-0000-c000-000000000046\": \"Inbox\",\n    \"00020d76-0000-0000-c000-000000000046\": \"Inbox\",\n    \"0\": \"All Control Panel Items\",\n    \"1\": \"Appearance and Personalization\",\n    \"2\": \"Hardware and Sound\",\n    \"3\": \"Network and Internet\",\n    \"4\": \"Sounds, Speech, and Audio Devices\",\n    \"5\": \"System and Security\",\n    \"6\": \"Clock, Language, and Region\",\n    \"7\": \"Ease of Access\",\n    \"8\": \"Programs\",\n    \"9\": \"User Accounts\",\n    \"10\": \"Security Center\",\n    \"11\": \"Mobile PC\",\n    \"0ddd015d-b06c-45d5-8c4c-f59713854639\": \"Local Pictures\",\n    \"a0c69a99-21c8-4671-8703-7934162fcf1d\": \"Local Music\",\n    \"7d83ee9b-2244-4e70-b1f5-5393042af1e4\": \"Local Downloads\",\n    \"35286a68-3c57-41a1-bbb1-0eae73d76c95\": \"Local Videos\",\n    \"f42ee2d3-909f-4907-8871-4c22fc0bf756\": \"Local Documents\",\n    \"5ed4f38c-d3ff-4d61-b506-6820320aebfe\": \"All Settings\",\n    \"1bef2128-2f96-4500-ba7c-098dc0049cb2\": \"CLSID_DBFolderBoth\",\n    \"00021400-0000-0000-c000-000000000046\": \"Desktop\",\n    \"3936e9e4-d92c-4eee-a85a-bc16d5ea0819\": \"Frequent folders\",\n    \"45e8e0e8-7ae9-41ad-a9e8-594972716684\": \"Pictures\",\n    \"f5fb2c77-0e2f-4a16-a381-3e560c68bc83\": \"Removable Drives\",\n    \"0e5aae11-a475-4c5b-ab00-c66de400274e\": \"Shell File System Folder\",\n    \"f3364ba0-65b9-11ce-a9ba-00aa004ae837\": \"Shell File System Folder\",\n    \"5e5f29ce-e0a8-49d3-af32-7a7bdc173478\": \"This PC\",\n    \"e44e5d18-0652-4508-a4e2-8a090067bcb0\": \"Default Programs\",\n    \"e88865ea-0e1c-4e20-9aa6-edcd0212c87c\": \"Gallery\",\n    \"b2b4a4d1-2754-4140-a2eb-9a76d9d7cdc6\": \"Linux\",\n    \"2559a1f8-21d7-11d4-bdaf-00c04f60b9f0\": \"Windows Search\",\n    \"e342f0fe-ff1c-4c41-be37-a0271fc90396\": \"Intel Rapid Storage Technology\",\n    \"0bbca823-e77d-419e-9a44-5adec2c8eeb0\": \"NVIDIA Control Panel\",\n    \"8e0c279d-0bd1-43c3-9ebd-31c3dc5b8a77\": \"Windows To Go\",\n    \"00028b00-0000-0000-c000-000000000046\": \"Microsoft Network\",\n}\n"
  },
  {
    "path": "regipy/exceptions.py",
    "content": "class RegipyException(Exception):\n    \"\"\"\n    This is the parent exception for all regipy exceptions\n    \"\"\"\n\n    pass\n\n\nclass RegipyGeneralException(RegipyException):\n    \"\"\"\n    General exception\n    \"\"\"\n\n    pass\n\n\nclass RegistryValueNotFoundException(RegipyException):\n    pass\n\n\nclass NoRegistrySubkeysException(RegipyException):\n    pass\n\n\nclass NoRegistryValuesException(RegipyException):\n    pass\n\n\nclass RegistryKeyNotFoundException(RegipyException):\n    pass\n\n\nclass UnidentifiedHiveException(RegipyException):\n    pass\n\n\nclass RegistryRecoveryException(RegipyException):\n    pass\n\n\nclass RegistryParsingException(RegipyException):\n    \"\"\"\n    Raised when there is a parsing error, most probably a corrupted hive\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "regipy/hive_types.py",
    "content": "NTUSER_HIVE_TYPE = \"ntuser\"\nSYSTEM_HIVE_TYPE = \"system\"\nAMCACHE_HIVE_TYPE = \"amcache\"\nSOFTWARE_HIVE_TYPE = \"software\"\nSAM_HIVE_TYPE = \"sam\"\nSECURITY_HIVE_TYPE = \"security\"\nCLASSES_ROOT_HIVE_TYPE = \"classes_root\"\nBCD_HIVE_TYPE = \"bcd\"\nUSRCLASS_HIVE_TYPE = \"usrclass\"\n\n\nSUPPORTED_HIVE_TYPES = [\n    NTUSER_HIVE_TYPE,\n    SYSTEM_HIVE_TYPE,\n    AMCACHE_HIVE_TYPE,\n    SOFTWARE_HIVE_TYPE,\n    SAM_HIVE_TYPE,\n    SECURITY_HIVE_TYPE,\n    BCD_HIVE_TYPE,\n    USRCLASS_HIVE_TYPE,\n]\n\n\nHVLE_TRANSACTION_LOG_MAGIC = b\"HvLE\"\nDIRT_TRANSACTION_LOG_MAGIC = b\"DIRT\"\n"
  },
  {
    "path": "regipy/plugins/__init__.py",
    "content": "# flake8: noqa\nfrom .amcache.amcache import AmCachePlugin\nfrom .bcd.boot_entry_list import BootEntryListPlugin\nfrom .ntuser.installed_programs_ntuser import InstalledProgramsNTUserPlugin\nfrom .ntuser.network_drives import NetworkDrivesPlugin\nfrom .ntuser.persistence import NTUserPersistencePlugin\nfrom .ntuser.shellbags_ntuser import ShellBagNtuserPlugin\nfrom .ntuser.tsclient import TSClientPlugin\nfrom .ntuser.typed_urls import TypedUrlsPlugin\nfrom .ntuser.typed_paths import TypedPathsPlugin\nfrom .ntuser.user_assist import UserAssistPlugin\nfrom .ntuser.winrar import WinRARPlugin\nfrom .ntuser.winscp_saved_sessions import WinSCPSavedSessionsPlugin\nfrom .ntuser.word_wheel_query import WordWheelQueryPlugin\nfrom .sam.local_sid import LocalSidPlugin\nfrom .security.domain_sid import DomainSidPlugin\nfrom .software.classes_installer import SoftwareClassesInstallerPlugin\nfrom .software.image_file_execution_options import ImageFileExecutionOptions\nfrom .software.installed_programs import InstalledProgramsSoftwarePlugin\nfrom .software.last_logon import LastLogonPlugin\nfrom .software.persistence import SoftwarePersistencePlugin\nfrom .software.printdemon import PrintDemonPlugin\nfrom .software.profilelist import ProfileListPlugin\nfrom .software.tracing import RASTracingPlugin\nfrom .software.uac import UACStatusPlugin\nfrom .system.active_controlset import ActiveControlSetPlugin\nfrom .system.bam import BAMPlugin\nfrom .system.bootkey import BootKeyPlugin\nfrom .system.computer_name import ComputerNamePlugin\nfrom .system.host_domain_name import HostDomainNamePlugin\nfrom .system.routes import RoutesPlugin\nfrom .system.safeboot_configuration import SafeBootConfigurationPlugin\nfrom .system.services import ServicesPlugin\nfrom .system.shimcache import ShimCachePlugin\nfrom .system.timezone_data import TimezoneDataPlugin\nfrom .system.usbstor import USBSTORPlugin\nfrom .system.wdigest import WDIGESTPlugin\nfrom .usrclass.shellbags_usrclass import ShellBagUsrclassPlugin\nfrom .ntuser.classes_installer import NtuserClassesInstallerPlugin\nfrom .system.network_data import NetworkDataPlugin\nfrom .software.winver import WinVersionPlugin\nfrom .system.previous_winver import PreviousWinVersionPlugin\nfrom .system.shutdown import ShutdownPlugin\nfrom .system.processor_architecture import ProcessorArchitecturePlugin\nfrom .system.crash_dump import CrashDumpPlugin\nfrom .software.susclient import SusclientPlugin\nfrom .system.disablelastaccess import DisableLastAccessPlugin\nfrom .system.codepage import CodepagePlugin\nfrom .software.disablesr import DisableSRPlugin\nfrom .system.diag_sr import DiagSRPlugin\nfrom .software.spp_clients import SppClientsPlugin\nfrom .system.backuprestore import BackupRestorePlugin\nfrom .system.timezone_data2 import TimezoneDataPlugin2\nfrom .ntuser.wsl import WSLPlugin\nfrom .ntuser.recentdocs import RecentDocsPlugin\nfrom .ntuser.comdlg32 import ComDlg32Plugin\nfrom .ntuser.runmru import RunMRUPlugin\nfrom .ntuser.muicache import MUICachePlugin\nfrom .ntuser.appkeys import AppKeysPlugin\nfrom .ntuser.sysinternals import SysinternalsPlugin\nfrom .ntuser.putty import PuTTYPlugin\nfrom .software.appinitdlls import AppInitDLLsPlugin\nfrom .system.appcertdlls import AppCertDLLsPlugin\nfrom .software.appcompatflags import AppCompatFlagsPlugin\nfrom .software.apppaths import AppPathsPlugin\nfrom .software.defender import WindowsDefenderPlugin\nfrom .software.pslogging import PowerShellLoggingPlugin\nfrom .software.execpolicy import ExecutionPolicyPlugin\nfrom .software.networklist import NetworkListPlugin\nfrom .system.usb_devices import USBDevicesPlugin\nfrom .system.mountdev import MountedDevicesPlugin\nfrom .system.shares import SharesPlugin\nfrom .system.pagefile import PagefilePlugin\nfrom .system.lsa_packages import LSAPackagesPlugin\nfrom .system.pending_file_rename import PendingFileRenamePlugin\nfrom .sam.samparse import SAMParsePlugin\n"
  },
  {
    "path": "regipy/plugins/amcache/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/amcache/amcache.py",
    "content": "import logging\n\nfrom inflection import underscore\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import AMCACHE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nAMCACHE_FIELD_NUMERIC_MAPPINGS = {\n    \"0\": \"product_name\",\n    \"1\": \"company_name\",\n    \"2\": \"file_version_number\",\n    \"3\": \"language_code\",\n    \"4\": \"switchback_context\",\n    \"5\": \"file_version\",\n    \"6\": \"file_size\",\n    \"7\": \"pe_header_hash\",\n    \"8\": \"unknown1\",\n    \"9\": \"pe_header_checksum\",\n    \"a\": \"unknown2\",\n    \"b\": \"unknown3\",\n    \"c\": \"file_description\",\n    \"d\": \"unknown4\",\n    \"f\": \"linker_compile_time\",\n    \"10\": \"unknown5\",\n    \"11\": \"last_modified_timestamp\",\n    \"12\": \"created_timestamp\",\n    \"15\": \"full_path\",\n    \"16\": \"unknown6\",\n    \"17\": \"last_modified_timestamp_2\",\n    \"100\": \"program_id\",\n    \"101\": \"sha1\",\n}\n\nWIN8_TS_FIELDS = [\n    \"last_modified_timestamp\",\n    \"created_timestamp\",\n    \"last_modified_timestamp_2\",\n]\n\n\nclass AmCachePlugin(Plugin):\n    NAME = \"amcache\"\n    DESCRIPTION = \"Parse Amcache\"\n    COMPATIBLE_HIVE = AMCACHE_HIVE_TYPE\n\n    def parse_amcache_file_entry(self, subkey):\n        entry = {underscore(x.name): x.value for x in subkey.iter_values(as_json=self.as_json)}\n\n        # Sometimes the value names might be numeric instead. Translate them:\n        for k, v in AMCACHE_FIELD_NUMERIC_MAPPINGS.items():\n            content = entry.pop(k, None)\n            if content:\n                entry[v] = content\n\n        if \"sha1\" in entry:\n            entry[\"sha1\"] = entry[\"sha1\"][4:]\n\n        if \"file_id\" in entry and entry[\"file_id\"] != 0:\n            entry[\"file_id\"] = entry[\"file_id\"][4:]\n            if \"sha1\" not in entry:\n                entry[\"sha1\"] = entry[\"file_id\"]\n\n        if \"program_id\" in entry:\n            entry[\"program_id\"] = entry[\"program_id\"][4:]\n\n        entry[\"timestamp\"] = convert_wintime(subkey.header.last_modified, as_json=self.as_json)\n\n        if \"size\" in entry:\n            entry[\"size\"] = int(entry[\"size\"], 16) if isinstance(entry[\"size\"], str) else entry[\"size\"]\n\n        is_pefile = entry.get(\"is_pe_file\")\n        if is_pefile is not None:\n            entry[\"is_pe_file\"] = bool(is_pefile)\n\n        is_os_component = entry.get(\"is_os_component\")\n        if is_os_component is not None:\n            entry[\"is_os_component\"] = bool(is_os_component)\n\n        if entry.get(\"link_date\") == 0:\n            entry.pop(\"link_date\")\n\n        for ts_field_name in WIN8_TS_FIELDS:\n            ts = entry.pop(ts_field_name, None)\n            if ts:\n                entry[ts_field_name] = convert_wintime(ts, as_json=self.as_json)\n\n        self.entries.append(entry)\n\n    def run(self):\n        logger.debug(\"Started AmCache Plugin...\")\n\n        try:\n            amcache_file_subkey = self.registry_hive.get_key(r\"\\Root\\File\")\n        except RegistryKeyNotFoundException:\n            logger.error(r\"Could not find \\Root\\File subkey\")\n            amcache_file_subkey = None\n\n        try:\n            amcache_inventory_file_subkey = self.registry_hive.get_key(r\"\\Root\\InventoryApplicationFile\")\n        except RegistryKeyNotFoundException:\n            logger.info(r\"Could not find \\Root\\InventoryApplicationFile subkey\")\n            amcache_inventory_file_subkey = None\n\n        if amcache_file_subkey:\n            for subkey in amcache_file_subkey.iter_subkeys():\n                if subkey.header.subkey_count > 0:\n                    for file_subkey in subkey.iter_subkeys():\n                        self.parse_amcache_file_entry(file_subkey)\n                if subkey.header.values_count > 0:\n                    self.entries.append(subkey)\n\n        if amcache_inventory_file_subkey:\n            for file_subkey in amcache_inventory_file_subkey.iter_subkeys():\n                self.parse_amcache_file_entry(file_subkey)\n"
  },
  {
    "path": "regipy/plugins/bcd/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/bcd/boot_entry_list.py",
    "content": "\"\"\"\nWindows Boot Configuration Data (BCD) boot entry list plugin\n\"\"\"\n\nimport logging\nimport uuid\nfrom typing import Union\n\nfrom regipy.hive_types import BCD_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.registry import NKRecord\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# BCD object store key path\n# See https://www.geoffchappell.com/notes/windows/boot/bcd/objects.htm\nBCD_OBJECTS_PATH = r\"\\Objects\"\n\n# Relevant BCD object element types:\n\n# BcdLibraryDevice_ApplicationDevice\nELEM_TYPE_APPLICATION_DEVICE = 0x11000001\n# BcdLibraryString_ApplicationPath\nELEM_TYPE_APPLICATION_PATH = 0x12000002\n# BcdLibraryString_Description\nELEM_TYPE_DESCRIPTION = 0x12000004\n\n\ndef _get_element_by_type(obj_key: NKRecord, datatype: int) -> Union[str, bytes, None]:\n    \"\"\"\n    Retrieves stored BCD object elements by their datatype.\n\n    See https://www.geoffchappell.com/notes/windows/boot/bcd/elements.htm\n    \"\"\"\n\n    # The BCD object attributes are stored as \"elements\" instead of normal values\n    elements_key = obj_key.get_subkey(\"Elements\", raise_on_missing=False)\n    if elements_key.subkey_count == 0:\n        return None\n\n    elem_key = elements_key.get_subkey(f\"{datatype:08X}\", raise_on_missing=False)\n    if elem_key is None:\n        return None\n\n    return elem_key.get_value(\"Element\")\n\n\nclass BootEntryListPlugin(Plugin):\n    \"\"\"\n    Windows Boot Configuration Data (BCD) boot entry list extractor\n    \"\"\"\n\n    NAME = \"boot_entry_list\"\n    DESCRIPTION = \"List the Windows BCD boot entries\"\n    COMPATIBLE_HIVE = BCD_HIVE_TYPE\n\n    def run(self) -> None:\n        logger.debug(\"Started Boot Entry List Plugin...\")\n\n        objects_key = self.registry_hive.get_key(BCD_OBJECTS_PATH)\n\n        for obj_key in objects_key.iter_subkeys():\n            desc_key = obj_key.get_subkey(\"Description\")\n            # Object type defines the boot entry features\n            desc_type = desc_key.get_value(\"Type\")\n\n            # The remaining boot entry attributes are stored as object elements\n            desc_name = _get_element_by_type(obj_key, ELEM_TYPE_DESCRIPTION)\n            path_name = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_PATH)\n            device_data = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_DEVICE)\n\n            # Filter out objects that do not look like boot entries\n            if desc_name is None or path_name is None or device_data is None:\n                continue\n\n            # TODO: Figure out the device data blob format\n            if not isinstance(device_data, bytes) or len(device_data) < 72:\n                continue\n\n            # TODO: Figure out how non-GPT partitions are encoded\n            gpt_part_guid = str(uuid.UUID(bytes_le=device_data[32:48]))\n            gpt_disk_guid = str(uuid.UUID(bytes_le=device_data[56:72]))\n\n            entry_type = f\"0x{desc_type:08X}\" if self.as_json else desc_type\n\n            self.entries.append(\n                {\n                    \"guid\": obj_key.name,\n                    \"type\": entry_type,\n                    \"name\": desc_name,\n                    \"gpt_disk\": gpt_disk_guid,\n                    \"gpt_partition\": gpt_part_guid,\n                    \"image_path\": path_name,\n                    \"timestamp\": convert_wintime(obj_key.header.last_modified, as_json=self.as_json),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/ntuser/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/ntuser/appkeys.py",
    "content": "\"\"\"\nAppKeys plugin - Parses application-specific keyboard shortcuts\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nAPPKEYS_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\AppKey\"\n\n\nclass AppKeysPlugin(Plugin):\n    \"\"\"\n    Parses Application Keys from NTUSER.DAT\n    Registry Key: Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\AppKey\n    These are keyboard shortcuts that launch specific applications.\n    \"\"\"\n\n    NAME = \"appkeys\"\n    DESCRIPTION = \"Parses application keyboard shortcuts\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started AppKeys Plugin...\")\n\n        try:\n            appkeys_key = self.registry_hive.get_key(APPKEYS_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Could not find {self.NAME} plugin data at: {APPKEYS_PATH}: {ex}\")\n            return\n\n        for subkey in appkeys_key.iter_subkeys():\n            key_id = subkey.name\n            subkey_path = f\"{APPKEYS_PATH}\\\\{key_id}\"\n\n            entry = {\n                \"key_path\": subkey_path,\n                \"key_id\": key_id,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            }\n\n            for value in subkey.iter_values():\n                if value.name == \"ShellExecute\":\n                    entry[\"shell_execute\"] = value.value\n                elif value.name == \"Association\":\n                    entry[\"association\"] = value.value\n                elif value.name == \"RegisteredApp\":\n                    entry[\"registered_app\"] = value.value\n\n            # [comment] why filter?\n            if \"shell_execute\" in entry or \"association\" in entry or \"registered_app\" in entry:\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/ntuser/classes_installer.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nCLASSES_INSTALLER_PATH = r\"\\Software\\Microsoft\\Installer\\Products\"\n\n\nclass NtuserClassesInstallerPlugin(Plugin):\n    NAME = \"ntuser_classes_installer\"\n    DESCRIPTION = \"List of installed software from NTUSER hive\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            classes_installer_subkey = self.registry_hive.get_key(CLASSES_INSTALLER_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for entry in classes_installer_subkey.iter_subkeys():\n            identifier = entry.name\n            timestamp = convert_wintime(entry.header.last_modified, as_json=self.as_json)\n            product_name = entry.get_value(\"ProductName\")\n            self.entries.append(\n                {\n                    \"identifier\": identifier,\n                    \"timestamp\": timestamp,\n                    \"product_name\": product_name,\n                    \"is_hidden\": product_name is None,\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/ntuser/comdlg32.py",
    "content": "\"\"\"\nComDlg32 plugin - Parses Open/Save dialog history (OpenSavePidlMRU, OpenSaveMRU)\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# Windows Vista+ path\nOPEN_SAVE_PIDL_MRU_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\"\n# Windows XP path\nOPEN_SAVE_MRU_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSaveMRU\"\n# Last visited path\nLAST_VISITED_PIDL_MRU_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\LastVisitedPidlMRU\"\n\n\ndef parse_pidl_mru_value(data: bytes) -> Optional[str]:\n    \"\"\"\n    Parse the PIDL MRU value to extract path/filename.\n    \"\"\"\n    if not data or len(data) < 4:\n        return None\n\n    try:\n        # Try to find readable unicode strings\n        result_parts = []\n        i = 0\n        while i < len(data) - 1:\n            # Look for printable unicode sequences\n            start = i\n            while i < len(data) - 1:\n                char_code = int.from_bytes(data[i : i + 2], \"little\")\n                # Check if it's a printable character or common path character\n                if 0x20 <= char_code <= 0x7E or char_code in [0x5C, 0x2F, 0x3A]:  # \\, /, :\n                    i += 2\n                else:\n                    break\n\n            if i - start >= 4:  # At least 2 unicode characters\n                try:\n                    part = data[start:i].decode(\"utf-16-le\", errors=\"ignore\").strip(\"\\x00\")\n                    if part and len(part) >= 2:\n                        result_parts.append(part)\n                except Exception:\n                    pass\n            i += 2\n\n        # Return the longest meaningful path-like string\n        for part in sorted(result_parts, key=len, reverse=True):\n            if \"\\\\\" in part or \"/\" in part or \".\" in part:\n                return part\n        if result_parts:\n            return result_parts[0]\n    except Exception:\n        pass\n\n    return None\n\n\nclass ComDlg32Plugin(Plugin):\n    \"\"\"\n    Parses Open/Save dialog history from NTUSER.DAT\n    Provides information about files opened or saved through common dialogs.\n    \"\"\"\n\n    NAME = \"comdlg32\"\n    DESCRIPTION = \"Parses Open/Save dialog MRU lists\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started ComDlg32 Plugin...\")\n\n        # Try OpenSavePidlMRU (Vista+)\n        self._parse_open_save_mru(OPEN_SAVE_PIDL_MRU_PATH, \"OpenSavePidlMRU\")\n\n        # Try OpenSaveMRU (XP)\n        self._parse_open_save_mru(OPEN_SAVE_MRU_PATH, \"OpenSaveMRU\")\n\n        # Try LastVisitedPidlMRU\n        self._parse_last_visited_mru(LAST_VISITED_PIDL_MRU_PATH)\n\n    def _parse_open_save_mru(self, base_path: str, mru_type: str):\n        \"\"\"Parse OpenSaveMRU or OpenSavePidlMRU entries\"\"\"\n        try:\n            base_key = self.registry_hive.get_key(base_path)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find {mru_type} at: {base_path}\")\n            return\n\n        # Process the main key and all extension subkeys\n        for subkey in base_key.iter_subkeys():\n            extension = subkey.name\n            subkey_path = f\"{base_path}\\\\{extension}\"\n\n            entry = {\n                \"key_path\": subkey_path,\n                \"mru_type\": mru_type,\n                \"extension\": extension,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                \"items\": [],\n            }\n\n            mru_list = None\n            mru_values = {}\n\n            for value in subkey.iter_values():\n                if value.name == \"MRUListEx\":\n                    mru_list = value.value\n                elif value.name.isdigit():\n                    parsed = parse_pidl_mru_value(value.value)\n                    if parsed:\n                        mru_values[int(value.name)] = parsed\n\n            # Build ordered list\n            if mru_list and mru_values:\n                for i in range(0, len(mru_list) - 4, 4):\n                    index = int.from_bytes(mru_list[i : i + 4], \"little\", signed=True)\n                    if index == -1:\n                        break\n                    if index in mru_values:\n                        entry[\"items\"].append({\"index\": index, \"path\": mru_values[index]})\n\n            if entry[\"items\"]:\n                self.entries.append(entry)\n\n    def _parse_last_visited_mru(self, path: str):\n        \"\"\"Parse LastVisitedPidlMRU entries\"\"\"\n        try:\n            key = self.registry_hive.get_key(path)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find LastVisitedPidlMRU at: {path}\")\n            return\n\n        entry = {\n            \"key_path\": path,\n            \"mru_type\": \"LastVisitedPidlMRU\",\n            \"last_write\": convert_wintime(key.header.last_modified, as_json=self.as_json),\n            \"items\": [],\n        }\n\n        mru_list = None\n        mru_values = {}\n\n        for value in key.iter_values():\n            if value.name == \"MRUListEx\":\n                mru_list = value.value\n            elif value.name.isdigit():\n                parsed = parse_pidl_mru_value(value.value)\n                if parsed:\n                    mru_values[int(value.name)] = parsed\n\n        if mru_list and mru_values:\n            for i in range(0, len(mru_list) - 4, 4):\n                index = int.from_bytes(mru_list[i : i + 4], \"little\", signed=True)\n                if index == -1:\n                    break\n                if index in mru_values:\n                    entry[\"items\"].append({\"index\": index, \"path\": mru_values[index]})\n\n        if entry[\"items\"]:\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/ntuser/installed_programs_ntuser.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nINSTALLED_SOFTWARE_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\"\n\n\nclass InstalledProgramsNTUserPlugin(Plugin):\n    NAME = \"installed_programs_ntuser\"\n    DESCRIPTION = \"Retrieve list of installed programs and their install date from the NTUSER Hive\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def _get_installed_software(self, subkey_path):\n        try:\n            uninstall_sk = self.registry_hive.get_key(subkey_path)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for installed_program in uninstall_sk.iter_subkeys():\n            values = (\n                {x.name: x.value for x in installed_program.iter_values(as_json=self.as_json)}\n                if installed_program.values_count\n                else {}\n            )\n            self.entries.append(\n                {\n                    \"service_name\": installed_program.name,\n                    \"timestamp\": convert_wintime(installed_program.header.last_modified, as_json=self.as_json),\n                    \"registry_path\": subkey_path,\n                    **values,\n                }\n            )\n\n    def run(self):\n        self._get_installed_software(INSTALLED_SOFTWARE_PATH)\n"
  },
  {
    "path": "regipy/plugins/ntuser/muicache.py",
    "content": "\"\"\"\nMUICache plugin - Parses MUI Cache entries (application display names)\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# Windows Vista+ path\nMUICACHE_PATH_VISTA = r\"\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache\"\n# Windows XP path\nMUICACHE_PATH_XP = r\"\\Software\\Microsoft\\Windows\\ShellNoRoam\\MUICache\"\n\n\nclass MUICachePlugin(Plugin):\n    \"\"\"\n    Parses MUICache entries from NTUSER.DAT or UsrClass.dat\n    MUICache stores the display names of applications that have been run.\n    \"\"\"\n\n    NAME = \"muicache\"\n    DESCRIPTION = \"Parses MUI Cache (application display names)\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started MUICache Plugin...\")\n\n        # Try Vista+ path first\n        muicache_found = self._parse_muicache(MUICACHE_PATH_VISTA)\n\n        # Try XP path if Vista+ not found\n        if not muicache_found:\n            self._parse_muicache(MUICACHE_PATH_XP)\n\n    def _parse_muicache(self, path: str) -> bool:\n        \"\"\"Parse MUICache at the given path\"\"\"\n        try:\n            muicache_key = self.registry_hive.get_key(path)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find MUICache at: {path}\")\n            return False\n\n        entry = {\n            \"key_path\": path,\n            \"last_write\": convert_wintime(muicache_key.header.last_modified, as_json=self.as_json),\n            \"applications\": [],\n        }\n\n        for value in muicache_key.iter_values():\n            # Skip system values\n            if value.name.startswith(\"@\") or value.name == \"LangID\":\n                continue\n\n            app_entry = {\n                \"path\": value.name,\n                \"display_name\": value.value if isinstance(value.value, str) else None,\n            }\n\n            # Extract just the filename from the path for easier reading\n            if \"\\\\\" in value.name:\n                app_entry[\"filename\"] = value.name.split(\"\\\\\")[-1]\n            else:\n                app_entry[\"filename\"] = value.name\n\n            entry[\"applications\"].append(app_entry)\n\n        if entry[\"applications\"]:\n            self.entries.append(entry)\n            return True\n\n        return False\n"
  },
  {
    "path": "regipy/plugins/ntuser/network_drives.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nNETWORK_DRIVES = r\"\\Network\"\n\n\nclass NetworkDrivesPlugin(Plugin):\n    NAME = \"network_drives_plugin\"\n    DESCRIPTION = \"Parse the user's mapped network drives\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            network_drives = self.registry_hive.get_key(NETWORK_DRIVES)\n            for mapped_drive in network_drives.iter_subkeys():\n                timestamp = convert_wintime(mapped_drive.header.last_modified, as_json=self.as_json)\n                self.entries.append(\n                    {\n                        \"last_write\": timestamp,\n                        \"drive_letter\": mapped_drive.name,\n                        \"network_path\": mapped_drive.get_value(\"RemotePath\"),\n                    }\n                )\n\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {NETWORK_DRIVES}: {ex}\")\n"
  },
  {
    "path": "regipy/plugins/ntuser/persistence.py",
    "content": "import logging\n\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import get_subkey_values_from_list\n\nlogger = logging.getLogger(__name__)\n\nPERSISTENCE_ENTRIES = [\n    r\"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Run\",\n    r\"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\",\n    r\"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\Setup\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServices\",\n    r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\RunServicesOnce\",\n    r\"\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run\",\n    r\"\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run\",\n    r\"\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\n    r\"\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\",\n    r\"\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\Setup\",\n    r\"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\",\n]\n\n\nclass NTUserPersistencePlugin(Plugin):\n    NAME = \"ntuser_persistence\"\n    DESCRIPTION = \"Retrieve values from known persistence subkeys in NTUSER hive\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        self.entries = get_subkey_values_from_list(\n            self.registry_hive,\n            PERSISTENCE_ENTRIES,\n            as_json=self.as_json,\n            trim_values=False,\n        )\n"
  },
  {
    "path": "regipy/plugins/ntuser/putty.py",
    "content": "\"\"\"\nPuTTY plugin - Parses PuTTY SSH client configuration and session history\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nPUTTY_SESSIONS_PATH = r\"\\Software\\SimonTatham\\PuTTY\\Sessions\"\nPUTTY_SSH_HOST_KEYS_PATH = r\"\\Software\\SimonTatham\\PuTTY\\SshHostKeys\"\nPUTTY_JUMPLIST_PATH = r\"\\Software\\SimonTatham\\PuTTY\\Jumplist\"\n\n\nclass PuTTYPlugin(Plugin):\n    \"\"\"\n    Parses PuTTY configuration and session history from NTUSER.DAT\n\n    Extracts:\n    - Saved sessions with connection details\n    - SSH host keys (evidence of connections)\n    - Jump list entries\n    \"\"\"\n\n    NAME = \"putty\"\n    DESCRIPTION = \"Parses PuTTY sessions and SSH host keys\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started PuTTY Plugin...\")\n\n        self._parse_sessions()\n        self._parse_ssh_host_keys()\n        self._parse_jumplist()\n\n    def _parse_sessions(self):\n        \"\"\"Parse saved PuTTY sessions\"\"\"\n        try:\n            sessions_key = self.registry_hive.get_key(PUTTY_SESSIONS_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PuTTY sessions at: {PUTTY_SESSIONS_PATH}\")\n            return\n\n        for subkey in sessions_key.iter_subkeys():\n            session_name = subkey.name\n            # Session names are URL-encoded\n            try:\n                from urllib.parse import unquote\n\n                decoded_name = unquote(session_name)\n            except Exception:\n                decoded_name = session_name\n\n            entry = {\n                \"type\": \"session\",\n                \"key_path\": f\"{PUTTY_SESSIONS_PATH}\\\\{session_name}\",\n                \"session_name\": decoded_name,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            }\n\n            # Extract required fields\n            extract_values(\n                subkey,\n                {\n                    \"HostName\": \"hostname\",\n                    \"PortNumber\": \"port\",\n                    \"UserName\": \"username\",\n                    \"Protocol\": (\"protocol\", self._get_protocol_name),\n                },\n                entry,\n            )\n\n            # Extract optional fields (only if non-empty)\n            for value in subkey.iter_values():\n                name = value.name\n                val = value.value\n\n                if name == \"ProxyHost\" and val:\n                    entry[\"proxy_host\"] = val\n                elif name == \"ProxyPort\" and val and val != 0:\n                    entry[\"proxy_port\"] = val\n                elif name == \"ProxyUsername\" and val:\n                    entry[\"proxy_username\"] = val\n                elif name == \"PublicKeyFile\" and val:\n                    entry[\"public_key_file\"] = val\n                elif name == \"RemoteCommand\" and val:\n                    entry[\"remote_command\"] = val\n                elif name == \"PortForwardings\" and val:\n                    entry[\"port_forwardings\"] = val\n                elif name == \"LogFileName\" and val:\n                    entry[\"log_filename\"] = val\n                elif name == \"WinTitle\" and val:\n                    entry[\"window_title\"] = val\n\n            self.entries.append(entry)\n\n    def _parse_ssh_host_keys(self):\n        \"\"\"Parse SSH host keys - evidence of connections made\"\"\"\n        try:\n            hostkeys_key = self.registry_hive.get_key(PUTTY_SSH_HOST_KEYS_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PuTTY SSH host keys at: {PUTTY_SSH_HOST_KEYS_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"ssh_host_keys\",\n            \"key_path\": PUTTY_SSH_HOST_KEYS_PATH,\n            \"last_write\": convert_wintime(hostkeys_key.header.last_modified, as_json=self.as_json),\n            \"hosts\": [],\n        }\n\n        for value in hostkeys_key.iter_values():\n            # Format: algorithm@port:hostname\n            # e.g., rsa2@22:192.168.1.1\n            host_entry = {\"raw_key\": value.name}\n\n            if \"@\" in value.name and \":\" in value.name:\n                try:\n                    algo_port, hostname = value.name.rsplit(\":\", 1)\n                    algo, port = algo_port.split(\"@\", 1)\n                    host_entry[\"algorithm\"] = algo\n                    host_entry[\"port\"] = int(port)\n                    host_entry[\"hostname\"] = hostname\n                except Exception:\n                    pass\n\n            entry[\"hosts\"].append(host_entry)\n\n        if entry[\"hosts\"]:\n            self.entries.append(entry)\n\n    def _parse_jumplist(self):\n        \"\"\"Parse PuTTY jump list entries\"\"\"\n        try:\n            jumplist_key = self.registry_hive.get_key(PUTTY_JUMPLIST_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PuTTY jumplist at: {PUTTY_JUMPLIST_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"jumplist\",\n            \"key_path\": PUTTY_JUMPLIST_PATH,\n            \"last_write\": convert_wintime(jumplist_key.header.last_modified, as_json=self.as_json),\n            \"recent_sessions\": [],\n        }\n\n        for value in jumplist_key.iter_values():\n            if value.name == \"Recent sessions\" and value.value:\n                # Value is a comma-separated list of session names\n                sessions = [s.strip() for s in value.value.split(\",\") if s.strip()]\n                entry[\"recent_sessions\"] = sessions\n\n        if entry[\"recent_sessions\"]:\n            self.entries.append(entry)\n\n    @staticmethod\n    def _get_protocol_name(protocol_id):\n        \"\"\"Convert protocol ID to name\"\"\"\n        protocols = {\n            0: \"Raw\",\n            1: \"Telnet\",\n            2: \"Rlogin\",\n            3: \"SSH\",\n            4: \"Serial\",\n        }\n        return protocols.get(protocol_id, f\"Unknown ({protocol_id})\")\n"
  },
  {
    "path": "regipy/plugins/ntuser/recentdocs.py",
    "content": "\"\"\"\nRecentDocs plugin - Parses recently opened documents from the registry\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nRECENT_DOCS_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs\"\n\n\ndef parse_mru_value(data: bytes) -> Optional[str]:\n    \"\"\"\n    Parse the binary MRU value to extract the filename.\n    The format is: filename (null-terminated unicode) + shell item data\n    \"\"\"\n    if not data:\n        return None\n\n    try:\n        # Find the null terminator for the unicode string\n        null_pos = 0\n        for i in range(0, len(data) - 1, 2):\n            if data[i] == 0 and data[i + 1] == 0:\n                null_pos = i\n                break\n\n        if null_pos > 0:\n            return data[:null_pos].decode(\"utf-16-le\", errors=\"replace\")\n    except Exception:\n        pass\n\n    return None\n\n\nclass RecentDocsPlugin(Plugin):\n    \"\"\"\n    Parses Recently opened documents from NTUSER.DAT\n    Registry Key: Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\RecentDocs\n    \"\"\"\n\n    NAME = \"recentdocs\"\n    DESCRIPTION = \"Parses recently opened documents\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started RecentDocs Plugin...\")\n\n        try:\n            recent_docs_key = self.registry_hive.get_key(RECENT_DOCS_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Could not find {self.NAME} plugin data at: {RECENT_DOCS_PATH}: {ex}\")\n            return\n\n        # Process the main RecentDocs key\n        self._process_recent_docs_key(recent_docs_key, RECENT_DOCS_PATH)\n\n        # Process extension subkeys (e.g., .txt, .docx, .pdf, etc.)\n        for subkey in recent_docs_key.iter_subkeys():\n            subkey_path = f\"{RECENT_DOCS_PATH}\\\\{subkey.name}\"\n            self._process_recent_docs_key(subkey, subkey_path, extension=subkey.name)\n\n    def _process_recent_docs_key(self, key, key_path: str, extension: str = None):\n        \"\"\"Process a RecentDocs key and extract document entries\"\"\"\n        entry = {\n            \"key_path\": key_path,\n            \"last_write\": convert_wintime(key.header.last_modified, as_json=self.as_json),\n            \"extension\": extension,\n            \"documents\": [],\n        }\n\n        mru_list = None\n        mru_values = {}\n\n        for value in key.iter_values():\n            if value.name == \"MRUListEx\":\n                # MRUListEx contains the order of recently accessed items\n                # It's an array of DWORDs representing indices\n                mru_list = value.value\n            elif value.name.isdigit():\n                # Numeric values contain the actual document data\n                parsed_name = parse_mru_value(value.value)\n                if parsed_name:\n                    mru_values[int(value.name)] = parsed_name\n\n        # Build ordered list of documents based on MRUListEx\n        if mru_list and mru_values:\n            # MRUListEx is a binary blob of 4-byte integers\n            for i in range(0, len(mru_list) - 4, 4):\n                index = int.from_bytes(mru_list[i : i + 4], \"little\", signed=True)\n                if index == -1:  # End marker\n                    break\n                if index in mru_values:\n                    entry[\"documents\"].append({\"index\": index, \"name\": mru_values[index]})\n\n        if entry[\"documents\"]:\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/ntuser/runmru.py",
    "content": "\"\"\"\nRunMRU plugin - Parses Run dialog history\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nRUN_MRU_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RunMRU\"\n\n\nclass RunMRUPlugin(Plugin):\n    \"\"\"\n    Parses Run dialog MRU (Most Recently Used) list from NTUSER.DAT\n    Registry Key: Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\RunMRU\n    \"\"\"\n\n    NAME = \"runmru\"\n    DESCRIPTION = \"Parses Run dialog MRU list\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started RunMRU Plugin...\")\n\n        try:\n            runmru_key = self.registry_hive.get_key(RUN_MRU_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Could not find {self.NAME} plugin data at: {RUN_MRU_PATH}: {ex}\")\n            return\n\n        entry = {\n            \"key_path\": RUN_MRU_PATH,\n            \"last_write\": convert_wintime(runmru_key.header.last_modified, as_json=self.as_json),\n            \"mru_order\": None,\n            \"commands\": [],\n        }\n\n        mru_list = None\n        mru_values = {}\n\n        for value in runmru_key.iter_values():\n            if value.name == \"MRUList\":\n                # MRUList contains the order as a string of letters (e.g., \"dcba\")\n                mru_list = value.value\n            elif len(value.name) == 1 and value.name.isalpha():\n                # Single letter values (a, b, c, etc.) contain the commands\n                # Commands end with \\1 which indicates the command was typed\n                command = value.value\n                if command and isinstance(command, str):\n                    # Remove the trailing \\1 marker if present\n                    command = command.rstrip(\"\\x01\")\n                    mru_values[value.name] = command\n\n        if mru_list:\n            entry[\"mru_order\"] = mru_list\n\n        # Build ordered list based on MRUList\n        if mru_list and mru_values:\n            for letter in mru_list:\n                if letter in mru_values:\n                    entry[\"commands\"].append({\"letter\": letter, \"command\": mru_values[letter]})\n\n        if entry[\"commands\"] or entry[\"mru_order\"]:\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/ntuser/shellbags_ntuser.py",
    "content": "import logging\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\nfrom regipy.constants import KNOWN_GUIDS\n\nlogger = logging.getLogger(__name__)\n\nNTUSER_SHELLBAG = \"\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\BagMRU\"\nDEFAULT_CODEPAGE = \"cp1252\"\n\n\nclass ShellBagNtuserPlugin(Plugin):\n    NAME = \"ntuser_shellbag_plugin\"\n    DESCRIPTION = \"Parse NTUSER Shellbag items\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    @staticmethod\n    def _parse_mru(mru_val):\n        mru_order_string = \"\"\n        if isinstance(mru_val, bytes):\n            mru_val = mru_val[:-4]\n            for i in range(0, len(mru_val), 4):\n                current_val = int.from_bytes(mru_val[i : i + 4], byteorder=\"little\")\n                mru_order_string += f\"{current_val}-\"\n\n            return mru_order_string[:-1]\n        else:\n            return mru_order_string\n\n    @staticmethod\n    def _get_shell_item_type(shell_item):\n        try:\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        if isinstance(shell_item, pyfwsi.volume):\n            item_type = \"Volume\"\n\n        elif isinstance(shell_item, pyfwsi.file_entry):\n            item_type = \"Directory\"\n\n        elif isinstance(shell_item, pyfwsi.network_location):\n            item_type = \"Network Location\"\n\n        elif isinstance(shell_item, pyfwsi.root_folder):\n            item_type = \"Root Folder\"\n\n        elif isinstance(shell_item, pyfwsi.control_panel_category):\n            item_type = \"Control Panel Category\"\n\n        elif isinstance(shell_item, pyfwsi.control_panel_item):\n            item_type = \"Control Panel Item\"\n\n        elif isinstance(shell_item, pyfwsi.users_property_view):\n            item_type = \"Users Property View\"\n\n        else:\n            item_type = \"unknown\"\n\n        return item_type\n\n    @staticmethod\n    def _check_known_guids(guid):\n        if guid in KNOWN_GUIDS:\n            path_segment = KNOWN_GUIDS[guid]\n        else:\n            path_segment = \"{{{0:s}}}\".format(guid)\n        return path_segment\n\n    @staticmethod\n    def _get_entry_string(fwps_record):\n        if fwps_record.entry_name:\n            entry_string = fwps_record.entry_name\n        else:\n            entry_string = f\"{fwps_record.entry_type:d}\"\n        return entry_string\n\n    @staticmethod\n    def _create_entry(\n        value,\n        slot,\n        reg_path,\n        value_name,\n        node_slot,\n        shell_type,\n        path,\n        full_path=None,\n        location_description=None,\n        creation_time=None,\n        access_time=None,\n        modification_time=None,\n        last_write=None,\n        mru_order=None,\n        mru_order_location=None,\n        first_interacted=None,\n    ):\n        return {\n            \"value\": value,\n            \"slot\": slot,\n            \"reg_path\": reg_path,\n            \"value_name\": value_name,\n            \"node_slot\": node_slot,\n            \"shell_type\": shell_type,\n            \"path\": path,\n            \"full path\": full_path,\n            \"location description\": location_description,\n            \"creation_time\": creation_time,\n            \"access_time\": access_time,\n            \"modification_time\": modification_time,\n            \"last_write\": last_write,\n            \"mru_order\": mru_order,\n            \"mru_order_location\": mru_order_location,\n            \"first_interacted\": first_interacted,\n        }\n\n    @staticmethod\n    def _parse_shell_item_path_segment(self, shell_item):\n        \"\"\"Parses a shell item path segment.\n        Args:\n          shell_item (pyfwsi.item): shell item.\n        Returns:\n          str: shell item path segment.\n        \"\"\"\n\n        try:\n            import pyfwsi\n            import pyfwps\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                f\"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                f\" `pip install regipy[full]` in order to install plugin dependencies. \"\n                f\"This might take some time... \"\n            )\n            raise ex\n\n        path_segment = None\n        full_path = None\n        location_description = None\n\n        if isinstance(shell_item, pyfwsi.volume):\n            if shell_item.name:\n                path_segment = shell_item.name\n            elif shell_item.identifier:\n                path_segment = self._check_known_guids(shell_item.identifier)\n\n        elif isinstance(shell_item, pyfwsi.file_entry):\n            long_name = \"\"\n            for extension_block in shell_item.extension_blocks:\n                if isinstance(extension_block, pyfwsi.file_entry_extension):\n                    long_name = extension_block.long_name\n\n            if long_name:\n                path_segment = long_name\n            elif shell_item.name:\n                path_segment = shell_item.name\n\n        elif isinstance(shell_item, pyfwsi.network_location):\n            if shell_item.location:\n                path_segment = shell_item.location\n            if shell_item.description:\n                location_description = shell_item.description\n                if shell_item.comments:\n                    location_description += f\", {shell_item.comments}\"\n\n        elif isinstance(shell_item, pyfwsi.root_folder):\n            if shell_item.shell_folder_identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.shell_folder_identifier]\n            elif hasattr(shell_item, \"identifier\") and shell_item.identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.identifier]\n            else:\n                path_segment = \"{{{0:s}}}\".format(shell_item.shell_folder_identifier)\n\n        elif isinstance(shell_item, pyfwsi.users_property_view):\n            # Users property view\n            if shell_item.delegate_folder_identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.delegate_folder_identifier]\n            elif hasattr(shell_item, \"identifier\") and shell_item.identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.identifier]\n\n            # Variable: Users property view\n            elif shell_item.property_store_data:\n                fwps_store = pyfwps.store()\n                fwps_store.copy_from_byte_stream(shell_item.property_store_data)\n\n                for fwps_set in iter(fwps_store.sets):\n                    if fwps_set.identifier == \"b725f130-47ef-101a-a5f1-02608c9eebac\":\n                        for fwps_record in iter(fwps_set.records):\n                            entry_string = self._get_entry_string(fwps_record)\n\n                            # PKEY_DisplayName: {b725f130-47ef-101a-a5f1-02608c9eebac}/10\n                            if entry_string == \"10\":\n                                if fwps_record.value_type == 0x0001:\n                                    value_string = \"<VT_NULL>\"\n                                elif fwps_record.value_type in (\n                                    0x0003,\n                                    0x0013,\n                                    0x0014,\n                                    0x0015,\n                                ):\n                                    value_string = str(fwps_record.get_data_as_integer())\n                                elif fwps_record.value_type in (0x0008, 0x001E, 0x001F):\n                                    value_string = fwps_record.get_data_as_string()\n                                elif fwps_record.value_type == 0x000B:\n                                    value_string = str(fwps_record.get_data_as_boolean())\n                                elif fwps_record.value_type == 0x0040:\n                                    filetime = fwps_record.get_data_as_integer()\n                                    value_string = self._FormatFiletimeValue(filetime)\n                                elif fwps_record.value_type == 0x0042:\n                                    # TODO: add support\n                                    value_string = \"<VT_STREAM>\"\n                                elif fwps_record.value_type == 0x0048:\n                                    value_string = fwps_record.get_data_as_guid()\n                                elif fwps_record.value_type & 0xF000 == 0x1000:\n                                    # TODO: add support\n                                    value_string = \"<VT_VECTOR>\"\n                                else:\n                                    value_string = None\n\n                                path_segment = value_string\n\n                    elif fwps_set.identifier == \"28636aa6-953d-11d2-b5d6-00c04fd918d0\":\n                        for fwps_record in iter(fwps_set.records):\n                            entry_string = self._get_entry_string(fwps_record)\n\n                            # PKEY_ParsingPath: {28636aa6-953d-11d2-b5d6-00c04fd918d0}/30\n                            if entry_string == \"30\":\n                                full_path = fwps_record.get_data_as_string()\n\n        elif isinstance(shell_item, pyfwsi.control_panel_category):\n            path_segment = self._check_known_guids(str(shell_item.identifier))\n\n        elif isinstance(shell_item, pyfwsi.control_panel_item):\n            path_segment = self._check_known_guids(shell_item.identifier)\n\n        if path_segment is None:\n            path_segment = \"<UNKNOWN: 0x{0:02x}>\".format(shell_item.class_type)\n\n        return path_segment, full_path, location_description\n\n    def iter_sk(self, key, reg_path, codepage=DEFAULT_CODEPAGE, base_path=\"\", path=\"\"):\n        try:\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                f\"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                f\" `pip install regipy[full]` in order to install plugin dependencies. \"\n                f\"This might take some time... \"\n            )\n            raise ex\n\n        last_write = convert_wintime(key.header.last_modified, as_json=True)\n\n        mru_val = key.get_value(\"MRUListEx\")\n        mru_order = self._parse_mru(mru_val)\n        base_path = path\n\n        if key.get_value(\"NodeSlot\"):\n            node_slot = str(key.get_value(\"NodeSlot\"))\n        else:\n            node_slot = \"\"\n\n        processed_values = set()\n        for v in key.iter_values(trim_values=False):\n            if v.name.isdigit():\n                processed_values.add(v.name)\n                slot = v.name\n                byte_stream = v.value\n                shell_items = pyfwsi.item_list()\n                shell_items.copy_from_byte_stream(byte_stream, ascii_codepage=codepage)\n                for item in shell_items.items:\n                    shell_type = self._get_shell_item_type(item)\n                    value, full_path, location_description = self._parse_shell_item_path_segment(self, item)\n                    if not path:\n                        path = value\n                        base_path = \"\"\n                    else:\n                        path += f\"\\\\{value}\"\n\n                    creation_time = None\n                    access_time = None\n                    modification_time = None\n\n                    if len(item.extension_blocks) > 0:\n                        for extension_block in item.extension_blocks:\n                            if isinstance(extension_block, pyfwsi.file_entry_extension):\n                                try:\n                                    creation_time = extension_block.get_creation_time()\n                                    if self.as_json:\n                                        creation_time = creation_time.isoformat()\n                                except OSError:\n                                    logger.exception(f\"Malformed creation time for {path}\")\n                                try:\n                                    access_time = extension_block.get_access_time()\n                                    if self.as_json:\n                                        access_time = access_time.isoformat()\n                                except OSError:\n                                    logger.exception(f\"Malformed access time for {path}\")\n\n                    try:\n                        if hasattr(item, \"modification_time\"):\n                            modification_time = item.get_modification_time()\n                            if self.as_json:\n                                modification_time = modification_time.isoformat()\n                    except OSError:\n                        logger.exception(f\"Malformed modification time for {path}\")\n\n                    value_name = v.name\n                    mru_order_location = mru_order.split(\"-\").index(value_name)\n                    self.entries.append(\n                        self._create_entry(\n                            value=value,\n                            slot=slot,\n                            reg_path=reg_path,\n                            value_name=value_name,\n                            node_slot=node_slot,\n                            shell_type=shell_type,\n                            path=path,\n                            full_path=full_path,\n                            location_description=location_description,\n                            creation_time=creation_time,\n                            access_time=access_time,\n                            modification_time=modification_time,\n                            last_write=last_write,\n                            mru_order=mru_order,\n                            mru_order_location=mru_order_location,\n                        )\n                    )\n                    sk_reg_path = f\"{reg_path}\\\\{value_name}\"\n                    try:\n                        sk = self.registry_hive.get_key(sk_reg_path)\n                        self.iter_sk(sk, sk_reg_path, codepage, base_path, path)\n                    except RegistryKeyNotFoundException:\n                        pass  # Subkey doesn't exist, continue\n                    path = base_path\n\n        # Issue #268: Handle childless subkeys - subkeys without corresponding numbered\n        # values contain \"first interacted\" timestamps for when a folder was initially accessed\n        for subkey in key.iter_subkeys():\n            if subkey.name not in processed_values:\n                childless_last_write = convert_wintime(subkey.header.last_modified, as_json=True)\n                self.entries.append(\n                    self._create_entry(\n                        value=None,\n                        slot=subkey.name,\n                        reg_path=f\"{reg_path}\\\\{subkey.name}\",\n                        value_name=subkey.name,\n                        node_slot=str(subkey.get_value(\"NodeSlot\")) if subkey.get_value(\"NodeSlot\") else \"\",\n                        shell_type=\"Childless\",\n                        path=path or None,\n                        last_write=childless_last_write,\n                        first_interacted=childless_last_write,\n                    )\n                )\n                self.iter_sk(subkey, f\"{reg_path}\\\\{subkey.name}\", codepage, base_path, path)\n\n    def run(self, codepage=DEFAULT_CODEPAGE):\n        try:\n            # flake8: noqa\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        try:\n            shellbag_ntuser_subkey = self.registry_hive.get_key(NTUSER_SHELLBAG)\n            self.iter_sk(shellbag_ntuser_subkey, NTUSER_SHELLBAG, codepage=codepage)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {NTUSER_SHELLBAG}: {ex}\")\n"
  },
  {
    "path": "regipy/plugins/ntuser/sysinternals.py",
    "content": "\"\"\"\nSysinternals plugin - Parses Sysinternals tools EULA acceptance records\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSYSINTERNALS_PATH = r\"\\Software\\Sysinternals\"\n\n\nclass SysinternalsPlugin(Plugin):\n    \"\"\"\n    Parses Sysinternals EULA acceptance records from NTUSER.DAT\n    Registry Key: Software\\\\Sysinternals\n\n    When a Sysinternals tool is run and the EULA is accepted,\n    it creates a subkey with the tool name containing an EulaAccepted value.\n    This provides evidence of which Sysinternals tools have been executed.\n    \"\"\"\n\n    NAME = \"sysinternals\"\n    DESCRIPTION = \"Parses Sysinternals tools EULA acceptance\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Sysinternals Plugin...\")\n\n        try:\n            sysinternals_key = self.registry_hive.get_key(SYSINTERNALS_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Could not find {self.NAME} plugin data at: {SYSINTERNALS_PATH}: {ex}\")\n            return\n\n        for subkey in sysinternals_key.iter_subkeys():\n            tool_name = subkey.name\n            subkey_path = f\"{SYSINTERNALS_PATH}\\\\{tool_name}\"\n\n            entry = {\n                \"key_path\": subkey_path,\n                \"tool_name\": tool_name,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                \"eula_accepted\": False,\n            }\n\n            for value in subkey.iter_values():\n                if value.name == \"EulaAccepted\":\n                    entry[\"eula_accepted\"] = value.value == 1\n\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/ntuser/tsclient.py",
    "content": "import logging\n\nfrom regipy import (\n    NoRegistrySubkeysException,\n    RegistryKeyNotFoundException,\n    convert_wintime,\n)\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nTSCLIENT_HISTORY_PATH = r\"\\Software\\Microsoft\\Terminal Server Client\\Servers\"\n\n\nclass TSClientPlugin(Plugin):\n    NAME = \"terminal_services_history\"\n    DESCRIPTION = \"Retrieve history of RDP connections\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            tsclient_subkey = self.registry_hive.get_key(TSCLIENT_HISTORY_PATH)\n        except (RegistryKeyNotFoundException, NoRegistrySubkeysException) as ex:\n            logger.error(ex)\n            return\n\n        for server in tsclient_subkey.iter_subkeys():\n            self.entries.append(\n                {\n                    \"server\": server.name,\n                    \"last_connection\": convert_wintime(server.header.last_modified, as_json=self.as_json),\n                    \"username_hint\": server.get_value(\"UsernameHint\"),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/ntuser/typed_paths.py",
    "content": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nTYPED_PATHS_KEY_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\TypedPaths\"\n\n\nclass TypedPathsPlugin(Plugin):\n    NAME = \"typed_paths\"\n    DESCRIPTION = \"Retrieve the typed Paths from the history\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            subkey = self.registry_hive.get_key(TYPED_PATHS_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {TYPED_PATHS_KEY_PATH}: {ex}\")\n            return None\n\n        self.entries = {\n            \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            \"entries\": [{underscore(x.name): x.value} for x in subkey.iter_values(as_json=self.as_json)],\n        }\n"
  },
  {
    "path": "regipy/plugins/ntuser/typed_urls.py",
    "content": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nTYPED_URLS_KEY_PATH = r\"\\Software\\Microsoft\\Internet Explorer\\TypedURLs\"\n\n\nclass TypedUrlsPlugin(Plugin):\n    NAME = \"typed_urls\"\n    DESCRIPTION = \"Retrieve the typed URLs from IE history\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            subkey = self.registry_hive.get_key(TYPED_URLS_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {TYPED_URLS_KEY_PATH}: {ex}\")\n            return None\n\n        self.entries = {\n            \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            \"entries\": [{underscore(x.name): x.value} for x in subkey.iter_values(as_json=self.as_json)],\n        }\n"
  },
  {
    "path": "regipy/plugins/ntuser/user_assist.py",
    "content": "import codecs\nimport logging\n\nfrom construct import Bytes, Const, ConstError, Int32ul, Int64ul, Struct\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nUSER_ASSIST_KEY_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\"\n\n# guids for the various Operating Systems\nGUIDS = [\n    \"{75048700-EF1F-11D0-9888-006097DEACF9}\",  # Windows XP GUIDs\n    \"{5E6AB780-7743-11CF-A12B-00AA004AE837}\",\n    \"{75048700-EF1F-11D0-9888-006097DEACF9}\",  # Windows vista\n    \"{5E6AB780-7743-11CF-A12B-00AA004AE837}\",\n    \"{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\",  # Windows 7\n    \"{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\",\n    \"{FA99DFC7-6AC2-453A-A5E2-5E2AFF4507BD}\",  # Windows 8\n    \"{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\",\n    \"{F2A1CB5A-E3CC-4A2E-AF9D-505A7009D442}\",\n    \"{CEBFF5CD-ACE2-4F4F-9178-9926F41749EA}\",\n    \"{CAA59E3C-4792-41A5-9909-6A6A8D32490E}\",\n    \"{B267E3AD-A825-4A09-82B9-EEC22AA3B847}\",\n    \"{A3D53349-6E61-4557-8FC7-0028EDCEEBF6}\",\n    \"{9E04CAB2-CC14-11DF-BB8C-A2F1DED72085}\",\n]\n\nGUID_TO_PATH_MAPPINGS = {\n    \"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\": r\"%SYSTEM32%\",\n    \"{6D809377-6AF0-444B-8957-A3773F02200E}\": r\"%PROGRAMFILES%\",\n    \"{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}\": r\"%PROGRAMFILES(X86)%\",\n    \"{F38BF404-1D43-42F2-9305-67DE0B28FC23}\": r\"%WINDIR%\",\n    \"{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}\": r\"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\",\n    \"{9E3995AB-1F9C-4F13-B827-48B24B6C7174}\": r\"%AppData%\\Roaming\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\",\n    \"{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}\": r\"%AppData%\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\",\n    \"{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}\": r\"%WINDIR%\\SysWOW64\",\n}\n\nWHITELISTED_NAMES = [\"UEME_CTLSESSION\"]\n\nWIN_XP_USER_ASSIST = Struct(\n    \"session_id\" / Int32ul,\n    \"run_counter\" / Int32ul,\n    \"last_execution_timestamp\" / Int64ul,\n)\n\nWIN7_USER_ASSIST = Struct(\n    \"session_id\" / Int32ul,\n    \"run_counter\" / Int32ul,\n    \"focus_count\" / Int32ul,\n    \"total_focus_time_ms\" / Int32ul,\n    \"unknown\" * Bytes(44),\n    \"last_execution_timestamp\" / Int64ul,\n    \"Const\" * Const(b\"\\x00\\x00\\x00\\x00\"),\n)\n\n\nclass UserAssistPlugin(Plugin):\n    NAME = \"user_assist\"\n    DESCRIPTION = \"Parse User Assist artifact\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        for guid in GUIDS:\n            try:\n                subkey = self.registry_hive.get_key(rf\"{USER_ASSIST_KEY_PATH}\\{guid}\")\n                count_subkey = subkey.get_subkey(\"Count\")\n\n                if not count_subkey.values_count:\n                    logger.debug(f\"Skipping {guid}\")\n                    continue\n\n                for value in count_subkey.iter_values(trim_values=False):\n                    name = codecs.decode(value.name, encoding=\"rot-13\")\n\n                    if name in WHITELISTED_NAMES:\n                        continue\n\n                    for k, v in GUID_TO_PATH_MAPPINGS.items():\n                        if k in name:\n                            name = name.replace(k, v)\n                            break\n\n                    entry = None\n                    data = value.value\n                    if len(data) == 72:\n                        try:\n                            parsed_entry = WIN7_USER_ASSIST.parse(data)\n                        except ConstError as ex:\n                            logger.error(f\"Could not parse user assist entry named {name}: {ex}\")\n                            continue\n\n                        entry = {\n                            \"name\": name,\n                            \"timestamp\": convert_wintime(\n                                parsed_entry.last_execution_timestamp,\n                                as_json=self.as_json,\n                            ),\n                            \"run_counter\": parsed_entry.run_counter,\n                            \"focus_count\": parsed_entry.focus_count,\n                            \"total_focus_time_ms\": parsed_entry.total_focus_time_ms,\n                            \"session_id\": parsed_entry.session_id,\n                        }\n\n                    elif len(data) == 16:\n                        try:\n                            parsed_entry = WIN_XP_USER_ASSIST.parse(data)\n                        except ConstError as ex:\n                            logger.error(f\"Could not parse user assist entry named {name}: {ex}\")\n                            continue\n\n                        entry = {\n                            \"name\": name,\n                            \"timestamp\": convert_wintime(\n                                parsed_entry.last_execution_timestamp,\n                                as_json=self.as_json,\n                            ),\n                            \"session_id\": parsed_entry.session_id,\n                            \"run_counter\": parsed_entry.run_counter - 5,\n                        }\n\n                    if entry:\n                        self.entries.append(entry)\n            except RegistryKeyNotFoundException:\n                continue\n"
  },
  {
    "path": "regipy/plugins/ntuser/winrar.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nWINRAR_ARCHIVE_CREATION_HIST = r\"\\SOFTWARE\\WinRAR\\DialogEditHistory\\ArcName\"\nWINRAR_ARCHIVE_EXTRACT_HIST = r\"\\SOFTWARE\\WinRAR\\DialogEditHistory\\ExtrPath\"\nWINRAR_ARCHIVE_OPEN_HIST = r\"\\SOFTWARE\\WinRAR\\ArcHistory\"\n\n\nclass WinRARPlugin(Plugin):\n    NAME = \"winrar_plugin\"\n    DESCRIPTION = \"Parse the WinRAR archive history\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            open_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_OPEN_HIST)\n\n            timestamp = convert_wintime(open_subkey.header.last_modified, as_json=self.as_json)\n            for value in open_subkey.iter_values(as_json=self.as_json):\n                self.entries.append(\n                    {\n                        \"last_write\": timestamp,\n                        \"file_path\": value.value,\n                        \"value_name\": value.name,\n                        \"operation\": \"archive_opened\",\n                    }\n                )\n\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_OPEN_HIST}: {ex}\")\n\n        try:\n            create_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_CREATION_HIST)\n\n            timestamp = convert_wintime(create_subkey.header.last_modified, as_json=self.as_json)\n            for value in create_subkey.iter_values(as_json=self.as_json):\n                self.entries.append(\n                    {\n                        \"last_write\": timestamp,\n                        \"file_name\": value.value,\n                        \"value_name\": value.name,\n                        \"operation\": \"archive_created\",\n                    }\n                )\n\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_CREATION_HIST}: {ex}\")\n\n        try:\n            extract_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_EXTRACT_HIST)\n\n            timestamp = convert_wintime(extract_subkey.header.last_modified, as_json=self.as_json)\n            for value in extract_subkey.iter_values(as_json=self.as_json):\n                self.entries.append(\n                    {\n                        \"last_write\": timestamp,\n                        \"file_path\": value.value,\n                        \"value_name\": value.name,\n                        \"operation\": \"archive_extracted\",\n                    }\n                )\n\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_EXTRACT_HIST}: {ex}\")\n"
  },
  {
    "path": "regipy/plugins/ntuser/winscp_saved_sessions.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nWINSCP_SAVED_SESSIONS_PATH = r\"\\Software\\Martin Prikryl\\WinSCP 2\\Sessions\"\n\n\nclass WinSCPSavedSessionsPlugin(Plugin):\n    NAME = \"winscp_saved_sessions\"\n    DESCRIPTION = \"Retrieve list of WinSCP saved sessions\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def _get_winscp_saved_sessions(self, subkey_path):\n        try:\n            sessions_sk = self.registry_hive.get_key(subkey_path)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for winscp_saved_session in sessions_sk.iter_subkeys():\n            values = (\n                {x.name: x.value for x in winscp_saved_session.iter_values(as_json=self.as_json)}\n                if winscp_saved_session.values_count\n                else {}\n            )\n            self.entries.append(\n                {\n                    \"timestamp\": convert_wintime(winscp_saved_session.header.last_modified, as_json=self.as_json),\n                    \"hive_name\": \"HKEY_CURRENT_USER\",\n                    \"key_path\": rf\"HKEY_CURRENT_USER{subkey_path}\\{winscp_saved_session.name}\",\n                    **values,\n                }\n            )\n\n    def run(self):\n        self._get_winscp_saved_sessions(WINSCP_SAVED_SESSIONS_PATH)\n"
  },
  {
    "path": "regipy/plugins/ntuser/word_wheel_query.py",
    "content": "import logging\n\nfrom construct import CString, GreedyRange, Int32ul\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nWORD_WHEEL_QUERY_KEY_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\WordWheelQuery\"\n\n\nclass WordWheelQueryPlugin(Plugin):\n    NAME = \"word_wheel_query\"\n    DESCRIPTION = \"Parse the word wheel query artifact\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def run(self):\n        try:\n            subkey = self.registry_hive.get_key(WORD_WHEEL_QUERY_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {WORD_WHEEL_QUERY_KEY_PATH}: {ex}\")\n            return None\n\n        timestamp = convert_wintime(subkey.header.last_modified, as_json=self.as_json)\n\n        mru_list_order = subkey.get_value(\"MRUListEx\")\n\n        # If this is the value, the list is empty\n        if mru_list_order == 0xFFFFFFFF:\n            return None\n\n        for i, entry_name in enumerate(GreedyRange(Int32ul).parse(mru_list_order)):\n            entry_value = subkey.get_value(str(entry_name))\n\n            if not entry_value:\n                continue\n\n            self.entries.append(\n                {\n                    \"last_write\": timestamp,\n                    \"mru_id\": entry_name,\n                    \"order\": i,\n                    \"name\": CString(\"utf-16\").parse(entry_value),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/ntuser/wsl.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# Ressources : https://patrickwu.space/2020/07/19/wsl-related-registry/\n\nWSL_PATH = r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss\"\n\n\nclass WSLPlugin(Plugin):\n    NAME = \"wsl\"\n    DESCRIPTION = \"Get WSL information\"\n    COMPATIBLE_HIVE = NTUSER_HIVE_TYPE\n\n    def get_wsl_info(self, subkey, distribs=None):\n        if distribs is None:\n            distribs = []\n\n        try:\n            flags = subkey.get_value(\"Flags\")\n            state = subkey.get_value(\"State\")\n            version = subkey.get_value(\"Version\")\n\n            # Initialize the entry for a distribution with its GUID as the key\n            distribution_entry = {\n                \"GUID\": subkey.name,\n                \"last_modified\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                \"wsl_distribution_source_location\": subkey.get_value(\"BasePath\"),\n                \"default_uid\": subkey.get_value(\"DefaultUid\"),\n                \"distribution_name\": subkey.get_value(\"DistributionName\"),\n                \"default_environment\": subkey.get_value(\"DefaultEnvironment\"),  # REG_MULTI_SZ\n                \"flags\": flags,\n                \"kernel_command_line\": subkey.get_value(\"KernelCommandLine\"),\n                \"package_family_name\": subkey.get_value(\"PackageFamilyName\"),\n                \"state\": state,\n                \"filesystem\": (\"lxfs\" if version == 1 else \"wslfs\" if version == 2 else \"Unknown\"),\n            }\n\n            # Decode flags for additional information\n            if flags is not None:\n                distribution_entry[\"enable_interop\"] = bool(flags & 0x1)\n                distribution_entry[\"append_nt_path\"] = bool(flags & 0x2)\n                distribution_entry[\"enable_drive_mounting\"] = bool(flags & 0x4)\n\n            # Decode the state of the distribution\n            if state is not None:\n                if state == 0x1:\n                    distribution_entry[\"state\"] = \"Normal\"\n                elif state == 0x3:\n                    distribution_entry[\"state\"] = \"Installing\"\n                elif state == 0x4:\n                    distribution_entry[\"state\"] = \"Uninstalling\"\n                else:\n                    distribution_entry[\"state\"] = \"Unknown\"\n\n            # Add the distribution entry with its GUID to the list of distributions\n            distribs.append(distribution_entry)\n\n        except Exception as e:\n            logger.error(f\"Error processing subkey {subkey.name}: {e}\")\n            raise\n\n        return distribs\n\n    def run(self):\n        try:\n            # Attempt to get the WSL registry key\n            wsl_key = self.registry_hive.get_key(WSL_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Registry key not found at path {WSL_PATH}: {ex}\")\n            return\n\n        self.entries = {\n            WSL_PATH: {\n                \"last_modified\": convert_wintime(wsl_key.header.last_modified, as_json=self.as_json),\n                \"number_of_distrib\": wsl_key.header.subkey_count,\n                \"default_distrib_GUID\": wsl_key.get_value(\"DefaultDistribution\"),\n                \"wsl_version\": (\n                    \"WSL1\"\n                    if wsl_key.get_value(\"DefaultVersion\") == 1\n                    else (\"WSL2\" if wsl_key.get_value(\"DefaultVersion\") == 2 else \"Unknown\")\n                ),\n                \"nat_ip_address\": wsl_key.get_value(\"NatIpAddress\"),\n                \"distributions\": [],\n            }\n        }\n\n        try:\n            for distrib in wsl_key.iter_subkeys():\n                self.get_wsl_info(distrib, self.entries[WSL_PATH][\"distributions\"])\n        except Exception as e:\n            logger.error(f\"Error iterating over subkeys in {distrib.path}: {e}\")\n"
  },
  {
    "path": "regipy/plugins/plugin.py",
    "content": "import logging\nfrom typing import Any\n\nfrom regipy.registry import RegistryHive\n\nPLUGINS = set()\n\nlogger = logging.getLogger(__name__)\n\n\nclass Plugin:\n    NAME: str = None\n    DESCRIPTION: str = None\n    COMPATIBLE_HIVE: str = None\n\n    def __init_subclass__(cls):\n        PLUGINS.add(cls)\n\n    def __init__(self, registry_hive: RegistryHive, as_json=False, trim_values=False):\n        self.registry_hive = registry_hive\n        self.as_json = as_json\n        self.trim_values = trim_values\n\n        self.partial_hive_path = registry_hive.partial_hive_path\n\n        # This variable should always hold the final result - in order to use it in anomaly detection and timeline gen.\n        self.entries: list[dict[str, Any]] = []\n\n    def can_run(self):\n        \"\"\"\n        Wether the plugin can run or not, according to specific checks\n        :return:\n        \"\"\"\n        return self.registry_hive.hive_type == self.COMPATIBLE_HIVE\n\n    def run(self):\n        \"\"\"\n        Execute the plugin\n        :return:\n        \"\"\"\n\n    def generate_timeline_artifacts(self):\n        \"\"\"\n        Run on the output of a plugin and generate timeline entries\n        :return:\n        \"\"\"\n        pass\n\n    def detect_anomalies(self):\n        \"\"\"\n        Run on the output of a plugin and detect possible anomalies\n        :return:\n        \"\"\"\n        pass\n"
  },
  {
    "path": "regipy/plugins/plugin_template.py",
    "content": "import logging\n\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nclass TemplatePlugin(Plugin):\n    NAME = \"template_plugin\"\n    DESCRIPTION = \"template_description\"\n\n    def can_run(self):\n        # TODO: Choose the relevant condition - to determine if the plugin is relevant for the given hive\n        return self.registry_hive.hive_type == NTUSER_HIVE_TYPE\n\n    def run(self):\n        # TODO: Return the relevant values\n        raise NotImplementedError\n"
  },
  {
    "path": "regipy/plugins/sam/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/sam/local_sid.py",
    "content": "\"\"\"\nWindows machine local SID extractor plugin\n\"\"\"\n\nimport logging\n\nfrom regipy.hive_types import SAM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.security_utils import convert_sid\nfrom regipy.structs import SID\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nACCOUNT_PATH = r\"\\SAM\\Domains\\Account\"\n\n\nclass LocalSidPlugin(Plugin):\n    \"\"\"\n    Windows machine local SID extractor\n    \"\"\"\n\n    NAME = \"local_sid\"\n    DESCRIPTION = \"Get the machine local SID\"\n    COMPATIBLE_HIVE = SAM_HIVE_TYPE\n\n    def run(self) -> None:\n        logger.debug(\"Started Machine Local SID Plugin...\")\n\n        account_key = self.registry_hive.get_key(ACCOUNT_PATH)\n\n        # A computer's SID is stored in the SECURITY hive\n        # under 'SECURITY\\SAM\\Domains\\Account'.\n        # This key has a value named 'F' and a value named 'V'.\n        v_value = account_key.get_value(\"V\")\n\n        # The 'V' value is a binary value that has the computer SID embedded\n        # within it at the end of its data.\n        sid_value = v_value[-24:]\n\n        parsed_sid = SID.parse(sid_value)\n\n        self.entries.append(\n            {\n                \"machine_sid\": convert_sid(parsed_sid),\n                \"timestamp\": convert_wintime(account_key.header.last_modified, as_json=self.as_json),\n            }\n        )\n"
  },
  {
    "path": "regipy/plugins/sam/samparse.py",
    "content": "\"\"\"\nSAM Parse plugin - Parses user account information from SAM hive\n\"\"\"\n\nimport contextlib\nimport logging\nimport struct\nfrom datetime import datetime\nfrom typing import Optional\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SAM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSAM_USERS_PATH = r\"\\SAM\\Domains\\Account\\Users\"\nSAM_NAMES_PATH = r\"\\SAM\\Domains\\Account\\Users\\Names\"\n\n# Account type flags\nACCOUNT_FLAGS = {\n    0x0001: \"Account Disabled\",\n    0x0002: \"Home Directory Required\",\n    0x0004: \"Password Not Required\",\n    0x0008: \"Temp Duplicate Account\",\n    0x0010: \"Normal User Account\",\n    0x0020: \"MNS Logon Account\",\n    0x0040: \"Interdomain Trust Account\",\n    0x0080: \"Workstation Trust Account\",\n    0x0100: \"Server Trust Account\",\n    0x0200: \"Password Does Not Expire\",\n    0x0400: \"Account Auto Locked\",\n    0x0800: \"Encrypted Text Password Allowed\",\n    0x1000: \"Smartcard Required\",\n    0x2000: \"Trusted For Delegation\",\n    0x4000: \"Not Delegated\",\n    0x8000: \"Use DES Key Only\",\n    0x10000: \"Preauth Not Required\",\n    0x20000: \"Password Expired\",\n    0x40000: \"Trusted To Auth For Delegation\",\n    0x80000: \"No Auth Data Required\",\n    0x100000: \"Partial Secrets Account\",\n}\n\n\ndef filetime_to_datetime(filetime: int) -> Optional[str]:\n    \"\"\"Convert Windows FILETIME to ISO datetime string\"\"\"\n    if filetime == 0 or filetime == 0x7FFFFFFFFFFFFFFF:\n        return None\n    try:\n        # FILETIME is 100-nanosecond intervals since January 1, 1601\n        epoch_diff = 116444736000000000  # Difference between 1601 and 1970 in 100-ns\n        timestamp = (filetime - epoch_diff) / 10000000\n        dt = datetime.utcfromtimestamp(timestamp)\n        return dt.isoformat() + \"+00:00\"\n    except (ValueError, OSError, OverflowError):\n        return None\n\n\ndef parse_account_flags(flags: int) -> list:\n    \"\"\"Parse account flags bitmask to list of descriptions\"\"\"\n    result = []\n    for bit, description in ACCOUNT_FLAGS.items():\n        if flags & bit:\n            result.append(description)\n    return result\n\n\nclass SAMParsePlugin(Plugin):\n    \"\"\"\n    Parses user account information from SAM hive\n\n    Extracts:\n    - User names and RIDs\n    - Account creation time\n    - Last login time\n    - Password last set time\n    - Login count\n    - Account flags (disabled, locked, etc.)\n    - Group memberships\n\n    Registry Key: SAM\\\\Domains\\\\Account\\\\Users\n    \"\"\"\n\n    NAME = \"samparse\"\n    DESCRIPTION = \"Parses user accounts from SAM hive\"\n    COMPATIBLE_HIVE = SAM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started SAM Parse Plugin...\")\n\n        # First, build a mapping of RID to username from the Names subkey\n        rid_to_name = self._get_rid_to_name_mapping()\n\n        # Then parse user data from the RID subkeys\n        try:\n            users_key = self.registry_hive.get_key(SAM_USERS_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find SAM Users at: {SAM_USERS_PATH}: {ex}\")\n            return\n\n        for subkey in users_key.iter_subkeys():\n            # Skip the Names subkey\n            if subkey.name == \"Names\":\n                continue\n\n            # RID is the subkey name in hex (e.g., \"000001F4\" = 500 = Administrator)\n            try:\n                rid = int(subkey.name, 16)\n            except ValueError:\n                continue\n\n            entry = {\n                \"key_path\": f\"{SAM_USERS_PATH}\\\\{subkey.name}\",\n                \"rid\": rid,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            }\n\n            # Get username from mapping\n            if rid in rid_to_name:\n                entry[\"username\"] = rid_to_name[rid][\"username\"]\n                entry[\"name_key_last_write\"] = rid_to_name[rid][\"last_write\"]\n\n            # Parse the V value (contains most user data)\n            v_value = None\n            f_value = None\n            for value in subkey.iter_values():\n                if value.name == \"V\":\n                    v_value = value.value\n                elif value.name == \"F\":\n                    f_value = value.value\n\n            if f_value:\n                self._parse_f_value(f_value, entry)\n\n            if v_value:\n                self._parse_v_value(v_value, entry)\n\n            self.entries.append(entry)\n\n    def _get_rid_to_name_mapping(self) -> dict:\n        \"\"\"Build mapping of RID to username from Names subkey\"\"\"\n        mapping = {}\n        try:\n            names_key = self.registry_hive.get_key(SAM_NAMES_PATH)\n        except RegistryKeyNotFoundException:\n            return mapping\n\n        for subkey in names_key.iter_subkeys():\n            username = subkey.name\n            last_write = convert_wintime(subkey.header.last_modified, as_json=self.as_json)\n\n            # The default value's type contains the RID\n            for value in subkey.iter_values():\n                if value.name == \"\" or value.name == \"(Default)\":\n                    # The value type is the RID\n                    rid = value.value_type_raw if hasattr(value, \"value_type_raw\") else None\n                    if rid is None:\n                        # Try to get from the raw type\n                        with contextlib.suppress(Exception):\n                            rid = value._vk_record.data_type\n                    if rid:\n                        mapping[rid] = {\"username\": username, \"last_write\": last_write}\n                    break\n\n        return mapping\n\n    def _parse_f_value(self, data, entry: dict):\n        \"\"\"Parse the F value containing user account metadata\"\"\"\n        if not data:\n            return\n\n        # Ensure data is bytes\n        if isinstance(data, str):\n            try:\n                data = bytes.fromhex(data)\n            except ValueError:\n                data = data.encode(\"latin-1\")\n\n        if len(data) < 72:\n            return\n\n        try:\n            # F value structure (offsets are for Vista+)\n            # 0x08: Last Login Time (FILETIME)\n            # 0x18: Password Last Set (FILETIME)\n            # 0x20: Account Expires (FILETIME)\n            # 0x28: Last Failed Login (FILETIME)\n            # 0x30: RID\n            # 0x38: Account Control Flags\n            # 0x40: Failed Login Count\n            # 0x42: Login Count\n\n            last_login = struct.unpack(\"<Q\", data[8:16])[0]\n            entry[\"last_login\"] = filetime_to_datetime(last_login)\n\n            pwd_last_set = struct.unpack(\"<Q\", data[24:32])[0]\n            entry[\"password_last_set\"] = filetime_to_datetime(pwd_last_set)\n\n            account_expires = struct.unpack(\"<Q\", data[32:40])[0]\n            entry[\"account_expires\"] = filetime_to_datetime(account_expires)\n\n            last_failed_login = struct.unpack(\"<Q\", data[40:48])[0]\n            entry[\"last_failed_login\"] = filetime_to_datetime(last_failed_login)\n\n            rid_from_f = struct.unpack(\"<I\", data[48:52])[0]\n            if rid_from_f and rid_from_f == entry.get(\"rid\"):\n                pass  # Consistent\n\n            acct_flags = struct.unpack(\"<H\", data[56:58])[0]\n            entry[\"account_flags\"] = acct_flags\n            entry[\"account_flags_parsed\"] = parse_account_flags(acct_flags)\n\n            failed_login_count = struct.unpack(\"<H\", data[64:66])[0]\n            entry[\"failed_login_count\"] = failed_login_count\n\n            login_count = struct.unpack(\"<H\", data[66:68])[0]\n            entry[\"login_count\"] = login_count\n\n        except (struct.error, IndexError) as e:\n            logger.debug(f\"Error parsing F value: {e}\")\n\n    def _parse_v_value(self, data, entry: dict):\n        \"\"\"Parse the V value containing user details like name, comment, etc.\"\"\"\n        if not data:\n            return\n\n        # Ensure data is bytes\n        if isinstance(data, str):\n            try:\n                data = bytes.fromhex(data)\n            except ValueError:\n                data = data.encode(\"latin-1\")\n\n        if len(data) < 0xCC:\n            return\n\n        try:\n            # V value has a header with offsets to various strings\n            # Each entry is: offset (4 bytes), length (4 bytes), unknown (4 bytes)\n            # The offsets are relative to 0xCC\n\n            # Username at offset 0x0C\n            name_offset = struct.unpack(\"<I\", data[0x0C:0x10])[0] + 0xCC\n            name_length = struct.unpack(\"<I\", data[0x10:0x14])[0]\n            if name_offset + name_length <= len(data) and name_length > 0:\n                username = data[name_offset : name_offset + name_length].decode(\"utf-16-le\", errors=\"replace\")\n                if \"username\" not in entry:\n                    entry[\"username\"] = username\n\n            # Full Name at offset 0x18\n            fullname_offset = struct.unpack(\"<I\", data[0x18:0x1C])[0] + 0xCC\n            fullname_length = struct.unpack(\"<I\", data[0x1C:0x20])[0]\n            if fullname_offset + fullname_length <= len(data) and fullname_length > 0:\n                entry[\"full_name\"] = data[fullname_offset : fullname_offset + fullname_length].decode(\n                    \"utf-16-le\", errors=\"replace\"\n                )\n\n            # Comment at offset 0x24\n            comment_offset = struct.unpack(\"<I\", data[0x24:0x28])[0] + 0xCC\n            comment_length = struct.unpack(\"<I\", data[0x28:0x2C])[0]\n            if comment_offset + comment_length <= len(data) and comment_length > 0:\n                entry[\"comment\"] = data[comment_offset : comment_offset + comment_length].decode(\"utf-16-le\", errors=\"replace\")\n\n            # User Comment at offset 0x30\n            user_comment_offset = struct.unpack(\"<I\", data[0x30:0x34])[0] + 0xCC\n            user_comment_length = struct.unpack(\"<I\", data[0x34:0x38])[0]\n            if user_comment_offset + user_comment_length <= len(data) and user_comment_length > 0:\n                entry[\"user_comment\"] = data[user_comment_offset : user_comment_offset + user_comment_length].decode(\n                    \"utf-16-le\", errors=\"replace\"\n                )\n\n            # Home Directory at offset 0x48\n            homedir_offset = struct.unpack(\"<I\", data[0x48:0x4C])[0] + 0xCC\n            homedir_length = struct.unpack(\"<I\", data[0x4C:0x50])[0]\n            if homedir_offset + homedir_length <= len(data) and homedir_length > 0:\n                entry[\"home_directory\"] = data[homedir_offset : homedir_offset + homedir_length].decode(\n                    \"utf-16-le\", errors=\"replace\"\n                )\n\n            # Home Directory Connect at offset 0x54\n            homedir_connect_offset = struct.unpack(\"<I\", data[0x54:0x58])[0] + 0xCC\n            homedir_connect_length = struct.unpack(\"<I\", data[0x58:0x5C])[0]\n            if homedir_connect_offset + homedir_connect_length <= len(data) and homedir_connect_length > 0:\n                entry[\"home_directory_connect\"] = data[\n                    homedir_connect_offset : homedir_connect_offset + homedir_connect_length\n                ].decode(\"utf-16-le\", errors=\"replace\")\n\n            # Script Path at offset 0x60\n            script_offset = struct.unpack(\"<I\", data[0x60:0x64])[0] + 0xCC\n            script_length = struct.unpack(\"<I\", data[0x64:0x68])[0]\n            if script_offset + script_length <= len(data) and script_length > 0:\n                entry[\"script_path\"] = data[script_offset : script_offset + script_length].decode(\"utf-16-le\", errors=\"replace\")\n\n            # Profile Path at offset 0x6C\n            profile_offset = struct.unpack(\"<I\", data[0x6C:0x70])[0] + 0xCC\n            profile_length = struct.unpack(\"<I\", data[0x70:0x74])[0]\n            if profile_offset + profile_length <= len(data) and profile_length > 0:\n                entry[\"profile_path\"] = data[profile_offset : profile_offset + profile_length].decode(\n                    \"utf-16-le\", errors=\"replace\"\n                )\n\n            # Workstations at offset 0x78\n            workstations_offset = struct.unpack(\"<I\", data[0x78:0x7C])[0] + 0xCC\n            workstations_length = struct.unpack(\"<I\", data[0x7C:0x80])[0]\n            if workstations_offset + workstations_length <= len(data) and workstations_length > 0:\n                entry[\"workstations\"] = data[workstations_offset : workstations_offset + workstations_length].decode(\n                    \"utf-16-le\", errors=\"replace\"\n                )\n\n        except (struct.error, IndexError) as e:\n            logger.debug(f\"Error parsing V value: {e}\")\n"
  },
  {
    "path": "regipy/plugins/security/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/security/domain_sid.py",
    "content": "\"\"\"\nWindows machine domain name and SID extractor plugin\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom regipy.hive_types import SECURITY_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.security_utils import convert_sid\nfrom regipy.structs import SID\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nDOMAIN_NAME_PATH = r\"\\Policy\\PolPrDmN\"\nDOMAIN_SID_PATH = r\"\\Policy\\PolMachineAccountS\"\n\n\nclass DomainSidPlugin(Plugin):\n    \"\"\"\n    Windows machine domain name and SID extractor\n    \"\"\"\n\n    NAME = \"domain_sid\"\n    DESCRIPTION = \"Get the machine domain name and SID\"\n    COMPATIBLE_HIVE = SECURITY_HIVE_TYPE\n\n    def run(self) -> None:\n        logger.debug(\"Started Machine Domain SID Plugin...\")\n\n        name_key = self.registry_hive.get_key(DOMAIN_NAME_PATH)\n\n        # Primary Domain Name or Workgroup Name (binary-encoded and length-prefixed)\n        name_value = name_key.get_value()\n\n        # Skip UNICODE_STRING struct header and strip trailing \\x0000\n        domain_name = name_value[8:].decode(\"utf-16-le\", errors=\"replace\").rstrip(\"\\x00\")\n\n        sid_key = self.registry_hive.get_key(DOMAIN_SID_PATH)\n\n        # Domain SID value (binary-encoded)\n        sid_value = sid_key.get_value()\n\n        domain_sid: Optional[str] = None\n        machine_sid: Optional[str] = None\n\n        # The default key value is 0x00000000 (REG_DWORD) when\n        # the Windows machine is not in an AD domain.\n        # Otherwise, it contains the domain machine SID data\n        # in the standard binary format (REG_BINARY).\n        if isinstance(sid_value, bytes):\n            parsed_sid = SID.parse(sid_value)\n            domain_sid = convert_sid(parsed_sid, strip_rid=True)\n            machine_sid = convert_sid(parsed_sid)\n\n        self.entries.append(\n            {\n                \"domain_name\": domain_name,\n                \"domain_sid\": domain_sid,\n                \"machine_sid\": machine_sid,\n                \"timestamp\": convert_wintime(sid_key.header.last_modified, as_json=self.as_json),\n            }\n        )\n"
  },
  {
    "path": "regipy/plugins/software/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/software/appcompatflags.py",
    "content": "\"\"\"\nAppCompatFlags plugin - Parses application compatibility flags and layers\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nAPPCOMPAT_FLAGS_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\"\nAPPCOMPAT_LAYERS_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers\"\nAPPCOMPAT_CUSTOM_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom\"\n\n\nclass AppCompatFlagsPlugin(Plugin):\n    \"\"\"\n    Parses Application Compatibility Flags from SOFTWARE hive\n\n    Provides information about:\n    - Compatibility layers (e.g., RunAsAdmin, WinXP compatibility mode)\n    - Custom compatibility shims applied to applications\n    - Evidence of application execution\n\n    Registry Keys:\n    - Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\n    - Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Layers\n    - Microsoft\\\\Windows NT\\\\CurrentVersion\\\\AppCompatFlags\\\\Custom\n    \"\"\"\n\n    NAME = \"appcompat_flags\"\n    DESCRIPTION = \"Parses application compatibility flags and layers\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started AppCompatFlags Plugin...\")\n\n        self._parse_layers()\n        self._parse_custom()\n\n    def _parse_layers(self):\n        \"\"\"Parse compatibility layers applied to applications\"\"\"\n        try:\n            layers_key = self.registry_hive.get_key(APPCOMPAT_LAYERS_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find AppCompatFlags Layers at: {APPCOMPAT_LAYERS_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"layers\",\n            \"key_path\": APPCOMPAT_LAYERS_PATH,\n            \"last_write\": convert_wintime(layers_key.header.last_modified, as_json=self.as_json),\n            \"applications\": [],\n        }\n\n        for value in layers_key.iter_values():\n            # Value name is the application path\n            # Value data is the compatibility settings (e.g., \"~ RUNASADMIN\")\n            app_entry = {\n                \"path\": value.name,\n                \"layers\": value.value,\n            }\n\n            # Parse the layers string\n            if isinstance(value.value, str):\n                layers = [layer.strip() for layer in value.value.split() if layer.strip() and layer.strip() != \"~\"]\n                app_entry[\"parsed_layers\"] = layers\n\n            entry[\"applications\"].append(app_entry)\n\n        if entry[\"applications\"]:\n            self.entries.append(entry)\n\n    def _parse_custom(self):\n        \"\"\"Parse custom compatibility shims\"\"\"\n        try:\n            custom_key = self.registry_hive.get_key(APPCOMPAT_CUSTOM_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find AppCompatFlags Custom at: {APPCOMPAT_CUSTOM_PATH}\")\n            return\n\n        for subkey in custom_key.iter_subkeys():\n            # Each subkey is an application name (e.g., \"program.exe\")\n            app_name = subkey.name\n            subkey_path = f\"{APPCOMPAT_CUSTOM_PATH}\\\\{app_name}\"\n\n            entry = {\n                \"type\": \"custom\",\n                \"key_path\": subkey_path,\n                \"application\": app_name,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                \"shims\": [],\n            }\n\n            for value in subkey.iter_values():\n                # Value names are shim database identifiers\n                entry[\"shims\"].append(\n                    {\n                        \"name\": value.name,\n                        \"value\": value.value,\n                    }\n                )\n\n            if entry[\"shims\"]:\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/appinitdlls.py",
    "content": "\"\"\"\nAppInit_DLLs plugin - Parses persistence via AppInit_DLLs\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# 32-bit path\nAPPINIT_DLLS_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\Windows\"\n# 64-bit path (WoW6432Node)\nAPPINIT_DLLS_WOW64_PATH = r\"\\Wow6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows\"\n\n\nclass AppInitDLLsPlugin(Plugin):\n    \"\"\"\n    Parses AppInit_DLLs persistence mechanism from SOFTWARE hive\n\n    AppInit_DLLs is a registry value that causes Windows to load specified DLLs\n    into every user-mode process that links to User32.dll. This is a known\n    persistence mechanism used by malware.\n\n    Registry Keys:\n    - Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Windows\n    - Wow6432Node\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\Windows (64-bit systems)\n    \"\"\"\n\n    NAME = \"appinit_dlls\"\n    DESCRIPTION = \"Parses AppInit_DLLs persistence entries\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started AppInit_DLLs Plugin...\")\n\n        self._parse_appinit_dlls(APPINIT_DLLS_PATH, \"x64\")\n        self._parse_appinit_dlls(APPINIT_DLLS_WOW64_PATH, \"x86\")\n\n    def _parse_appinit_dlls(self, path: str, architecture: str):\n        \"\"\"Parse AppInit_DLLs at the given path\"\"\"\n        try:\n            windows_key = self.registry_hive.get_key(path)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find AppInit_DLLs at: {path}\")\n            return\n\n        entry = {\n            \"key_path\": path,\n            \"architecture\": architecture,\n            \"last_write\": convert_wintime(windows_key.header.last_modified, as_json=self.as_json),\n            \"appinit_dlls\": None,\n            \"load_appinit_dlls\": None,\n            \"require_signed_appinit_dlls\": None,\n        }\n\n        for value in windows_key.iter_values():\n            name = value.name\n            val = value.value\n\n            if name == \"AppInit_DLLs\":\n                entry[\"appinit_dlls\"] = val\n            elif name == \"LoadAppInit_DLLs\":\n                entry[\"load_appinit_dlls\"] = val == 1\n            elif name == \"RequireSignedAppInit_DLLs\":\n                entry[\"require_signed_appinit_dlls\"] = val == 1\n\n        # Only add entry if AppInit_DLLs has content or loading is enabled\n        if entry[\"appinit_dlls\"] or entry[\"load_appinit_dlls\"]:\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/apppaths.py",
    "content": "\"\"\"\nApp Paths plugin - Parses application paths registry entries\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nAPP_PATHS_PATH = r\"\\Microsoft\\Windows\\CurrentVersion\\App Paths\"\nAPP_PATHS_WOW64_PATH = r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\"\n\n\nclass AppPathsPlugin(Plugin):\n    \"\"\"\n    Parses App Paths from SOFTWARE hive\n\n    App Paths allows applications to be launched by name without specifying\n    the full path. Each subkey under App Paths is an executable name, and\n    contains the path to the actual executable.\n\n    This is useful for:\n    - Finding installed applications\n    - Detecting persistence (malware can hijack app paths)\n    - Understanding application configurations\n\n    Registry Keys:\n    - Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\n    - Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\n    \"\"\"\n\n    NAME = \"app_paths\"\n    DESCRIPTION = \"Parses application paths registry entries\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started App Paths Plugin...\")\n\n        self._parse_app_paths(APP_PATHS_PATH, \"x64\")\n        self._parse_app_paths(APP_PATHS_WOW64_PATH, \"x86\")\n\n    def _parse_app_paths(self, path: str, architecture: str):\n        \"\"\"Parse App Paths at the given path\"\"\"\n        try:\n            app_paths_key = self.registry_hive.get_key(path)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find App Paths at: {path}\")\n            return\n\n        for subkey in app_paths_key.iter_subkeys():\n            app_name = subkey.name\n            subkey_path = f\"{path}\\\\{app_name}\"\n\n            entry = {\n                \"key_path\": subkey_path,\n                \"application\": app_name,\n                \"architecture\": architecture,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            }\n\n            for value in subkey.iter_values():\n                name = value.name.lower() if value.name else \"(default)\"\n                val = value.value\n\n                if name == \"(default)\" or value.name == \"\":\n                    entry[\"path\"] = val\n                elif name == \"path\":\n                    entry[\"app_path\"] = val\n                elif name == \"useurl\":\n                    entry[\"use_url\"] = val\n                elif name == \"dropmenu\":\n                    entry[\"drop_menu\"] = val\n                elif name == \"dontusedestoolbar\":\n                    entry[\"dont_use_des_toolbar\"] = val\n\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/classes_installer.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nCLASSES_INSTALLER_PATH = r\"\\Classes\\Installer\\Products\"\n\n\nclass SoftwareClassesInstallerPlugin(Plugin):\n    NAME = \"software_classes_installer\"\n    DESCRIPTION = \"List of installed software from SOFTWARE hive\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        try:\n            installer_subkey = self.registry_hive.get_key(CLASSES_INSTALLER_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for entry in installer_subkey.iter_subkeys():\n            identifier = entry.name\n            timestamp = convert_wintime(entry.header.last_modified, as_json=self.as_json)\n            product_name = entry.get_value(\"ProductName\")\n            self.entries.append(\n                {\n                    \"identifier\": identifier,\n                    \"timestamp\": timestamp,\n                    \"product_name\": product_name,\n                    \"is_hidden\": product_name is None,\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/software/defender.py",
    "content": "\"\"\"\nWindows Defender plugin - Parses Windows Defender configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nDEFENDER_PATH = r\"\\Microsoft\\Windows Defender\"\nDEFENDER_POLICY_PATH = r\"\\Policies\\Microsoft\\Windows Defender\"\nDEFENDER_EXCLUSIONS_PATH = r\"\\Microsoft\\Windows Defender\\Exclusions\"\n\n\nclass WindowsDefenderPlugin(Plugin):\n    \"\"\"\n    Parses Windows Defender configuration from SOFTWARE hive\n\n    Extracts:\n    - Defender enabled/disabled status\n    - Real-time protection settings\n    - Exclusions (paths, processes, extensions)\n    - Policy overrides\n\n    Registry Keys:\n    - Microsoft\\\\Windows Defender\n    - Policies\\\\Microsoft\\\\Windows Defender\n    \"\"\"\n\n    NAME = \"windows_defender\"\n    DESCRIPTION = \"Parses Windows Defender configuration and exclusions\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Windows Defender Plugin...\")\n\n        self._parse_defender_config()\n        self._parse_defender_policy()\n        self._parse_exclusions()\n\n    def _parse_defender_config(self):\n        \"\"\"Parse main Defender configuration\"\"\"\n        try:\n            defender_key = self.registry_hive.get_key(DEFENDER_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find Windows Defender at: {DEFENDER_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"configuration\",\n            \"key_path\": DEFENDER_PATH,\n            \"last_write\": convert_wintime(defender_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            defender_key,\n            {\n                \"DisableAntiSpyware\": (\"antispyware_disabled\", lambda v: v == 1),\n                \"DisableAntiVirus\": (\"antivirus_disabled\", lambda v: v == 1),\n                \"ProductStatus\": \"product_status\",\n                \"InstallLocation\": \"install_location\",\n            },\n            entry,\n        )\n\n        # Parse Real-Time Protection subkey\n        try:\n            rtp_key = self.registry_hive.get_key(f\"{DEFENDER_PATH}\\\\Real-Time Protection\")\n            extract_values(\n                rtp_key,\n                {\n                    \"DisableRealtimeMonitoring\": (\"realtime_monitoring_disabled\", lambda v: v == 1),\n                    \"DisableBehaviorMonitoring\": (\"behavior_monitoring_disabled\", lambda v: v == 1),\n                    \"DisableOnAccessProtection\": (\"on_access_protection_disabled\", lambda v: v == 1),\n                    \"DisableScanOnRealtimeEnable\": (\"scan_on_realtime_enable_disabled\", lambda v: v == 1),\n                },\n                entry,\n            )\n        except RegistryKeyNotFoundException:\n            pass\n\n        self.entries.append(entry)\n\n    def _parse_defender_policy(self):\n        \"\"\"Parse Defender Group Policy settings\"\"\"\n        try:\n            policy_key = self.registry_hive.get_key(DEFENDER_POLICY_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find Windows Defender policy at: {DEFENDER_POLICY_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"policy\",\n            \"key_path\": DEFENDER_POLICY_PATH,\n            \"last_write\": convert_wintime(policy_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            policy_key,\n            {\n                \"DisableAntiSpyware\": (\"policy_antispyware_disabled\", lambda v: v == 1),\n                \"DisableAntiVirus\": (\"policy_antivirus_disabled\", lambda v: v == 1),\n                \"DisableRoutinelyTakingAction\": (\"routine_action_disabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        # Check for Real-Time Protection policy\n        try:\n            rtp_policy_key = self.registry_hive.get_key(f\"{DEFENDER_POLICY_PATH}\\\\Real-Time Protection\")\n            extract_values(\n                rtp_policy_key,\n                {\n                    \"DisableRealtimeMonitoring\": (\"policy_realtime_monitoring_disabled\", lambda v: v == 1),\n                },\n                entry,\n            )\n        except RegistryKeyNotFoundException:\n            pass\n\n        if len(entry) > 3:  # More than just type, key_path, last_write\n            self.entries.append(entry)\n\n    def _parse_exclusions(self):\n        \"\"\"Parse Defender exclusions\"\"\"\n        exclusion_types = {\n            \"Paths\": \"path_exclusions\",\n            \"Processes\": \"process_exclusions\",\n            \"Extensions\": \"extension_exclusions\",\n            \"IpAddresses\": \"ip_exclusions\",\n        }\n\n        for exclusion_type in exclusion_types:\n            path = f\"{DEFENDER_EXCLUSIONS_PATH}\\\\{exclusion_type}\"\n            try:\n                exclusions_key = self.registry_hive.get_key(path)\n            except RegistryKeyNotFoundException:\n                continue\n\n            exclusions = []\n            for value in exclusions_key.iter_values():\n                # Value name is the excluded item\n                exclusions.append(value.name)\n\n            if exclusions:\n                entry = {\n                    \"type\": \"exclusion\",\n                    \"exclusion_type\": exclusion_type.lower(),\n                    \"key_path\": path,\n                    \"last_write\": convert_wintime(exclusions_key.header.last_modified, as_json=self.as_json),\n                    \"exclusions\": exclusions,\n                }\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/disablesr.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nSYS_RESTORE_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\SystemRestore\"\nvalue_list = [\"DisableSR\"]\n\n\nclass DisableSRPlugin(Plugin):\n    NAME = \"disablesr_plugin\"\n    DESCRIPTION = \"Gets the value that turns System Restore either on or off\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.info(\"Started disablesr Plugin...\")\n\n        try:\n            key = self.registry_hive.get_key(SYS_RESTORE_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {SYS_RESTORE_PATH}: {ex}\")\n            return None\n\n        self.entries = {SYS_RESTORE_PATH: {\"last_write\": convert_wintime(key.header.last_modified).isoformat()}}\n\n        for val in key.iter_values():\n            if val.name in value_list:\n                self.entries[SYS_RESTORE_PATH][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/software/execpolicy.py",
    "content": "\"\"\"\nExecution Policy plugin - Parses PowerShell and script execution policies\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# PowerShell execution policy paths\nPS_SHELL_IDS_PATH = r\"\\Microsoft\\PowerShell\\1\\ShellIds\\Microsoft.PowerShell\"\nPS_POLICY_PATH = r\"\\Policies\\Microsoft\\Windows\\PowerShell\"\n\n# Script execution paths\nWSH_SETTINGS_PATH = r\"\\Microsoft\\Windows Script Host\\Settings\"\n\n\nclass ExecutionPolicyPlugin(Plugin):\n    \"\"\"\n    Parses execution policies from SOFTWARE hive\n\n    Extracts:\n    - PowerShell execution policy\n    - Windows Script Host (WSH) settings\n\n    Registry Keys:\n    - Microsoft\\\\PowerShell\\\\1\\\\ShellIds\\\\Microsoft.PowerShell\n    - Policies\\\\Microsoft\\\\Windows\\\\PowerShell\n    - Microsoft\\\\Windows Script Host\\\\Settings\n    \"\"\"\n\n    NAME = \"execution_policy\"\n    DESCRIPTION = \"Parses PowerShell and script execution policies\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Execution Policy Plugin...\")\n\n        self._parse_powershell_policy()\n        self._parse_powershell_group_policy()\n        self._parse_wsh_settings()\n\n    def _parse_powershell_policy(self):\n        \"\"\"Parse PowerShell execution policy\"\"\"\n        try:\n            ps_key = self.registry_hive.get_key(PS_SHELL_IDS_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PowerShell ShellIds at: {PS_SHELL_IDS_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"powershell\",\n            \"key_path\": PS_SHELL_IDS_PATH,\n            \"last_write\": convert_wintime(ps_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            ps_key,\n            {\n                \"ExecutionPolicy\": \"execution_policy\",\n                \"Path\": \"path\",\n            },\n            entry,\n        )\n\n        self.entries.append(entry)\n\n    def _parse_powershell_group_policy(self):\n        \"\"\"Parse PowerShell Group Policy settings\"\"\"\n        try:\n            gp_key = self.registry_hive.get_key(PS_POLICY_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PowerShell policy at: {PS_POLICY_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"powershell_policy\",\n            \"key_path\": PS_POLICY_PATH,\n            \"last_write\": convert_wintime(gp_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            gp_key,\n            {\n                \"ExecutionPolicy\": \"execution_policy\",\n                \"EnableScripts\": (\"scripts_enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        if \"execution_policy\" in entry or \"scripts_enabled\" in entry:\n            self.entries.append(entry)\n\n    def _parse_wsh_settings(self):\n        \"\"\"Parse Windows Script Host settings\"\"\"\n        try:\n            wsh_key = self.registry_hive.get_key(WSH_SETTINGS_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find WSH Settings at: {WSH_SETTINGS_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"wsh\",\n            \"key_path\": WSH_SETTINGS_PATH,\n            \"last_write\": convert_wintime(wsh_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            wsh_key,\n            {\n                \"Enabled\": (\"enabled\", lambda v: v != 0),\n                \"Remote\": (\"remote_enabled\", lambda v: v == 1),\n                \"TrustPolicy\": \"trust_policy\",\n                \"IgnoreUserSettings\": (\"ignore_user_settings\", lambda v: v == 1),\n                \"LogSecuritySuccesses\": (\"log_security_successes\", lambda v: v == 1),\n                \"DisplayLogo\": (\"display_logo\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        if len(entry) > 3:\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/image_file_execution_options.py",
    "content": "import logging\n\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nIMAGE_FILE_EXECUTION_OPTIONS = r\"\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\"\n\n\nclass ImageFileExecutionOptions(Plugin):\n    NAME = \"image_file_execution_options\"\n    DESCRIPTION = \"Retrieve image file execution options - a persistence method\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        image_file_execution_options = self.registry_hive.get_key(IMAGE_FILE_EXECUTION_OPTIONS)\n        if image_file_execution_options.subkey_count:\n            for subkey in image_file_execution_options.iter_subkeys():\n                values = {x.name: x.value for x in subkey.iter_values(as_json=self.as_json)} if subkey.values_count else {}\n                self.entries.append(\n                    {\n                        \"name\": subkey.name,\n                        \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                        **values,\n                    }\n                )\n"
  },
  {
    "path": "regipy/plugins/software/installed_programs.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nX64_INSTALLED_SOFTWARE_PATH = r\"\\Microsoft\\Windows\\CurrentVersion\\Uninstall\"\nX86_INSTALLED_SOFTWARE_PATH = r\"\\Wow6432Node\" + X64_INSTALLED_SOFTWARE_PATH\n\n\nclass InstalledProgramsSoftwarePlugin(Plugin):\n    NAME = \"installed_programs_software\"\n    DESCRIPTION = \"Retrieve list of installed programs and their install date from the SOFTWARE Hive\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def _get_installed_software(self, subkey_path):\n        try:\n            uninstall_sk = self.registry_hive.get_key(subkey_path)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for installed_program in uninstall_sk.iter_subkeys():\n            values = (\n                {x.name: x.value for x in installed_program.iter_values(as_json=self.as_json)}\n                if installed_program.values_count\n                else {}\n            )\n            self.entries.append(\n                {\n                    \"service_name\": installed_program.name,\n                    \"timestamp\": convert_wintime(installed_program.header.last_modified, as_json=self.as_json),\n                    \"registry_path\": subkey_path,\n                    **values,\n                }\n            )\n\n    def run(self):\n        self._get_installed_software(X64_INSTALLED_SOFTWARE_PATH)\n        self._get_installed_software(X86_INSTALLED_SOFTWARE_PATH)\n"
  },
  {
    "path": "regipy/plugins/software/last_logon.py",
    "content": "import logging\n\nfrom inflection import underscore\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nLAST_LOGON_KEY_PATH = r\"\\Microsoft\\Windows\\CurrentVersion\\Authentication\\LogonUI\"\n\n\nclass LastLogonPlugin(Plugin):\n    NAME = \"last_logon_plugin\"\n    DESCRIPTION = \"Get the last logged on username\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        try:\n            subkey = self.registry_hive.get_key(LAST_LOGON_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {LAST_LOGON_KEY_PATH}: {ex}\")\n            return None\n\n        self.entries = {\n            \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            **{underscore(x.name): x.value for x in subkey.iter_values(as_json=self.as_json)},\n        }\n"
  },
  {
    "path": "regipy/plugins/software/networklist.py",
    "content": "\"\"\"\nNetworkList plugin - Parses network connection history\n\"\"\"\n\nimport logging\nimport struct\nfrom datetime import datetime\nfrom typing import Optional\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nNETWORK_LIST_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\"\nPROFILES_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Profiles\"\nSIGNATURES_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Signatures\"\nNLAS_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\NetworkList\\Nla\\Wireless\"\n\n# Lookup tables for network types\nCATEGORY_TYPES = {0: \"Public\", 1: \"Private\", 2: \"Domain\"}\nNAME_TYPES = {0x06: \"Wired\", 0x17: \"Broadband\", 0x47: \"Wireless\"}\n\n\ndef format_mac_address(val) -> Optional[str]:\n    \"\"\"Format bytes as MAC address (XX:XX:XX:XX:XX:XX)\"\"\"\n    if isinstance(val, bytes) and len(val) == 6:\n        return \":\".join(f\"{b:02X}\" for b in val)\n    return val\n\n\ndef parse_network_date(data: bytes) -> Optional[str]:\n    \"\"\"Parse the binary date format used in NetworkList\"\"\"\n    if not data or len(data) < 16:\n        return None\n\n    try:\n        # Format: Year(2) Month(2) DayOfWeek(2) Day(2) Hour(2) Minute(2) Second(2) Milliseconds(2)\n        year = struct.unpack(\"<H\", data[0:2])[0]\n        month = struct.unpack(\"<H\", data[2:4])[0]\n        # day_of_week = struct.unpack(\"<H\", data[4:6])[0]\n        day = struct.unpack(\"<H\", data[6:8])[0]\n        hour = struct.unpack(\"<H\", data[8:10])[0]\n        minute = struct.unpack(\"<H\", data[10:12])[0]\n        second = struct.unpack(\"<H\", data[12:14])[0]\n        # milliseconds = struct.unpack(\"<H\", data[14:16])[0]\n\n        if year > 0 and month > 0 and day > 0:\n            dt = datetime(year, month, day, hour, minute, second)\n            return dt.isoformat()\n    except Exception:\n        pass\n\n    return None\n\n\nclass NetworkListPlugin(Plugin):\n    \"\"\"\n    Parses Network List (NetworkList) from SOFTWARE hive\n\n    Provides information about:\n    - Network profiles (wired and wireless networks connected to)\n    - First and last connection times\n    - Network signatures (MAC addresses, SSIDs)\n\n    Registry Key: Microsoft\\\\Windows NT\\\\CurrentVersion\\\\NetworkList\n    \"\"\"\n\n    NAME = \"networklist\"\n    DESCRIPTION = \"Parses network connection history\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started NetworkList Plugin...\")\n\n        self._parse_profiles()\n        self._parse_signatures()\n\n    def _parse_profiles(self):\n        \"\"\"Parse network profiles\"\"\"\n        try:\n            profiles_key = self.registry_hive.get_key(PROFILES_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find NetworkList Profiles at: {PROFILES_PATH}\")\n            return\n\n        for subkey in profiles_key.iter_subkeys():\n            profile_guid = subkey.name\n            subkey_path = f\"{PROFILES_PATH}\\\\{profile_guid}\"\n\n            entry = {\n                \"type\": \"profile\",\n                \"key_path\": subkey_path,\n                \"profile_guid\": profile_guid,\n                \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            }\n\n            extract_values(\n                subkey,\n                {\n                    \"ProfileName\": \"profile_name\",\n                    \"Description\": \"description\",\n                    \"Managed\": (\"managed\", lambda v: v == 1),\n                    \"Category\": (\"category\", lambda v: CATEGORY_TYPES.get(v, f\"Unknown ({v})\")),\n                    \"CategoryType\": \"category_type\",\n                    \"NameType\": (\"name_type\", lambda v: NAME_TYPES.get(v, f\"Unknown ({v})\")),\n                    \"DateCreated\": (\"date_created\", parse_network_date),\n                    \"DateLastConnected\": (\"date_last_connected\", parse_network_date),\n                },\n                entry,\n            )\n\n            self.entries.append(entry)\n\n    def _parse_signatures(self):\n        \"\"\"Parse network signatures (contains MAC addresses)\"\"\"\n        signature_types = [\"Managed\", \"Unmanaged\"]\n\n        for sig_type in signature_types:\n            sig_path = f\"{SIGNATURES_PATH}\\\\{sig_type}\"\n            try:\n                sig_key = self.registry_hive.get_key(sig_path)\n            except RegistryKeyNotFoundException:\n                continue\n\n            for subkey in sig_key.iter_subkeys():\n                signature_id = subkey.name\n                subkey_path = f\"{sig_path}\\\\{signature_id}\"\n\n                entry = {\n                    \"type\": \"signature\",\n                    \"signature_type\": sig_type.lower(),\n                    \"key_path\": subkey_path,\n                    \"signature_id\": signature_id,\n                    \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                }\n\n                extract_values(\n                    subkey,\n                    {\n                        \"ProfileGuid\": \"profile_guid\",\n                        \"Description\": \"description\",\n                        \"Source\": \"source\",\n                        \"DefaultGatewayMac\": (\"default_gateway_mac\", format_mac_address),\n                        \"DnsSuffix\": \"dns_suffix\",\n                        \"FirstNetwork\": \"first_network\",\n                    },\n                    entry,\n                )\n\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/persistence.py",
    "content": "import logging\n\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import get_subkey_values_from_list\n\nlogger = logging.getLogger(__name__)\n\nPERSISTENCE_ENTRIES = [\n    r\"\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run\",\n    r\"\\Microsoft\\Windows\\CurrentVersion\\Run\",\n    r\"\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\n    r\"\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\Setup\",\n    r\"\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\",\n    r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run\",\n    r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\n    r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\Setup\",\n    r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnceEx\",\n    r\"\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run\",\n    r\"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Notify\",\n]\n\n\nclass SoftwarePersistencePlugin(Plugin):\n    NAME = \"software_plugin\"\n    DESCRIPTION = \"Retrieve values from known persistence subkeys in Software hive\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        self.entries = get_subkey_values_from_list(self.registry_hive, PERSISTENCE_ENTRIES, as_json=self.as_json)\n"
  },
  {
    "path": "regipy/plugins/software/printdemon.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nPORTS_KEY_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\Ports\"\n\n\nclass PrintDemonPlugin(Plugin):\n    NAME = \"print_demon_plugin\"\n    DESCRIPTION = \"Get list of installed printer ports, as could be taken advantage by cve-2020-1048\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        try:\n            subkey = self.registry_hive.get_key(PORTS_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {PORTS_KEY_PATH}: {ex}\")\n            return None\n\n        last_write = convert_wintime(subkey.header.last_modified).isoformat()\n        for port in subkey.iter_values():\n            self.entries.append(\n                {\n                    \"timestamp\": last_write,\n                    \"port_name\": port.name,\n                    \"parameters\": (port.value if isinstance(port.value, int) else port.value.split(\",\")),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/software/profilelist.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_filetime, convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nPROFILE_LIST_KEY_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\"\n\n\nclass ProfileListPlugin(Plugin):\n    NAME = \"profilelist_plugin\"\n    DESCRIPTION = \"Parses information about user profiles found in the ProfileList key\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started profile list plugin...\")\n        try:\n            subkey = self.registry_hive.get_key(PROFILE_LIST_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n\n        for profile in subkey.iter_subkeys():\n            self.entries.append(\n                {\n                    \"last_write\": convert_wintime(profile.header.last_modified, as_json=self.as_json),\n                    \"path\": profile.get_value(\"ProfileImagePath\"),\n                    \"flags\": profile.get_value(\"Flags\"),\n                    \"full_profile\": profile.get_value(\"FullProfile\"),\n                    \"state\": profile.get_value(\"State\"),\n                    \"sid\": profile.name,\n                    \"load_time\": convert_filetime(\n                        profile.get_value(\"ProfileLoadTimeLow\"),\n                        profile.get_value(\"ProfileLoadTimeHigh\"),\n                    ),\n                    \"local_load_time\": convert_filetime(\n                        profile.get_value(\"LocalProfileLoadTimeLow\"),\n                        profile.get_value(\"LocalProfileLoadTimeHigh\"),\n                    ),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/software/pslogging.py",
    "content": "\"\"\"\nPowerShell Logging plugin - Parses PowerShell logging configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n# PowerShell policy paths\nPS_POLICY_PATH = r\"\\Policies\\Microsoft\\Windows\\PowerShell\"\nPS_SCRIPTBLOCK_PATH = r\"\\Policies\\Microsoft\\Windows\\PowerShell\\ScriptBlockLogging\"\nPS_MODULE_PATH = r\"\\Policies\\Microsoft\\Windows\\PowerShell\\ModuleLogging\"\nPS_TRANSCRIPTION_PATH = r\"\\Policies\\Microsoft\\Windows\\PowerShell\\Transcription\"\n\n\nclass PowerShellLoggingPlugin(Plugin):\n    \"\"\"\n    Parses PowerShell logging configuration from SOFTWARE hive\n\n    Extracts:\n    - Script Block Logging settings\n    - Module Logging settings\n    - Transcription settings\n    - Execution Policy\n\n    These settings are important for security monitoring and incident response.\n\n    Registry Key: Policies\\\\Microsoft\\\\Windows\\\\PowerShell\n    \"\"\"\n\n    NAME = \"powershell_logging\"\n    DESCRIPTION = \"Parses PowerShell logging and execution policy\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started PowerShell Logging Plugin...\")\n\n        self._parse_main_policy()\n        self._parse_scriptblock_logging()\n        self._parse_module_logging()\n        self._parse_transcription()\n\n    def _parse_main_policy(self):\n        \"\"\"Parse main PowerShell policy settings\"\"\"\n        try:\n            ps_key = self.registry_hive.get_key(PS_POLICY_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find PowerShell policy at: {PS_POLICY_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"policy\",\n            \"key_path\": PS_POLICY_PATH,\n            \"last_write\": convert_wintime(ps_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            ps_key,\n            {\n                \"EnableScripts\": (\"scripts_enabled\", lambda v: v == 1),\n                \"ExecutionPolicy\": \"execution_policy\",\n            },\n            entry,\n        )\n\n        if len(entry) > 3:\n            self.entries.append(entry)\n\n    def _parse_scriptblock_logging(self):\n        \"\"\"Parse Script Block Logging settings\"\"\"\n        try:\n            sb_key = self.registry_hive.get_key(PS_SCRIPTBLOCK_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find ScriptBlockLogging at: {PS_SCRIPTBLOCK_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"scriptblock_logging\",\n            \"key_path\": PS_SCRIPTBLOCK_PATH,\n            \"last_write\": convert_wintime(sb_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            sb_key,\n            {\n                \"EnableScriptBlockLogging\": (\"enabled\", lambda v: v == 1),\n                \"EnableScriptBlockInvocationLogging\": (\"invocation_logging_enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        self.entries.append(entry)\n\n    def _parse_module_logging(self):\n        \"\"\"Parse Module Logging settings\"\"\"\n        try:\n            ml_key = self.registry_hive.get_key(PS_MODULE_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find ModuleLogging at: {PS_MODULE_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"module_logging\",\n            \"key_path\": PS_MODULE_PATH,\n            \"last_write\": convert_wintime(ml_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            ml_key,\n            {\n                \"EnableModuleLogging\": (\"enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        # Check for module names subkey\n        try:\n            modules_key = self.registry_hive.get_key(f\"{PS_MODULE_PATH}\\\\ModuleNames\")\n            module_names = []\n            for value in modules_key.iter_values():\n                module_names.append(value.name)\n            if module_names:\n                entry[\"logged_modules\"] = module_names\n        except RegistryKeyNotFoundException:\n            pass\n\n        self.entries.append(entry)\n\n    def _parse_transcription(self):\n        \"\"\"Parse Transcription settings\"\"\"\n        try:\n            tr_key = self.registry_hive.get_key(PS_TRANSCRIPTION_PATH)\n        except RegistryKeyNotFoundException:\n            logger.debug(f\"Could not find Transcription at: {PS_TRANSCRIPTION_PATH}\")\n            return\n\n        entry = {\n            \"type\": \"transcription\",\n            \"key_path\": PS_TRANSCRIPTION_PATH,\n            \"last_write\": convert_wintime(tr_key.header.last_modified, as_json=self.as_json),\n        }\n\n        extract_values(\n            tr_key,\n            {\n                \"EnableTranscripting\": (\"enabled\", lambda v: v == 1),\n                \"OutputDirectory\": \"output_directory\",\n                \"EnableInvocationHeader\": (\"invocation_header_enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/software/spp_clients.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nSPP_CLIENT_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\\SPP\\Clients\"\nvalue_list = \"{09F7EDC5-294E-4180-AF6A-FB0E6A0E9513}\"\n\n\nclass SppClientsPlugin(Plugin):\n    NAME = \"spp_clients_plugin\"\n    DESCRIPTION = \"Determines volumes monitored by VSS\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.info(\"Started spp_clients Plugin...\")\n\n        try:\n            key = self.registry_hive.get_key(SPP_CLIENT_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {SPP_CLIENT_PATH}: {ex}\")\n            return None\n\n        self.entries = {SPP_CLIENT_PATH: {\"last_write\": convert_wintime(key.header.last_modified).isoformat()}}\n\n        for val in key.iter_values():\n            if val.name in value_list:\n                aux_list = []\n                for value in val.value:\n                    aux_list.append(value.replace(\"%3A\", \":\"))\n                self.entries[SPP_CLIENT_PATH][val.name] = aux_list\n"
  },
  {
    "path": "regipy/plugins/software/susclient.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nWIN_VER_PATH = r\"\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\"\nvalue_list = (\"LastRestorePointSetTime\", \"SusClientId\")\n\n\nclass SusclientPlugin(Plugin):\n    NAME = \"susclient_plugin\"\n    DESCRIPTION = \"Extracts SusClient* info, including HDD SN\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.info(\"Started susclient Plugin...\")\n\n        try:\n            key = self.registry_hive.get_key(WIN_VER_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {WIN_VER_PATH}: {ex}\")\n            return None\n\n        self.entries = {WIN_VER_PATH: {\"last_write\": convert_wintime(key.header.last_modified).isoformat()}}\n\n        for val in key.iter_values():\n            if val.name == \"SusClientIdValidation\":\n                self.entries[WIN_VER_PATH][val.name] = get_SN(val.value)\n            elif val.name in value_list:\n                self.entries[WIN_VER_PATH][val.name] = val.value\n\n\ndef get_SN(data):\n    offset = int(data[:2], 16)\n    length = int(data[4:6], 16)\n    return bytes.fromhex(data[2 * offset : 2 * (length + offset)]).decode(\"utf-16le\")\n"
  },
  {
    "path": "regipy/plugins/software/tracing.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nTRACING_PATH = r\"\\Microsoft\\Tracing\"\nX86_TRACING_PATH = r\"\\Wow6432Node\" + TRACING_PATH\n\n\nclass RASTracingPlugin(Plugin):\n    NAME = \"ras_tracing\"\n    DESCRIPTION = \"Retrieve list of executables using ras\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def _get_installed_software(self, subkey_path):\n        try:\n            ras_subkey = self.registry_hive.get_key(subkey_path)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(ex)\n            return\n\n        for entry in ras_subkey.iter_subkeys():\n            timestamp = convert_wintime(entry.header.last_modified, as_json=self.as_json)\n            self.entries.append({\"key\": subkey_path, \"name\": entry.name, \"timestamp\": timestamp})\n\n    def run(self):\n        self._get_installed_software(TRACING_PATH)\n        self._get_installed_software(X86_TRACING_PATH)\n"
  },
  {
    "path": "regipy/plugins/software/uac.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nUAC_KEY_PATH = r\"\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\"\n\n\nclass UACStatusPlugin(Plugin):\n    NAME = \"uac_plugin\"\n    DESCRIPTION = \"Get the status of User Access Control\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        \"\"\"\n        This plugin checks the following parameters:\n        EnableLUA - Windows notifies the user when programs try to make changes to the computer\n        EnableVirtualization - enables the redirection of legacy application File and Registry writes\n                               that would normally fail as standard user to a user-writable data location.\n        FilterAdministratorToken - If enabled approval is required when performing administrative tasks.\n        ConsentPromptBehaviorAdmin - This option allows the Consent Admin to perform an\n                                     operation that requires elevation without consent or credentials.\n        ConsentPromptBehaviorUser - If enabled,  a standard user that needs to perform an operation that requires\n                                    elevation of privilege will be prompted for an administrative user name and password\n        \"\"\"\n        try:\n            subkey = self.registry_hive.get_key(UAC_KEY_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {UAC_KEY_PATH}: {ex}\")\n            return None\n\n        self.entries = {\n            \"last_write\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n            \"enable_limited_user_accounts\": subkey.get_value(\"EnableLUA\", as_json=self.as_json),\n            \"enable_virtualization\": subkey.get_value(\"EnableVirtualization\", as_json=self.as_json),\n            \"filter_admin_token\": subkey.get_value(\"FilterAdministratorToken\", as_json=self.as_json),\n            \"consent_prompt_admin\": subkey.get_value(\"ConsentPromptBehaviorAdmin\", as_json=self.as_json),\n            \"consent_prompt_user\": subkey.get_value(\"ConsentPromptBehaviorUser\", as_json=self.as_json),\n        }\n"
  },
  {
    "path": "regipy/plugins/software/winver.py",
    "content": "import logging\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SOFTWARE_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nWIN_VER_PATH = r\"\\Microsoft\\Windows NT\\CurrentVersion\"\nos_list = (\n    \"ProductName\",\n    \"ReleaseID\",\n    \"CSDVersion\",\n    \"CurrentVersion\",\n    \"CurrentBuild\",\n    \"CurrentBuildNumber\",\n    \"InstallationType\",\n    \"EditionID\",\n    \"ProductName\",\n    \"ProductId\",\n    \"BuildLab\",\n    \"BuildLabEx\",\n    \"CompositionEditionID\",\n    \"RegisteredOrganization\",\n    \"RegisteredOwner\",\n    \"InstallDate\",\n)\n\n\nclass WinVersionPlugin(Plugin):\n    NAME = \"winver_plugin\"\n    DESCRIPTION = \"Get relevant OS information\"\n    COMPATIBLE_HIVE = SOFTWARE_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SOFTWARE_HIVE_TYPE\n\n    def run(self):\n        logger.info(\"Started winver Plugin...\")\n\n        try:\n            key = self.registry_hive.get_key(WIN_VER_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {WIN_VER_PATH}: {ex}\")\n            return None\n\n        self.entries = {WIN_VER_PATH: {\"last_write\": convert_wintime(key.header.last_modified).isoformat()}}\n\n        for val in key.iter_values():\n            if val.name in os_list:\n                if val.name == \"InstallDate\":\n                    self.entries[WIN_VER_PATH][val.name] = datetime.fromtimestamp(val.value, timezone.utc).strftime(\n                        \"%Y-%m-%d %H:%M:%S\"\n                    )\n                else:\n                    self.entries[WIN_VER_PATH][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/system/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/system/active_controlset.py",
    "content": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nSELECT = r\"\\Select\"\n\n\nclass ActiveControlSetPlugin(Plugin):\n    NAME = \"active_control_set\"\n    DESCRIPTION = \"Get information on SYSTEM hive control sets\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        subkey = self.registry_hive.get_key(SELECT)\n        self.entries = list(subkey.iter_values(as_json=self.as_json))\n        if self.as_json:\n            self.entries = [asdict(x) for x in self.entries]\n"
  },
  {
    "path": "regipy/plugins/system/appcertdlls.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSESSION_MANAGER_PATH = r\"Control\\Session Manager\"\n\n\nclass AppCertDLLsPlugin(Plugin):\n    \"\"\"\n    Parses AppCertDLLs persistence mechanism from SYSTEM hive\n\n    AppCertDLLs contains DLLs that are loaded into every process that calls\n    CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW,\n    CreateProcessWithTokenW, or WinExec.\n\n    This is a known persistence and code injection technique.\n\n    Registry Key: ControlSet*\\\\Control\\\\Session Manager\n    Value: AppCertDLLs (REG_MULTI_SZ)\n    \"\"\"\n\n    NAME = \"appcert_dlls\"\n    DESCRIPTION = \"Parses AppCertDLLs persistence entries\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started AppCertDLLs Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(SESSION_MANAGER_PATH):\n            try:\n                session_manager_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find Session Manager at: {controlset_path}\")\n                continue\n\n            for value in session_manager_key.iter_values():\n                if value.name == \"AppCertDLLs\":\n                    entry = {\n                        \"key_path\": controlset_path,\n                        \"last_write\": convert_wintime(session_manager_key.header.last_modified, as_json=self.as_json),\n                        \"appcert_dlls\": value.value,\n                    }\n                    self.entries.append(entry)\n                    break\n"
  },
  {
    "path": "regipy/plugins/system/backuprestore.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nBACKUPRESTORE_PATH = [\n    r\"Control\\BackupRestore\\FilesNotToSnapshot\",\n    r\"Control\\BackupRestore\\FilesNotToBackup\",\n    r\"Control\\BackupRestore\\KeysNotToRestore\",\n]\n\n\nclass BackupRestorePlugin(Plugin):\n    NAME = \"backuprestore_plugin\"\n    DESCRIPTION = \"Gets the contents of the FilesNotToSnapshot, KeysNotToRestore, and FilesNotToBackup keys\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        for br_path in BACKUPRESTORE_PATH:\n            br_subkeys = self.registry_hive.get_control_sets(br_path)\n            for br_subkey in br_subkeys:\n                try:\n                    backuprestore = self.registry_hive.get_key(br_subkey)\n                except RegistryKeyNotFoundException as ex:\n                    logger.error(f\"Could not find {self.NAME} subkey at {br_subkey}: {ex}\")\n                    continue\n                self.entries[br_subkey] = {\"last_write\": convert_wintime(backuprestore.header.last_modified).isoformat()}\n                for val in backuprestore.iter_values():\n                    self.entries[br_subkey][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/system/bam.py",
    "content": "import logging\n\nfrom construct import Int64ul\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nBAM_PATH = [r\"Services\\bam\\UserSettings\", r\"Services\\bam\\state\\UserSettings\"]\n\n\nclass BAMPlugin(Plugin):\n    NAME = \"background_activity_moderator\"\n    DESCRIPTION = \"Get the computer name\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Computer Name Plugin...\")\n\n        for path in BAM_PATH:\n            try:\n                for subkey_path in self.registry_hive.get_control_sets(path):\n                    subkey = self.registry_hive.get_key(subkey_path)\n                    for sid_subkey in subkey.iter_subkeys():\n                        sid = sid_subkey.name\n                        logger.debug(f\"Parsing BAM for {sid}\")\n                        sequence_number = None\n                        version = None\n                        entries = []\n\n                        for value in sid_subkey.get_values(trim_values=self.trim_values):\n                            if value.name == \"SequenceNumber\":\n                                sequence_number = value.value\n                            elif value.name == \"Version\":\n                                version = value.value\n                            else:\n                                entries.append(\n                                    {\n                                        \"executable\": value.name,\n                                        \"timestamp\": convert_wintime(\n                                            Int64ul.parse(value.value),\n                                            as_json=self.as_json,\n                                        ),\n                                    }\n                                )\n\n                        self.entries.extend(\n                            [\n                                {\n                                    \"sequence_number\": sequence_number,\n                                    \"version\": version,\n                                    \"sid\": sid,\n                                    \"key_path\": f\"{subkey_path}\\\\{sid}\",\n                                    **x,\n                                }\n                                for x in entries\n                            ]\n                        )\n            except RegistryKeyNotFoundException as ex:\n                logger.error(ex)\n"
  },
  {
    "path": "regipy/plugins/system/bootkey.py",
    "content": "\"\"\"\nThe boot key is an encryption key that is stored in\nthe Windows SYSTEM registry hive.\n\nThis key is used by several Windows components to encrypt\nsensitive information like the AD database,\nmachine account password or system certificates etc.\n\"\"\"\n\nimport logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.registry import NKRecord\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nLSA_KEY_PATH = r\"Control\\Lsa\"\n\n\ndef _collect_bootkey(lsa_key: NKRecord) -> str:\n    \"\"\"\n    Extracts the 128-bit scrambled boot key from four \"secret\"\n    LSA subkeys as a 32-character hex string.\n    \"\"\"\n\n    # The boot key is taken from four separate keys:\n    # SYSTEM\\CurrentControlSet\\Control\\Lsa\\{JD,Skew1,GBG,Data}.\n    # However, the actual data needed is stored in a hidden field of the key\n    # that cannot be seen using tools like regedit.\n    # Specifically, each part of the key is stored in the key's Class attribute,\n    # and is stored as a Unicode string giving the hex value of that piece of the key.\n\n    bootkey_subkeys = {\n        \"JD\": \"\",\n        \"Skew1\": \"\",\n        \"GBG\": \"\",\n        \"Data\": \"\",\n    }\n\n    for subkey in lsa_key.iter_subkeys():\n        if subkey.name in bootkey_subkeys:\n            bootkey_subkeys[subkey.name] = subkey.get_class_name()\n\n    return \"\".join(bootkey_subkeys.values())\n\n\n# Permutation matrix for the boot key\n# fmt: off\n_BOOTKEY_PBOX = [\n    0x8, 0x5, 0x4, 0x2,\n    0xB, 0x9, 0xD, 0x3,\n    0x0, 0x6, 0x1, 0xC,\n    0xE, 0xA, 0xF, 0x7\n]\n# fmt: on\n\n\ndef _descramble_bootkey(key: str) -> bytes:\n    \"\"\"\n    Parses the 128-bit binary boot key from a hex string\n    and reverses the bytewise scrambling transform.\n    \"\"\"\n\n    binkey = bytes.fromhex(key)\n\n    return bytes([binkey[i] for i in _BOOTKEY_PBOX])\n\n\nclass BootKeyPlugin(Plugin):\n    \"\"\"\n    The boot key is an encryption key that is stored in\n    the Windows SYSTEM registry hive.\n    \"\"\"\n\n    NAME = \"bootkey\"\n    DESCRIPTION = \"Get the Windows boot key\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started BootKey Plugin...\")\n\n        for subkey_path in self.registry_hive.get_control_sets(LSA_KEY_PATH):\n            lsa_key = self.registry_hive.get_key(subkey_path)\n\n            bootkey = _descramble_bootkey(_collect_bootkey(lsa_key))\n\n            self.entries.append(\n                {\n                    \"key\": bootkey.hex() if self.as_json else bootkey,\n                    \"timestamp\": convert_wintime(lsa_key.header.last_modified, as_json=self.as_json),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/system/codepage.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nPROCESSOR_PATH = r\"Control\\Nls\\CodePage\"\ncrash_items = \"ACP\"\n\n\nclass CodepagePlugin(Plugin):\n    NAME = \"codepage\"\n    DESCRIPTION = \"Get codepage value\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        codepage_subkeys = self.registry_hive.get_control_sets(PROCESSOR_PATH)\n        for codepage_subkey in codepage_subkeys:\n            try:\n                codepage = self.registry_hive.get_key(codepage_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {codepage_subkey}: {ex}\")\n                continue\n            self.entries[codepage_subkey] = {\"last_write\": convert_wintime(codepage.header.last_modified).isoformat()}\n            for val in codepage.iter_values():\n                if val.name in crash_items:\n                    self.entries[codepage_subkey][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/system/computer_name.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryValueNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nCOMPUTER_NAME_PATH = r\"Control\\ComputerName\\ComputerName\"\n\n\nclass ComputerNamePlugin(Plugin):\n    NAME: str = \"computer_name\"\n    DESCRIPTION = \"Get the computer name\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Computer Name Plugin...\")\n\n        for subkey_path in self.registry_hive.get_control_sets(COMPUTER_NAME_PATH):\n            subkey = self.registry_hive.get_key(subkey_path)\n\n            try:\n                self.entries.append(\n                    {\n                        \"name\": subkey.get_value(\"ComputerName\", as_json=self.as_json),\n                        \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                    }\n                )\n            except RegistryValueNotFoundException:\n                logger.exception(\"Could not get computer name\")\n                continue\n"
  },
  {
    "path": "regipy/plugins/system/crash_dump.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nPROCESSOR_PATH = r\"Control\\CrashControl\"\ncrash_items = (\"CrashDumpEnabled\", \"DumpFile\", \"MinidumpDir\", \"LogEvent\")\ndump_enabled = {\n    \"0\": \"None\",\n    \"1\": \"Complete memory dump\",\n    \"2\": \"Kernel memory dump\",\n    \"3\": \"Small memory dump (64 KB)\",\n    \"7\": \"Automatic memory dump\",\n}\n\n\nclass CrashDumpPlugin(Plugin):\n    NAME = \"crash_dump\"\n    DESCRIPTION = \"Get crash control information\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        architecture_subkeys = self.registry_hive.get_control_sets(PROCESSOR_PATH)\n        for architecture_subkey in architecture_subkeys:\n            try:\n                architecture = self.registry_hive.get_key(architecture_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {architecture_subkey}: {ex}\")\n                continue\n            self.entries[architecture_subkey] = {\"last_write\": convert_wintime(architecture.header.last_modified).isoformat()}\n            for val in architecture.iter_values():\n                if val.name in crash_items:\n                    self.entries[architecture_subkey][val.name] = val.value\n                if val.name == \"CrashDumpEnabled\":\n                    self.entries[architecture_subkey][\"CrashDumpEnabledStr\"] = dump_enabled.get(str(val.value), \"\")\n"
  },
  {
    "path": "regipy/plugins/system/diag_sr.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_filetime2, convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nDIAGSR_PATH = r\"Services\\VSS\\Diag\\SystemRestore\"\ncrash_items = (\"CrashDumpEnabled\", \"DumpFile\", \"MinidumpDir\", \"LogEvent\")\n\n\nclass DiagSRPlugin(Plugin):\n    NAME = \"diag_sr\"\n    DESCRIPTION = \"Get Diag\\\\SystemRestore values and data\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        diagsr_subkeys = self.registry_hive.get_control_sets(DIAGSR_PATH)\n        for diagsr_subkey in diagsr_subkeys:\n            try:\n                diagsr = self.registry_hive.get_key(diagsr_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {diagsr_subkey}: {ex}\")\n                continue\n            self.entries[diagsr_subkey] = {\"last_write\": convert_wintime(diagsr.header.last_modified).isoformat()}\n            for val in diagsr.iter_values():\n                self.entries[diagsr_subkey][val.name] = convert_filetime2(val.value[16:32])\n"
  },
  {
    "path": "regipy/plugins/system/disablelastaccess.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\nLAST_ACCESS_PATH = r\"Control\\FileSystem\"\ncrash_items = (\"NtfsDisableLastAccessUpdate\", \"NtfsDisableLastAccessUpdate\")\nlast_acc = {\n    \"80000000\": \"(User Managed, Updates Enabled)\",\n    \"80000001\": \"(User Managed, Updates Disabled)\",\n    \"80000002\": \"(System Managed, Updates Enabled)\",\n    \"80000003\": \"(System Managed, Updates Disabled)\",\n}\n\n\nclass DisableLastAccessPlugin(Plugin):\n    NAME = \"disable_last_access\"\n    DESCRIPTION = \"Get NTFSDisableLastAccessUpdate value\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        access_subkeys = self.registry_hive.get_control_sets(LAST_ACCESS_PATH)\n        for access_subkey in access_subkeys:\n            try:\n                access = self.registry_hive.get_key(access_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {access_subkey}: {ex}\")\n                continue\n            self.entries[access_subkey] = {\"last_write\": convert_wintime(access.header.last_modified).isoformat()}\n            for val in access.iter_values():\n                if val.name in crash_items:\n                    self.entries[access_subkey][val.name] = f\"{val.value:0x}\"\n                    self.entries[access_subkey][f\"{val.name}Str\"] = last_acc.get(f\"{val.value:0x}\", \"\")\n"
  },
  {
    "path": "regipy/plugins/system/external/ShimCacheParser.py",
    "content": "# Andrew Davis, andrew.davis@mandiant.com\n# Copyright 2012 Mandiant\n#\n# Mandiant licenses this file to you under the Apache License, Version\n# 2.0 (the \"License\"); you may not use this file except in compliance with the\n# License.  You may obtain a copy of the License at:\n#\n#        http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n# implied.  See the License for the specific language governing\n# permissions and limitations under the License.\n#\n# Identifies and parses Application Compatibility Shim Cache entries for forensic data.\n\n\n# Original Mandiant shim cache parser, ported to Python 3\n# Identifies and parses Application Compatibility Shim Cache entries for forensic data.\n\nimport datetime\nimport io as sio\nimport logging\nimport struct\n\nimport pytz\n\nCACHE_MAGIC_NT5_2 = 0xBADC0FFE\nCACHE_HEADER_SIZE_NT5_2 = 0x8\nNT5_2_ENTRY_SIZE32 = 0x18\nNT5_2_ENTRY_SIZE64 = 0x20\n\n# Values used by Windows 6.1 (Win7 and Server 2008 R2)\nCACHE_MAGIC_NT6_1 = 0xBADC0FEE\nCACHE_HEADER_SIZE_NT6_1 = 0x80\nNT6_1_ENTRY_SIZE32 = 0x20\nNT6_1_ENTRY_SIZE64 = 0x30\nCSRSS_FLAG = 0x2\n\n# Values used by Windows 5.1 (WinXP 32-bit)\nWINXP_MAGIC32 = 0xDEADBEEF\nWINXP_HEADER_SIZE32 = 0x190\nWINXP_ENTRY_SIZE32 = 0x228\nMAX_PATH = 520\n\n# Values used by Windows 8\nWIN8_STATS_SIZE = 0x80\nWIN8_MAGIC = b\"00ts\"\n\n# Magic value used by Windows 8.1\nWIN81_MAGIC = b\"10ts\"\n\n# Values used by Windows 10\nWIN10_STATS_SIZE = 0x30\nWIN10_MAGIC = b\"10ts\"\nCACHE_HEADER_SIZE_NT6_4 = 0x30\nCACHE_MAGIC_NT6_4 = 0x30\n\nBAD_ENTRY_DATA = \"N/A\"\nG_VERBOSE = False\nG_USE_BOM = False\nOUTPUT_HEADER = [\"Last Modified\", \"Last Update\", \"Path\", \"File Size\", \"Exec Flag\"]\n\nlogger = logging.getLogger(__name__)\n\n\n# Shim Cache format used by Windows 5.2 and 6.0 (Server 2003 through Vista/Server 2008)\nclass CacheEntryNt5:\n    def __init__(self, is_32_bit, data=None):\n        self.w_length = None\n        self.w_maximum_length = None\n        self.offset = None\n        self.dw_low_date_time = None\n        self.dw_high_date_time = None\n        self.dw_file_size_low = None\n        self.dw_file_size_high = None\n        self.is_32_bit = is_32_bit\n        if data is not None:\n            self.update(data)\n\n    def update(self, data):\n        if self.is_32_bit:\n            entry = struct.unpack(\"<2H 3L 2L\", data)\n        else:\n            entry = struct.unpack(\"<2H 4x Q 2L 2L\", data)\n        self.w_length = entry[0]\n        self.w_maximum_length = entry[1]\n        self.offset = entry[2]\n        self.dw_low_date_time = entry[3]\n        self.dw_high_date_time = entry[4]\n        self.dw_file_size_low = entry[5]\n        self.dw_file_size_high = entry[6]\n\n    def size(self):\n        if self.is_32_bit:\n            return NT5_2_ENTRY_SIZE32\n        else:\n            return NT5_2_ENTRY_SIZE64\n\n\n# Shim Cache format used by Windows 6.1 (Win7 through Server 2008 R2).\nclass CacheEntryNt6:\n    def __init__(self, is_32_bit, data=None):\n        self.w_length = None\n        self.w_maximum_length = None\n        self.offset = None\n        self.dw_low_date_time = None\n        self.dw_high_date_time = None\n        self.file_flags = None\n        self.flags = None\n        self.blob_size = None\n        self.blob_offset = None\n        self.is_32_bit = is_32_bit\n        if data is not None:\n            self.update(data)\n\n    def update(self, data):\n        if self.is_32_bit:\n            entry = struct.unpack(\"<2H 7L\", data)\n        else:\n            entry = struct.unpack(\"<2H 4x Q 4L 2Q\", data)\n        self.w_length = entry[0]\n        self.w_maximum_length = entry[1]\n        self.offset = entry[2]\n        self.dw_low_date_time = entry[3]\n        self.dw_high_date_time = entry[4]\n        self.file_flags = entry[5]\n        self.flags = entry[6]\n        self.blob_size = entry[7]\n        self.blob_offset = entry[8]\n\n    def size(self):\n        if self.is_32_bit:\n            return NT6_1_ENTRY_SIZE32\n        else:\n            return NT6_1_ENTRY_SIZE64\n\n\n# Convert FILETIME to datetime.\n# Based on http://code.activestate.com/recipes/511425-filetime-to-datetime/\ndef convert_filetime(dw_low_date_time, dw_high_date_time):\n    try:\n        date = datetime.datetime(1601, 1, 1, 0, 0, 0)\n        temp_time = dw_high_date_time\n        temp_time <<= 32\n        temp_time |= dw_low_date_time\n        return pytz.utc.localize(date + datetime.timedelta(microseconds=temp_time / 10))\n    except OverflowError:\n        return None\n\n\n# Return a unique list while preserving ordering.\ndef unique_list(li):\n    ret_list = []\n    for entry in li:\n        if entry not in ret_list:\n            ret_list.append(entry)\n    return ret_list\n\n\n# Read the Shim Cache format, return a list of last modified dates/paths.\ndef get_shimcache_entries(cachebin, as_json=False):\n    if len(cachebin) < 16:\n        # Data size less than minimum header size.\n        return None\n\n    # Get the format type\n    magic = struct.unpack(\"<L\", cachebin[0:4])[0]\n\n    # This is a Windows 2k3/Vista/2k8 Shim Cache format,\n    if magic == CACHE_MAGIC_NT5_2:\n        # Shim Cache types can come in 32-bit or 64-bit formats. We can\n        # determine this because 64-bit entries are serialized with u_int64\n        # pointers. This means that in a 64-bit entry, valid UNICODE_STRING\n        # sizes are followed by a NULL DWORD. Check for this here.\n        test_size = struct.unpack(\"<H\", cachebin[8:10])[0]\n        test_max_size = struct.unpack(\"<H\", cachebin[10:12])[0]\n        if (test_max_size - test_size == 2 and struct.unpack(\"<L\", cachebin[12:16])[0]) == 0:\n            logger.debug(\"[+] Found 64bit Windows 2k3/Vista/2k8 Shim Cache data...\")\n            entry = CacheEntryNt5(False)\n            yield from read_nt5_entries(cachebin, entry, as_json=as_json)\n\n        # Otherwise it's 32-bit data.\n        else:\n            logger.debug(\"[+] Found 32bit Windows 2k3/Vista/2k8 Shim Cache data...\")\n            entry = CacheEntryNt5(True)\n            yield from read_nt5_entries(cachebin, entry, as_json=as_json)\n\n    # This is a Windows 7/2k8-R2 Shim Cache.\n    elif magic == CACHE_MAGIC_NT6_1:\n        test_size = struct.unpack(\"<H\", cachebin[CACHE_HEADER_SIZE_NT6_1 : CACHE_HEADER_SIZE_NT6_1 + 2])[0]\n        test_max_size = struct.unpack(\"<H\", cachebin[CACHE_HEADER_SIZE_NT6_1 + 2 : CACHE_HEADER_SIZE_NT6_1 + 4])[0]\n\n        # Shim Cache types can come in 32-bit or 64-bit formats.\n        # We can determine this because 64-bit entries are serialized with\n        # u_int64 pointers. This means that in a 64-bit entry, valid\n        # UNICODE_STRING sizes are followed by a NULL DWORD. Check for this here.\n        if (\n            test_max_size - test_size == 2\n            and struct.unpack(\n                \"<L\",\n                cachebin[CACHE_HEADER_SIZE_NT6_1 + 4 : CACHE_HEADER_SIZE_NT6_1 + 8],\n            )[0]\n        ) == 0:\n            logger.debug(\"[+] Found 64bit Windows 7/2k8-R2 Shim Cache data...\")\n            entry = CacheEntryNt6(False)\n            yield from read_nt6_entries(cachebin, entry, as_json=as_json)\n        else:\n            logger.debug(\"[+] Found 32bit Windows 7/2k8-R2 Shim Cache data...\")\n            entry = CacheEntryNt6(True)\n            yield from read_nt6_entries(cachebin, entry, as_json=as_json)\n\n    # This is WinXP cache data\n    elif magic == WINXP_MAGIC32:\n        logger.debug(\"[+] Found 32bit Windows XP Shim Cache data...\")\n        yield from read_winxp_entries(cachebin, as_json=as_json)\n\n    # Check the data set to see if it matches the Windows 8 format.\n    elif len(cachebin) > WIN8_STATS_SIZE and cachebin[WIN8_STATS_SIZE : WIN8_STATS_SIZE + 4] == WIN8_MAGIC:\n        logger.debug(\"[+] Found Windows 8/2k12 Apphelp Cache data...\")\n        yield from read_win8_entries(cachebin, WIN8_MAGIC, as_json=as_json)\n\n    # Windows 8.1 will use a different magic dword, check for it\n    elif len(cachebin) > WIN8_STATS_SIZE and cachebin[WIN8_STATS_SIZE : WIN8_STATS_SIZE + 4] == WIN81_MAGIC:\n        logger.debug(\"[+] Found Windows 8.1 Apphelp Cache data...\")\n        yield from read_win8_entries(cachebin, WIN81_MAGIC, as_json=as_json)\n\n    # Windows 10 will use a different magic dword, check for it\n    elif len(cachebin) > WIN10_STATS_SIZE and cachebin[WIN10_STATS_SIZE : WIN10_STATS_SIZE + 4] == WIN10_MAGIC:\n        logger.debug(\"[+] Found Windows 10 Apphelp Cache data...\")\n        yield from read_win10_entries(cachebin, WIN10_MAGIC, as_json=as_json)\n\n    # Windows 10 creators update moved the damn magic 4 bytes forward...\n    elif len(cachebin) > WIN10_STATS_SIZE and cachebin[WIN10_STATS_SIZE + 4 : WIN10_STATS_SIZE + 8] == WIN10_MAGIC:\n        logger.debug(\"[+] Found Windows 10 Apphelp Cache data... (creators update)\")\n        yield from read_win10_entries(cachebin, WIN10_MAGIC, creators_update=True, as_json=as_json)\n\n    else:\n        raise Exception(f\"Got an unrecognized magic value of 0x{magic:x}... bailing\")\n\n\n# Read Windows 8/2k12/8.1 Apphelp Cache entry formats.\ndef read_win8_entries(bin_data, ver_magic, as_json=False):\n    entry_meta_len = 12\n\n    # Skip past the stats in the header\n    cache_data = bin_data[WIN8_STATS_SIZE:]\n\n    data = sio.BytesIO(cache_data)\n    while data.tell() < len(cache_data):\n        header = data.read(entry_meta_len)\n        # Read in the entry metadata\n        # Note: the crc32 hash is of the cache entry data\n        magic, crc32_hash, entry_len = struct.unpack(\"<4sLL\", header)\n\n        # Check the magic tag\n        if magic != ver_magic:\n            raise Exception(\"Invalid version magic tag found: {}\".format(struct.unpack(\"I\", magic)[0]))\n\n        entry_data = sio.BytesIO(data.read(entry_len))\n\n        # Read the path length\n        path_len = struct.unpack(\"<H\", entry_data.read(2))[0]\n        if path_len == 0:\n            path = \"None\"\n        else:\n            path = entry_data.read(path_len).decode(\"utf-16le\", \"replace\")\n\n        # Check for package data\n        package_len = struct.unpack(\"<H\", entry_data.read(2))[0]\n        if package_len > 0:\n            # Just skip past the package data if present (for now)\n            entry_data.seek(package_len, 1)\n\n        # Read the remaining entry data\n        flags, unk_1, low_datetime, high_datetime, unk_2 = struct.unpack(\"<LLLLL\", entry_data.read(20))\n\n        # Check the flag set in CSRSS\n        if flags & CSRSS_FLAG:\n            exec_flag = \"True\"\n        else:\n            exec_flag = \"False\"\n\n        last_mod_date = convert_filetime(low_datetime, high_datetime)\n\n        yield {\n            \"last_mod_date\": last_mod_date.isoformat() if as_json else last_mod_date,\n            \"path\": path,\n            \"exec_flag\": exec_flag,\n        }\n\n\n# Read Windows 10 Apphelp Cache entry format\ndef read_win10_entries(bin_data, ver_magic, creators_update=False, as_json=False):\n    entry_meta_len = 12\n\n    # Skip past the stats in the header\n    if creators_update:\n        cache_data = bin_data[WIN10_STATS_SIZE + 4 :]\n    else:\n        cache_data = bin_data[WIN10_STATS_SIZE:]\n\n    data = sio.BytesIO(cache_data)\n    while data.tell() < len(cache_data):\n        header = data.read(entry_meta_len)\n        # Read in the entry metadata\n        # Note: the crc32 hash is of the cache entry data\n        magic, crc32_hash, entry_len = struct.unpack(\"<4sLL\", header)\n\n        # Check the magic tag\n        if magic != ver_magic:\n            raise Exception(\"Invalid version magic tag found: {}\".format(struct.unpack(\"<I\", magic)[0]))\n\n        entry_data = sio.BytesIO(data.read(entry_len))\n\n        # Read the path length\n        path_len = struct.unpack(\"<H\", entry_data.read(2))[0]\n        if path_len == 0:\n            path = \"None\"\n        else:\n            path = entry_data.read(path_len).decode(\"utf-16le\", \"replace\")\n\n        # Read the remaining entry data\n        low_datetime, high_datetime = struct.unpack(\"<LL\", entry_data.read(8))\n\n        # Skip the unrecognized Microsoft App entry format for now\n        if not (low_datetime + high_datetime):\n            continue\n        else:\n            last_mod_date = convert_filetime(low_datetime, high_datetime)\n\n        yield {\n            \"last_mod_date\": last_mod_date.isoformat() if as_json else last_mod_date,\n            \"path\": path,\n        }\n\n\n# Read Windows 2k3/Vista/2k8 Shim Cache entry formats.\ndef read_nt5_entries(bin_data, entry, as_json=False):\n    contains_file_size = False\n    entry_size = entry.size()\n\n    num_entries = struct.unpack(\"<L\", bin_data[4:8])[0]\n    if num_entries == 0:\n        return None\n\n    # On Windows Server 2008/Vista, the filesize is swapped out of this\n    # structure with two 4-byte flags. Check to see if any of the values in\n    # \"dwFileSizeLow\" are larger than 2-bits. This indicates the entry contained file sizes.\n    for offset in range(\n        CACHE_HEADER_SIZE_NT5_2,\n        (num_entries * entry_size) + CACHE_HEADER_SIZE_NT5_2,\n        entry_size,\n    ):\n        entry.update(bin_data[offset : offset + entry_size])\n\n        if entry.dw_file_size_low > 3:\n            contains_file_size = True\n            break\n\n    # Now grab all the data in the value.\n    for offset in range(\n        CACHE_HEADER_SIZE_NT5_2,\n        (num_entries * entry_size) + CACHE_HEADER_SIZE_NT5_2,\n        entry_size,\n    ):\n        entry.update(bin_data[offset : offset + entry_size])\n\n        last_mod_date = convert_filetime(entry.dw_low_date_time, entry.dw_high_date_time)\n        path = bin_data[entry.offsets : entry.offset + entry.w_length].decode(\"utf-16le\", \"replace\")\n\n        # It contains file size data.\n        exec_flag = None\n        if contains_file_size:\n            yield {\n                \"last_mod_date\": (last_mod_date.isoformat() if as_json else last_mod_date),\n                \"path\": path,\n                \"file_size\": entry.dw_file_size_low,\n            }\n\n        # It contains flags.\n        else:\n            # Check the flag set in CSRSS\n            if entry.dw_file_size_low & CSRSS_FLAG:\n                exec_flag = \"True\"\n            else:\n                exec_flag = \"False\"\n\n            yield {\n                \"last_mod_date\": (last_mod_date.isoformat() if as_json else last_mod_date),\n                \"path\": path,\n                \"exec_flag\": exec_flag,\n            }\n\n\n# Read the Shim Cache Windows 7/2k8-R2 entry format,\n# return a list of last modifed dates/paths.\ndef read_nt6_entries(bin_data, entry, as_json=False):\n    entry_size = entry.size()\n    num_entries = struct.unpack(\"<L\", bin_data[4:8])[0]\n\n    if num_entries == 0:\n        return None\n\n    # Walk each entry in the data structure.\n    for offset in range(\n        CACHE_HEADER_SIZE_NT6_1,\n        num_entries * entry_size + CACHE_HEADER_SIZE_NT6_1,\n        entry_size,\n    ):\n        entry.update(bin_data[offset : offset + entry_size])\n        last_mod_date = convert_filetime(entry.dw_low_date_time, entry.dw_high_date_time)\n        path = bin_data[entry.offset : entry.offset + entry.w_length].decode(\"utf-16le\", \"replace\")\n\n        # Test to see if the file may have been executed.\n        if entry.file_flags & CSRSS_FLAG:\n            exec_flag = \"True\"\n        else:\n            exec_flag = \"False\"\n\n        yield {\n            \"last_mod_date\": last_mod_date.isoformat() if as_json else last_mod_date,\n            \"path\": path,\n            \"exec_flag\": exec_flag,\n        }\n\n\n# Read the WinXP Shim Cache data. Some entries can be missing data but still\n# contain useful information, so try to get as much as we can.\ndef read_winxp_entries(bin_data, as_json=False):\n    num_entries = struct.unpack(\"<L\", bin_data[8:12])[0]\n    if num_entries == 0:\n        return None\n\n    for offset in range(\n        WINXP_HEADER_SIZE32,\n        (num_entries * WINXP_ENTRY_SIZE32) + WINXP_HEADER_SIZE32,\n        WINXP_ENTRY_SIZE32,\n    ):\n        # No size values are included in these entries, so search for utf-16 terminator.\n        path_len = bin_data[offset : offset + (MAX_PATH + 8)].find(b\"\\x00\\x00\")\n\n        # if path is corrupt, procede to next entry.\n        if path_len == 0:\n            continue\n        path = bin_data[offset : offset + path_len + 1].decode(\"utf-16le\")\n\n        if len(path) == 0:\n            continue\n\n        entry_data = offset + (MAX_PATH + 8)\n\n        # Get last mod time.\n        last_mod_time = struct.unpack(\"<2L\", bin_data[entry_data : entry_data + 8])\n        last_mod_time = convert_filetime(last_mod_time[0], last_mod_time[1])\n\n        # Get last file size.\n        file_size = struct.unpack(\"<2L\", bin_data[entry_data + 8 : entry_data + 16])[0]\n        if file_size == 0:\n            file_size = BAD_ENTRY_DATA\n\n        # Get last update time.\n        exec_time = struct.unpack(\"<2L\", bin_data[entry_data + 16 : entry_data + 24])\n        exec_time = convert_filetime(exec_time[0], exec_time[1])\n\n        yield {\n            \"last_mod_time\": last_mod_time.isoformat() if as_json else last_mod_time,\n            \"exec_time\": exec_time.isoformat() if as_json else exec_time,\n            \"path\": path,\n            \"file_size\": file_size,\n        }\n\n\ndef parse_output(output):\n    new_output_list = []\n    for row in output:\n        exec_flag = False\n        if row[4] == \"True\":\n            exec_flag = True\n        entry = {\n            \"last_mod_date\": row[0],\n            \"last_update\": row[1],\n            \"path\": row[2],\n            \"file_size\": row[3],\n            \"exec_flag\": exec_flag,\n        }\n        new_output_list.append(entry)\n    return new_output_list\n"
  },
  {
    "path": "regipy/plugins/system/external/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/system/host_domain_name.py",
    "content": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nHOST_PARAMETERS_PATH = r\"Services\\Tcpip\\Parameters\"\n\n\nclass HostDomainNamePlugin(Plugin):\n    NAME = \"host_domain_name\"\n    DESCRIPTION = \"Get the computer host and domain names\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Host and Domain Name Plugin...\")\n\n        for subkey_path in self.registry_hive.get_control_sets(HOST_PARAMETERS_PATH):\n            subkey = self.registry_hive.get_key(subkey_path)\n\n            hostname = subkey.get_value(\"Hostname\", as_json=self.as_json)\n            domain = subkey.get_value(\"Domain\", as_json=self.as_json)\n\n            # The default key value is 0x00000000 (REG_DWORD) when\n            # the Windows machine is not in an AD domain.\n            if not isinstance(domain, str):\n                domain = None\n\n            self.entries.append(\n                {\n                    \"hostname\": hostname,\n                    \"domain\": domain,\n                    \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                }\n            )\n"
  },
  {
    "path": "regipy/plugins/system/lsa_packages.py",
    "content": "\"\"\"\nLSA Packages plugin - Parses LSA security package configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nLSA_PATH = r\"Control\\Lsa\"\n\n\nclass LSAPackagesPlugin(Plugin):\n    \"\"\"\n    Parses LSA (Local Security Authority) configuration from SYSTEM hive\n\n    Provides information about:\n    - Authentication packages\n    - Security packages\n    - Notification packages\n    - Security provider DLLs\n\n    These are common persistence and credential theft locations.\n\n    Registry Key: Control\\\\Lsa under each ControlSet\n    \"\"\"\n\n    NAME = \"lsa_packages\"\n    DESCRIPTION = \"Parses LSA security packages configuration\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started LSA Packages Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(LSA_PATH):\n            try:\n                lsa_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find LSA at: {controlset_path}\")\n                continue\n\n            entry = {\n                \"key_path\": controlset_path,\n                \"last_write\": convert_wintime(lsa_key.header.last_modified, as_json=self.as_json),\n            }\n\n            for value in lsa_key.iter_values():\n                name = value.name\n                val = value.value\n\n                if name == \"Authentication Packages\":\n                    entry[\"authentication_packages\"] = val if isinstance(val, list) else [val] if val else []\n                elif name == \"Security Packages\":\n                    entry[\"security_packages\"] = val if isinstance(val, list) else [val] if val else []\n                elif name == \"Notification Packages\":\n                    entry[\"notification_packages\"] = val if isinstance(val, list) else [val] if val else []\n                elif name == \"RunAsPPL\":\n                    entry[\"run_as_ppl\"] = val == 1\n                elif name == \"DisableDomainCreds\":\n                    entry[\"disable_domain_creds\"] = val == 1\n                elif name == \"LimitBlankPasswordUse\":\n                    entry[\"limit_blank_password_use\"] = val == 1\n                elif name == \"NoLMHash\":\n                    entry[\"no_lm_hash\"] = val == 1\n                elif name == \"LmCompatibilityLevel\":\n                    entry[\"lm_compatibility_level\"] = val\n                    entry[\"lm_compatibility_description\"] = self._get_lm_compat_desc(val)\n                elif name == \"SecureBoot\":\n                    entry[\"secure_boot\"] = val\n                elif name == \"auditbasedirectories\":\n                    entry[\"audit_base_directories\"] = val == 1\n                elif name == \"auditbaseobjects\":\n                    entry[\"audit_base_objects\"] = val == 1\n                elif name == \"CrashOnAuditFail\":\n                    entry[\"crash_on_audit_fail\"] = val\n                elif name == \"FullPrivilegeAuditing\":\n                    entry[\"full_privilege_auditing\"] = val\n\n            # Check for OSConfig subkey\n            try:\n                osconfig_key = self.registry_hive.get_key(f\"{controlset_path}\\\\OSConfig\")\n                for value in osconfig_key.iter_values():\n                    if value.name == \"Security Packages\":\n                        entry[\"osconfig_security_packages\"] = (\n                            value.value if isinstance(value.value, list) else [value.value] if value.value else []\n                        )\n            except RegistryKeyNotFoundException:\n                pass\n\n            self.entries.append(entry)\n\n    @staticmethod\n    def _get_lm_compat_desc(level: int) -> str:\n        \"\"\"Get description for LM compatibility level\"\"\"\n        descriptions = {\n            0: \"Send LM & NTLM responses\",\n            1: \"Send LM & NTLM - use NTLMv2 if negotiated\",\n            2: \"Send NTLM response only\",\n            3: \"Send NTLMv2 response only\",\n            4: \"Send NTLMv2 response only, refuse LM\",\n            5: \"Send NTLMv2 response only, refuse LM & NTLM\",\n        }\n        return descriptions.get(level, f\"Unknown level ({level})\")\n"
  },
  {
    "path": "regipy/plugins/system/mountdev.py",
    "content": "\"\"\"\nMountedDevices plugin - Parses mounted device information\n\"\"\"\n\nimport logging\nimport re\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nMOUNTED_DEVICES_PATH = r\"\\MountedDevices\"\n\n\ndef parse_device_data(data: bytes) -> dict:\n    \"\"\"Parse the binary device data to extract device information\"\"\"\n    result = {}\n\n    if not data:\n        return result\n\n    try:\n        # Try to decode as unicode path\n        decoded = data.decode(\"utf-16-le\", errors=\"ignore\").rstrip(\"\\x00\")\n        if decoded:\n            result[\"path\"] = decoded\n\n            # Extract volume GUID if present\n            guid_match = re.search(r\"\\{[0-9A-Fa-f-]+\\}\", decoded)\n            if guid_match:\n                result[\"volume_guid\"] = guid_match.group(0)\n\n            # Check for device type indicators\n            if \"\\\\DosDevices\\\\\" in decoded:\n                result[\"type\"] = \"dos_device\"\n            elif \"\\\\??\\\\Volume\" in decoded:\n                result[\"type\"] = \"volume\"\n            elif \"_??_USBSTOR\" in decoded or \"USBSTOR\" in decoded:\n                result[\"type\"] = \"usb_storage\"\n            elif \"IDE\" in decoded or \"SCSI\" in decoded:\n                result[\"type\"] = \"disk\"\n\n    except Exception:\n        pass\n\n    # If no path found, check for disk signature format (12 bytes)\n    if \"path\" not in result and len(data) == 12:\n        # First 4 bytes are disk signature, next 8 bytes are partition offset\n        try:\n            disk_sig = data[0:4].hex()\n            partition_offset = int.from_bytes(data[4:12], \"little\")\n            result[\"disk_signature\"] = disk_sig\n            result[\"partition_offset\"] = partition_offset\n            result[\"type\"] = \"mbr_partition\"\n        except Exception:\n            pass\n\n    # Check for GPT disk format (24 bytes)\n    if \"path\" not in result and len(data) == 24:\n        try:\n            # GPT disks have a different format with GUID\n            guid_bytes = data[0:16]\n            partition_offset = int.from_bytes(data[16:24], \"little\")\n            # Format GUID\n            guid = (\n                f\"{guid_bytes[3]:02x}{guid_bytes[2]:02x}{guid_bytes[1]:02x}{guid_bytes[0]:02x}-\"\n                f\"{guid_bytes[5]:02x}{guid_bytes[4]:02x}-\"\n                f\"{guid_bytes[7]:02x}{guid_bytes[6]:02x}-\"\n                f\"{guid_bytes[8]:02x}{guid_bytes[9]:02x}-\"\n                f\"{guid_bytes[10]:02x}{guid_bytes[11]:02x}{guid_bytes[12]:02x}\"\n                f\"{guid_bytes[13]:02x}{guid_bytes[14]:02x}{guid_bytes[15]:02x}\"\n            )\n            result[\"disk_guid\"] = guid\n            result[\"partition_offset\"] = partition_offset\n            result[\"type\"] = \"gpt_partition\"\n        except Exception:\n            pass\n\n    return result\n\n\nclass MountedDevicesPlugin(Plugin):\n    \"\"\"\n    Parses MountedDevices from SYSTEM hive\n\n    Provides information about:\n    - Drive letter assignments\n    - Volume mappings\n    - USB device volume assignments\n    - Disk and partition identifiers\n\n    Registry Key: MountedDevices\n    \"\"\"\n\n    NAME = \"mounted_devices\"\n    DESCRIPTION = \"Parses mounted device information\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started MountedDevices Plugin...\")\n\n        try:\n            mounted_key = self.registry_hive.get_key(MOUNTED_DEVICES_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.debug(f\"Could not find MountedDevices at: {MOUNTED_DEVICES_PATH}: {ex}\")\n            return\n\n        base_entry = {\n            \"key_path\": MOUNTED_DEVICES_PATH,\n            \"last_write\": convert_wintime(mounted_key.header.last_modified, as_json=self.as_json),\n        }\n\n        for value in mounted_key.iter_values():\n            name = value.name\n            data = value.value\n\n            entry = base_entry.copy()\n            entry[\"value_name\"] = name\n\n            # Determine mount point type\n            if name.startswith(\"\\\\DosDevices\\\\\"):\n                entry[\"mount_point\"] = name.replace(\"\\\\DosDevices\\\\\", \"\")\n                entry[\"mount_type\"] = \"drive_letter\"\n            elif name.startswith(\"\\\\??\\\\Volume\"):\n                entry[\"mount_point\"] = name\n                entry[\"mount_type\"] = \"volume\"\n            elif name == \"#{\":\n                entry[\"mount_type\"] = \"database\"\n            else:\n                entry[\"mount_type\"] = \"other\"\n\n            # Parse the device data\n            if isinstance(data, bytes):\n                device_info = parse_device_data(data)\n                entry.update(device_info)\n                entry[\"data_size\"] = len(data)\n\n            self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/system/network_data.py",
    "content": "import logging\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nINTERFACES_PATH = r\"Services\\Tcpip\\Parameters\\Interfaces\"\n\n\nclass NetworkDataPlugin(Plugin):\n    NAME = \"network_data\"\n    DESCRIPTION = \"Get network data from many interfaces\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def get_network_info(self, subkey, interfaces=None):\n        if interfaces is None:\n            interfaces = []\n\n        try:\n            for interface in subkey.iter_subkeys():\n                entries = {\n                    \"interface_name\": interface.name,\n                    \"last_modified\": convert_wintime(interface.header.last_modified, as_json=self.as_json),\n                    \"incomplete_data\": False,  # New key to indicate incomplete data\n                    \"dhcp_enabled\": interface.get_value(\"EnableDHCP\") == 1,  # Boolean value\n                }\n\n                if entries[\"dhcp_enabled\"]:\n                    entries.update(\n                        {\n                            \"dhcp_server\": interface.get_value(\"DhcpServer\"),\n                            \"dhcp_ip_address\": interface.get_value(\"DhcpIPAddress\"),\n                            \"dhcp_subnet_mask\": interface.get_value(\"DhcpSubnetMask\"),\n                            \"dhcp_default_gateway\": interface.get_value(\"DhcpDefaultGateway\"),\n                            \"dhcp_name_server\": interface.get_value(\"DhcpNameServer\"),\n                            \"dhcp_domain\": interface.get_value(\"DhcpDomain\"),\n                        }\n                    )\n\n                    # Lease Obtained Time\n                    lease_obtained_time = interface.get_value(\"LeaseObtainedTime\")\n                    if lease_obtained_time is not None:\n                        try:\n                            lease_obtained_time_str = datetime.fromtimestamp(lease_obtained_time, timezone.utc).strftime(\n                                \"%Y-%m-%d %H:%M:%S\"\n                            )\n                            entries[\"dhcp_lease_obtained_time\"] = lease_obtained_time_str\n                        except (OSError, ValueError) as e:\n                            logger.error(f\"Error converting DHCP lease obtained time for interface {interface.name}: {e}\")\n                            entries[\"incomplete_data\"] = True\n\n                    # Lease Terminates Time\n                    lease_terminates_time = interface.get_value(\"LeaseTerminatesTime\")\n                    if lease_terminates_time is not None:\n                        try:\n                            lease_terminates_time_str = datetime.fromtimestamp(lease_terminates_time, timezone.utc).strftime(\n                                \"%Y-%m-%d %H:%M:%S\"\n                            )\n                            entries[\"dhcp_lease_terminates_time\"] = lease_terminates_time_str\n                        except (OSError, ValueError) as e:\n                            logger.error(f\"Error converting DHCP lease terminates time for interface {interface.name}: {e}\")\n                            entries[\"incomplete_data\"] = True\n\n                else:\n                    entries.update(\n                        {\n                            \"ip_address\": interface.get_value(\"IPAddress\"),\n                            \"subnet_mask\": interface.get_value(\"SubnetMask\"),\n                            \"default_gateway\": interface.get_value(\"DefaultGateway\"),\n                            \"name_server\": interface.get_value(\"NameServer\"),\n                            \"domain\": interface.get_value(\"Domain\"),\n                        }\n                    )\n\n                try:\n                    if interface.subkey_count:\n                        sub_interfaces = []\n                        sub_interfaces = self.get_network_info(interface, sub_interfaces)\n                        entries[\"sub_interface\"] = sub_interfaces\n                except Exception as e:\n                    logger.error(f\"Error processing sub-interfaces for interface {interface.name}: {e}\")\n                    entries[\"incomplete_data\"] = True\n\n                interfaces.append(entries)\n\n        except Exception as e:\n            logger.error(f\"Error iterating over subkeys in {subkey.path}: {e}\")\n\n        return interfaces\n\n    def run(self):\n        self.entries = {}\n\n        for control_set_interfaces_path in self.registry_hive.get_control_sets(INTERFACES_PATH):\n            try:\n                subkey = self.registry_hive.get_key(control_set_interfaces_path)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Registry key not found at path {control_set_interfaces_path}: {ex}\")\n                continue  # Skip to the next path if the key is not found\n\n            try:\n                self.entries[control_set_interfaces_path] = {\n                    \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json)\n                }\n                interfaces = []\n                interfaces = self.get_network_info(subkey, interfaces)\n                self.entries[control_set_interfaces_path][\"interfaces\"] = interfaces\n            except Exception as ex:\n                logger.error(f\"Error processing registry key {control_set_interfaces_path}: {ex}\")\n                self.entries[control_set_interfaces_path][\"incomplete_data\"] = True\n"
  },
  {
    "path": "regipy/plugins/system/pagefile.py",
    "content": "\"\"\"\nPagefile plugin - Parses pagefile configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nMEMORY_MANAGEMENT_PATH = r\"Control\\Session Manager\\Memory Management\"\n\n\nclass PagefilePlugin(Plugin):\n    \"\"\"\n    Parses pagefile configuration from SYSTEM hive\n\n    Provides information about:\n    - Pagefile locations\n    - Pagefile sizes\n    - ClearPageFileAtShutdown setting\n\n    Registry Key: Control\\\\Session Manager\\\\Memory Management\n    \"\"\"\n\n    NAME = \"pagefile\"\n    DESCRIPTION = \"Parses pagefile configuration\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Pagefile Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(MEMORY_MANAGEMENT_PATH):\n            try:\n                mm_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find Memory Management at: {controlset_path}\")\n                continue\n\n            entry = {\n                \"key_path\": controlset_path,\n                \"last_write\": convert_wintime(mm_key.header.last_modified, as_json=self.as_json),\n            }\n\n            for value in mm_key.iter_values():\n                name = value.name\n                val = value.value\n\n                if name == \"PagingFiles\":\n                    # REG_MULTI_SZ containing pagefile paths and sizes\n                    # Format: \"C:\\pagefile.sys min_size max_size\"\n                    if isinstance(val, list):\n                        entry[\"paging_files\"] = val\n                        entry[\"parsed_paging_files\"] = []\n                        for pf in val:\n                            if pf:\n                                parts = pf.split()\n                                pf_entry = {\"path\": parts[0]}\n                                if len(parts) >= 2:\n                                    pf_entry[\"min_size_mb\"] = int(parts[1]) if parts[1].isdigit() else parts[1]\n                                if len(parts) >= 3:\n                                    pf_entry[\"max_size_mb\"] = int(parts[2]) if parts[2].isdigit() else parts[2]\n                                entry[\"parsed_paging_files\"].append(pf_entry)\n                    else:\n                        entry[\"paging_files\"] = val\n                elif name == \"ExistingPageFiles\":\n                    entry[\"existing_page_files\"] = val\n                elif name == \"ClearPageFileAtShutdown\":\n                    entry[\"clear_pagefile_at_shutdown\"] = val == 1\n                elif name == \"PagefileOnOsVolume\":\n                    entry[\"pagefile_on_os_volume\"] = val\n                elif name == \"TempPageFile\":\n                    entry[\"temp_page_file\"] = val\n\n            if \"paging_files\" in entry or \"existing_page_files\" in entry:\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/system/pending_file_rename.py",
    "content": "\"\"\"\nPending File Rename plugin - Parses pending file rename operations\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSESSION_MANAGER_PATH = r\"Control\\Session Manager\"\n\n\nclass PendingFileRenamePlugin(Plugin):\n    \"\"\"\n    Parses Pending File Rename Operations from SYSTEM hive\n\n    This registry value contains files scheduled for rename/delete on reboot.\n    This is commonly used by installers and can also be abused by malware.\n\n    Registry Key: Control\\\\Session Manager\n    Values:\n    - PendingFileRenameOperations\n    - PendingFileRenameOperations2\n\n    Format: Source path, destination path (or empty for delete), repeating\n    \"\"\"\n\n    NAME = \"pending_file_rename\"\n    DESCRIPTION = \"Parses pending file rename operations\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Pending File Rename Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(SESSION_MANAGER_PATH):\n            try:\n                sm_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find Session Manager at: {controlset_path}\")\n                continue\n\n            entry = {\n                \"key_path\": controlset_path,\n                \"last_write\": convert_wintime(sm_key.header.last_modified, as_json=self.as_json),\n                \"operations\": [],\n            }\n\n            for value in sm_key.iter_values():\n                if value.name in [\"PendingFileRenameOperations\", \"PendingFileRenameOperations2\"]:\n                    operations = self._parse_operations(value.value, value.name)\n                    entry[\"operations\"].extend(operations)\n\n            if entry[\"operations\"]:\n                self.entries.append(entry)\n\n    def _parse_operations(self, data, value_name: str) -> list:\n        \"\"\"Parse pending file rename operations\"\"\"\n        operations = []\n\n        if not data:\n            return operations\n\n        # Data is REG_MULTI_SZ - list of strings\n        if isinstance(data, list):\n            items = data\n        elif isinstance(data, str):\n            items = [data]\n        else:\n            return operations\n\n        # Operations come in pairs: source, destination (or empty for delete)\n        i = 0\n        while i < len(items):\n            source = items[i] if i < len(items) else None\n            destination = items[i + 1] if i + 1 < len(items) else None\n\n            if source:\n                # Remove NT path prefix if present\n                if source.startswith(\"\\\\??\\\\\"):\n                    source = source[4:]\n\n                op = {\n                    \"source\": source,\n                    \"value_name\": value_name,\n                }\n\n                if destination:\n                    if destination.startswith(\"\\\\??\\\\\"):\n                        destination = destination[4:]\n                    op[\"destination\"] = destination\n                    op[\"operation\"] = \"rename\"\n                else:\n                    op[\"operation\"] = \"delete\"\n\n                operations.append(op)\n\n            i += 2\n\n        return operations\n"
  },
  {
    "path": "regipy/plugins/system/previous_winver.py",
    "content": "import logging\nimport re\nfrom datetime import datetime, timezone\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nWIN_VER_PATH = r\"\\Setup\"\nos_list = [\n    \"ProductName\",\n    \"ReleaseID\",\n    \"CSDVersion\",\n    \"CurrentVersion\",\n    \"CurrentBuild\",\n    \"CurrentBuildNumber\",\n    \"InstallationType\",\n    \"EditionID\",\n    \"ProductName\",\n    \"ProductId\",\n    \"BuildLab\",\n    \"BuildLabEx\",\n    \"CompositionEditionID\",\n    \"RegisteredOrganization\",\n    \"RegisteredOwner\",\n    \"InstallDate\",\n]\n\n\nclass PreviousWinVersionPlugin(Plugin):\n    NAME = \"previous_winver_plugin\"\n    DESCRIPTION = \"Get previous relevant OS information\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.info(\"Started winver Plugin...\")\n\n        try:\n            key = self.registry_hive.get_key(WIN_VER_PATH)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} subkey at {WIN_VER_PATH}: {ex}\")\n            return None\n\n        for sk in key.iter_subkeys():\n            if sk.name.startswith(\"Source OS\"):\n                old_date = re.search(\n                    r\"Updated on (\\d{1,2})/(\\d{1,2})/(\\d{4}) (\\d{2}):(\\d{2}):(\\d{2})\",\n                    sk.name,\n                )\n                dt = datetime(\n                    int(old_date.group(3)),\n                    int(old_date.group(1)),\n                    int(old_date.group(2)),\n                    int(old_date.group(4)),\n                    int(old_date.group(5)),\n                    int(old_date.group(6)),\n                ).strftime(\"%Y-%m-%d %H:%M:%S\")\n                temp_dict = {\"key\": f\"\\\\{key.name}\\\\{sk.name}\"}\n                temp_dict[\"update_date\"] = dt\n                for val in sk.iter_values():\n                    if val.name in os_list:\n                        if val.name == \"InstallDate\":\n                            temp_dict[val.name] = datetime.fromtimestamp(val.value, timezone.utc).strftime(\"%Y-%m-%d %H:%M:%S\")\n                        else:\n                            temp_dict[val.name] = val.value\n                self.entries.append(temp_dict)\n"
  },
  {
    "path": "regipy/plugins/system/processor_architecture.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nPROCESSOR_PATH = r\"Control\\Session Manager\\Environment\"\nprocessor_list = (\n    \"PROCESSOR_ARCHITECTURE\",\n    \"NUMBER_OF_PROCESSORS\",\n    \"PROCESSOR_IDENTIFIER\",\n    \"PROCESSOR_REVISION\",\n)\n\n\nclass ProcessorArchitecturePlugin(Plugin):\n    NAME = \"processor_architecture\"\n    DESCRIPTION = \"Get processor architecture info from the System's environment key\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def can_run(self):\n        return self.registry_hive.hive_type == SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        processor_subkeys = self.registry_hive.get_control_sets(PROCESSOR_PATH)\n        for processor_subkey in processor_subkeys:\n            try:\n                processor = self.registry_hive.get_key(processor_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {processor_subkey}: {ex}\")\n                continue\n            self.entries[processor_subkey] = {}\n            for val in processor.iter_values():\n                if val.name in processor_list:\n                    self.entries[processor_subkey][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/system/routes.py",
    "content": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import get_subkey_values_from_list\n\nlogger = logging.getLogger(__name__)\n\n\nROUTES_PATH = r\"Services\\Tcpip\\Parameters\\PersistentRoutes\"\n\n\nclass RoutesPlugin(Plugin):\n    NAME = \"routes\"\n    DESCRIPTION = \"Get list of routes\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Routes Plugin...\")\n\n        routes_path_list = self.registry_hive.get_control_sets(ROUTES_PATH)\n        self.entries = get_subkey_values_from_list(self.registry_hive, routes_path_list, as_json=self.as_json)\n"
  },
  {
    "path": "regipy/plugins/system/safeboot_configuration.py",
    "content": "import logging\n\nfrom regipy import RegistryKeyNotFoundException, convert_wintime\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\n\nSAFEBOOT_NETWORK_PATH = r\"Control\\SafeBoot\\Network\"\nSAFEBOOT_MINIMAL_PATH = r\"Control\\SafeBoot\\Minimal\"\n\n\nclass SafeBootConfigurationPlugin(Plugin):\n    NAME = \"safeboot_configuration\"\n    DESCRIPTION = \"Get safeboot configuration\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def _get_safeboot_entries(self, subkey_path):\n        entries = []\n        for safeboot_network_path in self.registry_hive.get_control_sets(subkey_path):\n            control_set_name = safeboot_network_path.split(\"\\\\\")[1]\n            try:\n                subkey = self.registry_hive.get_key(safeboot_network_path)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(ex)\n                continue\n\n            for safeboot_network_subkey in subkey.iter_subkeys():\n                timestamp = convert_wintime(safeboot_network_subkey.header.last_modified, as_json=self.as_json)\n                entries.append(\n                    {\n                        \"timestamp\": timestamp,\n                        \"name\": safeboot_network_subkey.name,\n                        \"description\": safeboot_network_subkey.get_value(),\n                        \"control_set_name\": control_set_name,\n                    }\n                )\n        return entries\n\n    def run(self):\n        logger.debug(\"Started Safeboot Configuration Plugin...\")\n\n        self.entries = {\n            \"network\": self._get_safeboot_entries(SAFEBOOT_NETWORK_PATH),\n            \"minimal\": self._get_safeboot_entries(SAFEBOOT_MINIMAL_PATH),\n        }\n"
  },
  {
    "path": "regipy/plugins/system/services.py",
    "content": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.exceptions import (\n    RegistryKeyNotFoundException,\n    RegistryParsingException,\n)\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSERVICES_PATH = r\"Services\"\n\n\nclass ServicesPlugin(Plugin):\n    NAME = \"services\"\n    DESCRIPTION = \"Enumerate the services in the SYSTEM hive\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        logger.debug(\"Started Services enumeration Plugin...\")\n        for control_set_services_path in self.registry_hive.get_control_sets(SERVICES_PATH):\n            try:\n                subkey = self.registry_hive.get_key(control_set_services_path)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(ex)\n                continue\n            self.entries[control_set_services_path] = {\n                \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json)\n            }\n            services = []\n            for service in subkey.iter_subkeys():\n                values = None\n                parameters = []\n                if service.values_count > 0:\n                    try:\n                        values = [asdict(x) for x in service.iter_values(as_json=True)]\n                    except RegistryParsingException as ex:\n                        logger.error(\n                            f\"Exception while parsing data for service {service.name[:10] if service.name else None}: {ex}\"\n                        )\n\n                    if service.subkey_count:\n                        try:\n                            service_parameters_path = rf\"{control_set_services_path}\\{service.name}\"\n                            for parameter in self.registry_hive.recurse_subkeys(\n                                nk_record=service,\n                                path_root=service_parameters_path,\n                                as_json=True,\n                            ):\n                                parameters.append(asdict(parameter))\n                        except RegistryParsingException as ex:\n                            logger.info(f\"Exception while parsing parameters for service {service.name}: {ex}\")\n\n                entry = {\n                    \"name\": service.name,\n                    \"last_modified\": convert_wintime(service.header.last_modified, as_json=self.as_json),\n                    \"values\": values,\n                    \"parameters\": parameters,\n                }\n                services.append(entry)\n            self.entries[control_set_services_path][\"services\"] = services\n"
  },
  {
    "path": "regipy/plugins/system/shares.py",
    "content": "\"\"\"\nShares plugin - Parses network share configuration\n\"\"\"\n\nimport logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nLANMAN_SHARES_PATH = r\"Services\\LanmanServer\\Shares\"\n\n\nclass SharesPlugin(Plugin):\n    \"\"\"\n    Parses network shares from SYSTEM hive\n\n    Provides information about:\n    - Configured network shares\n    - Share paths and descriptions\n    - Share permissions\n\n    Registry Key: Services\\\\LanmanServer\\\\Shares under each ControlSet\n    \"\"\"\n\n    NAME = \"shares\"\n    DESCRIPTION = \"Parses network share configuration\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Shares Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(LANMAN_SHARES_PATH):\n            try:\n                shares_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find shares at: {controlset_path}\")\n                continue\n\n            self._parse_shares(shares_key, controlset_path)\n\n    def _parse_shares(self, shares_key, key_path: str):\n        \"\"\"Parse share entries\"\"\"\n        for value in shares_key.iter_values():\n            share_name = value.name\n            share_data = value.value\n\n            entry = {\n                \"key_path\": key_path,\n                \"share_name\": share_name,\n                \"last_write\": convert_wintime(shares_key.header.last_modified, as_json=self.as_json),\n            }\n\n            # Share data is a REG_MULTI_SZ with key=value pairs\n            if isinstance(share_data, list):\n                for item in share_data:\n                    if \"=\" in item:\n                        key, val = item.split(\"=\", 1)\n                        key_lower = key.lower()\n\n                        if key_lower == \"path\":\n                            entry[\"path\"] = val\n                        elif key_lower == \"remark\":\n                            entry[\"remark\"] = val\n                        elif key_lower == \"permissions\":\n                            entry[\"permissions\"] = val\n                        elif key_lower == \"maxuses\":\n                            entry[\"max_uses\"] = int(val) if val.isdigit() else val\n                        elif key_lower == \"type\":\n                            entry[\"type\"] = self._get_share_type(int(val) if val.isdigit() else 0)\n                        elif key_lower == \"cscflags\":\n                            entry[\"csc_flags\"] = val\n\n            self.entries.append(entry)\n\n    @staticmethod\n    def _get_share_type(type_value: int) -> str:\n        \"\"\"Convert share type value to description\"\"\"\n        types = {\n            0: \"Disk Drive\",\n            1: \"Print Queue\",\n            2: \"Device\",\n            3: \"IPC\",\n            0x80000000: \"Temporary\",\n            0x40000000: \"Special\",\n        }\n\n        base_type = type_value & 0x0FFFFFFF\n        special = type_value & 0xF0000000\n\n        type_str = types.get(base_type, f\"Unknown ({base_type})\")\n\n        if special & 0x80000000:\n            type_str += \" (Temporary)\"\n        if special & 0x40000000:\n            type_str += \" (Special)\"\n\n        return type_str\n"
  },
  {
    "path": "regipy/plugins/system/shimcache.py",
    "content": "import logging\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.system.external.ShimCacheParser import get_shimcache_entries\n\nlogger = logging.getLogger(__name__)\n\nCOMPUTER_NAME_PATH = r\"Control\\Session Manager\"\n\n\nclass ShimCachePlugin(Plugin):\n    NAME = \"shimcache\"\n    DESCRIPTION = \"Parse Shimcache artifact\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started Shim Cache Plugin...\")\n\n        for subkey_path in self.registry_hive.get_control_sets(COMPUTER_NAME_PATH):\n            appcompat_cache = self.registry_hive.get_key(subkey_path).get_subkey(\"AppCompatCache\")\n\n            if not appcompat_cache:\n                logger.info(\"No shimcache data was found\")\n                return\n\n            shimcache = appcompat_cache.get_value(\"AppCompatCache\")\n            if shimcache:\n                for entry in get_shimcache_entries(shimcache, as_json=self.as_json):\n                    self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/system/shutdown.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_filetime2, convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nSHUTDOWN_DATA_PATH = r\"Control\\Windows\"\n\n\nclass ShutdownPlugin(Plugin):\n    NAME = \"shutdown\"\n    DESCRIPTION = \"Get shutdown data\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        shutdown_subkeys = self.registry_hive.get_control_sets(SHUTDOWN_DATA_PATH)\n        for shutdown_subkey in shutdown_subkeys:\n            try:\n                shutdown = self.registry_hive.get_key(shutdown_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {shutdown_subkey}: {ex}\")\n                continue\n            self.entries[shutdown_subkey] = {\"last_write\": convert_wintime(shutdown.header.last_modified).isoformat()}\n            for val in shutdown.iter_values():\n                if val.name == \"ShutdownTime\":\n                    self.entries[shutdown_subkey][\"date\"] = convert_filetime2(val.value)\n"
  },
  {
    "path": "regipy/plugins/system/timezone_data.py",
    "content": "import logging\nfrom dataclasses import asdict\n\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\n\nlogger = logging.getLogger(__name__)\n\nTZ_DATA_PATH = r\"Control\\TimeZoneInformation\"\n\n\nclass TimezoneDataPlugin(Plugin):\n    NAME = \"timezone_data\"\n    DESCRIPTION = \"Get timezone data\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        tzdata_subkeys = self.registry_hive.get_control_sets(TZ_DATA_PATH)\n        for tzdata_subkey in tzdata_subkeys:\n            tzdata = self.registry_hive.get_key(tzdata_subkey)\n            self.entries[tzdata_subkey] = list(tzdata.iter_values(as_json=self.as_json))\n\n        if self.as_json:\n            for k, v in self.entries.items():\n                self.entries[k] = [asdict(x) for x in v]\n"
  },
  {
    "path": "regipy/plugins/system/timezone_data2.py",
    "content": "import logging\nimport struct\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nTZ_DATA_PATH = r\"Control\\TimeZoneInformation\"\n\n\nclass TimezoneDataPlugin2(Plugin):\n    NAME = \"timezone_data2\"\n    DESCRIPTION = \"Get timezone data\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        self.entries = {}\n        tzdata_subkeys = self.registry_hive.get_control_sets(TZ_DATA_PATH)\n        for tzdata_subkey in tzdata_subkeys:\n            try:\n                tzdata = self.registry_hive.get_key(tzdata_subkey)\n            except RegistryKeyNotFoundException as ex:\n                logger.error(f\"Could not find {self.NAME} subkey at {tzdata_subkey}: {ex}\")\n                continue\n            self.entries[tzdata_subkey] = list(tzdata.iter_values(as_json=self.as_json))\n            self.entries[tzdata_subkey] = {\"last_write\": convert_wintime(tzdata.header.last_modified).isoformat()}\n            for val in tzdata.iter_values():\n                if val.name in (\"ActiveTimeBias\", \"Bias\", \"DaylightBias\"):\n                    self.entries[tzdata_subkey][val.name] = struct.unpack(\">l\", struct.pack(\">L\", val.value & 0xFFFFFFFF))[0]\n                elif isinstance(val.value, bytes):\n                    # Decode bytes (often UTF-16 encoded strings like TimeZoneKeyName)\n                    try:\n                        self.entries[tzdata_subkey][val.name] = val.value.decode(\"utf-16-le\").rstrip(\"\\x00\")\n                    except UnicodeDecodeError:\n                        self.entries[tzdata_subkey][val.name] = val.value.hex()\n                else:\n                    self.entries[tzdata_subkey][val.name] = val.value\n"
  },
  {
    "path": "regipy/plugins/system/usb_devices.py",
    "content": "\"\"\"\nUSB Devices plugin - Parses USB device history (Enum\\\\USB)\n\"\"\"\n\nimport logging\nfrom typing import Optional\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.plugins.utils import extract_values\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nENUM_USB_PATH = r\"Enum\\USB\"\n\n\ndef strip_resource_ref(val) -> Optional[str]:\n    \"\"\"Remove resource reference prefix if present (e.g., '@oem123.inf,...;Device Name')\"\"\"\n    if isinstance(val, str) and \";\" in val:\n        return val.split(\";\")[-1]\n    return val\n\n\nclass USBDevicesPlugin(Plugin):\n    \"\"\"\n    Parses USB device history from SYSTEM hive (Enum\\\\USB)\n\n    This complements USBSTOR by providing information about non-storage USB devices\n    such as keyboards, mice, webcams, and other USB peripherals.\n\n    Registry Key: Enum\\\\USB under each ControlSet\n    \"\"\"\n\n    NAME = \"usb_devices\"\n    DESCRIPTION = \"Parses USB device connection history\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started USB Devices Plugin...\")\n\n        for controlset_path in self.registry_hive.get_control_sets(ENUM_USB_PATH):\n            try:\n                usb_key = self.registry_hive.get_key(controlset_path)\n            except RegistryKeyNotFoundException:\n                logger.debug(f\"Could not find USB devices at: {controlset_path}\")\n                continue\n\n            self._parse_usb_key(usb_key, controlset_path)\n\n    def _parse_usb_key(self, usb_key, base_path: str):\n        \"\"\"Parse USB device entries\"\"\"\n        # Each subkey is a VID_xxxx&PID_xxxx entry\n        for vid_pid_key in usb_key.iter_subkeys():\n            vid_pid = vid_pid_key.name\n            vid_pid_path = f\"{base_path}\\\\{vid_pid}\"\n\n            # Parse VID and PID from the key name\n            vid = None\n            pid = None\n            if \"VID_\" in vid_pid and \"PID_\" in vid_pid:\n                try:\n                    vid_start = vid_pid.index(\"VID_\") + 4\n                    vid_end = vid_pid.index(\"&\", vid_start) if \"&\" in vid_pid[vid_start:] else vid_start + 4\n                    vid = vid_pid[vid_start:vid_end]\n\n                    pid_start = vid_pid.index(\"PID_\") + 4\n                    pid_end_chars = [\"&\", \"\\\\\"]\n                    pid_end = len(vid_pid)\n                    for char in pid_end_chars:\n                        if char in vid_pid[pid_start:]:\n                            pid_end = min(pid_end, vid_pid.index(char, pid_start))\n                    pid = vid_pid[pid_start:pid_end]\n                except (ValueError, IndexError):\n                    pass\n\n            # Each sub-subkey is a serial number or instance ID\n            for instance_key in vid_pid_key.iter_subkeys():\n                instance_id = instance_key.name\n                instance_path = f\"{vid_pid_path}\\\\{instance_id}\"\n\n                entry = {\n                    \"key_path\": instance_path,\n                    \"vid_pid\": vid_pid,\n                    \"vid\": vid,\n                    \"pid\": pid,\n                    \"instance_id\": instance_id,\n                    \"last_write\": convert_wintime(instance_key.header.last_modified, as_json=self.as_json),\n                }\n\n                extract_values(\n                    instance_key,\n                    {\n                        \"DeviceDesc\": (\"device_desc\", strip_resource_ref),\n                        \"FriendlyName\": \"friendly_name\",\n                        \"Mfg\": (\"manufacturer\", strip_resource_ref),\n                        \"Service\": \"service\",\n                        \"Class\": \"class\",\n                        \"ClassGUID\": \"class_guid\",\n                        \"Driver\": \"driver\",\n                        \"ContainerID\": \"container_id\",\n                        \"HardwareID\": \"hardware_id\",\n                    },\n                    entry,\n                )\n\n                self.entries.append(entry)\n"
  },
  {
    "path": "regipy/plugins/system/usbstor.py",
    "content": "import logging\n\nfrom regipy.exceptions import NoRegistrySubkeysException, RegistryKeyNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nUSBSTOR_KEY_PATH = r\"Enum\\USBSTOR\"\nDISK_GUID_PATH = r\"Device Parameters\\Partmgr\"\nPROPERTIES_NAME_GUID = r\"{540b947e-8b40-45bc-a8a2-6a0b894cbda2}\"\nPROPERTIES_DATES_GUID = r\"{83da6326-97a6-4088-9453-a1923f573b29}\"\nDEVICE_NAME_KEY = \"0004\"\nFIRST_INSTALLED_TIME_KEY = \"0065\"\nLAST_CONNECTED_TIME_KEY = \"0066\"\nLAST_REMOVED_TIME_KEY = \"0067\"\nLAST_INSTALLED_TIME_KEY = \"0064\"\n\n\nclass USBSTORPlugin(Plugin):\n    NAME = \"usbstor_plugin\"\n    DESCRIPTION = \"Parse the connected USB devices history\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        try:\n            for subkey_path in self.registry_hive.get_control_sets(USBSTOR_KEY_PATH):\n                usbstor_key = self.registry_hive.get_key(subkey_path)\n                for usbstor_drive in usbstor_key.iter_subkeys():\n                    try:\n                        disk, manufacturer, title, version = usbstor_drive.name.split(\"&\")\n                    except ValueError:\n                        manufacturer, title, version = None, None, None\n\n                    for serial_subkey in usbstor_drive.iter_subkeys():\n                        timestamp = convert_wintime(serial_subkey.header.last_modified, as_json=self.as_json)\n                        serial_number = serial_subkey.name\n                        key_path = rf\"{subkey_path}\\{usbstor_drive.name}\\{serial_number}\"\n\n                        try:\n                            device_guid_key = self.registry_hive.get_key(\n                                rf\"{subkey_path}\\{usbstor_drive.name}\\{serial_number}\\{DISK_GUID_PATH}\"\n                            )\n                            disk_guid = device_guid_key.get_value(\"DiskId\")\n                        except RegistryKeyNotFoundException:\n                            disk_guid = None\n\n                        properties_subkey = serial_subkey.get_subkey(\"Properties\")\n                        if not properties_subkey:\n                            (\n                                device_name,\n                                first_installed_time,\n                                last_connected_time,\n                                last_removed_time,\n                                last_installed_time,\n                            ) = (None, None, None, None, None)\n                        else:\n                            try:\n                                device_name_guid_key = properties_subkey.get_subkey(PROPERTIES_NAME_GUID)\n                                device_name_key = device_name_guid_key.get_subkey(DEVICE_NAME_KEY)\n                                device_name = device_name_key.get_value()\n                            except (\n                                RegistryKeyNotFoundException,\n                                NoRegistrySubkeysException,\n                            ):\n                                device_name = None\n\n                            (\n                                first_installed_time,\n                                last_connected_time,\n                                last_removed_time,\n                                last_installed_time,\n                            ) = (None, None, None, None)\n\n                            dates_subkey = properties_subkey.get_subkey(PROPERTIES_DATES_GUID, raise_on_missing=False)\n                            if dates_subkey:\n                                first_installed_key = dates_subkey.get_subkey(FIRST_INSTALLED_TIME_KEY, raise_on_missing=False)\n                                if first_installed_key:\n                                    first_installed_time = first_installed_key.get_value(as_json=self.as_json)\n\n                                last_connected_key = dates_subkey.get_subkey(LAST_CONNECTED_TIME_KEY, raise_on_missing=False)\n                                if last_connected_key:\n                                    last_connected_time = last_connected_key.get_value(as_json=self.as_json)\n\n                                last_removed_key = dates_subkey.get_subkey(LAST_REMOVED_TIME_KEY, raise_on_missing=False)\n                                if last_removed_key:\n                                    last_removed_time = last_removed_key.get_value(as_json=self.as_json)\n\n                                last_installed_key = dates_subkey.get_subkey(LAST_INSTALLED_TIME_KEY, raise_on_missing=False)\n                                if last_installed_key:\n                                    last_installed_time = last_installed_key.get_value(as_json=self.as_json)\n\n                        self.entries.append(\n                            {\n                                \"last_write\": timestamp,\n                                \"key_path\": key_path,\n                                \"last_connected\": last_connected_time,\n                                \"last_removed\": last_removed_time,\n                                \"first_installed\": first_installed_time,\n                                \"last_installed\": last_installed_time,\n                                \"serial_number\": serial_number,\n                                \"device_name\": device_name,\n                                \"disk_guid\": disk_guid,\n                                \"manufacturer\": manufacturer,\n                                \"version\": version,\n                                \"title\": title,\n                            }\n                        )\n\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {USBSTOR_KEY_PATH}: {ex}\")\n"
  },
  {
    "path": "regipy/plugins/system/wdigest.py",
    "content": "import logging\n\nfrom regipy.exceptions import RegistryValueNotFoundException\nfrom regipy.hive_types import SYSTEM_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\n\nlogger = logging.getLogger(__name__)\n\nWDIGEST_PATH = r\"Control\\SecurityProviders\\WDigest\"\n\n\nclass WDIGESTPlugin(Plugin):\n    NAME = \"wdigest\"\n    DESCRIPTION = \"Get WDIGEST configuration\"\n    COMPATIBLE_HIVE = SYSTEM_HIVE_TYPE\n\n    def run(self):\n        logger.debug(\"Started WDIGEST Plugin...\")\n        for subkey_path in self.registry_hive.get_control_sets(WDIGEST_PATH):\n            subkey = self.registry_hive.get_key(subkey_path)\n\n            try:\n                self.entries.append(\n                    {\n                        \"subkey\": subkey_path,\n                        \"use_logon_credential\": subkey.get_value(\"UseLogonCredential\", as_json=self.as_json),\n                        \"timestamp\": convert_wintime(subkey.header.last_modified, as_json=self.as_json),\n                    }\n                )\n            except RegistryValueNotFoundException as ex:\n                logger.exception(f\"Could not find Wdigest for registry hive: {ex}\")\n                continue\n"
  },
  {
    "path": "regipy/plugins/usrclass/__init__.py",
    "content": ""
  },
  {
    "path": "regipy/plugins/usrclass/shellbags_usrclass.py",
    "content": "import logging\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.hive_types import USRCLASS_HIVE_TYPE\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.utils import convert_wintime\nfrom regipy.constants import KNOWN_GUIDS\nimport re\n\nlogger = logging.getLogger(__name__)\n\nUSRCLASS_SHELLBAG = \"\\\\Local Settings\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\BagMRU\"\nDEFAULT_CODEPAGE = \"cp1252\"\n\n\nclass ShellBagUsrclassPlugin(Plugin):\n    NAME = \"usrclass_shellbag_plugin\"\n    DESCRIPTION = \"Parse USRCLASS Shellbag items\"\n    COMPATIBLE_HIVE = USRCLASS_HIVE_TYPE\n\n    @staticmethod\n    def _parse_mru(mru_val):\n        mru_order_string = \"\"\n        if isinstance(mru_val, bytes):\n            mru_val = mru_val[:-4]\n            for i in range(0, len(mru_val), 4):\n                current_val = int.from_bytes(mru_val[i : i + 4], byteorder=\"little\")\n                mru_order_string += f\"{current_val}-\"\n\n            return mru_order_string[:-1]\n        else:\n            return mru_order_string\n\n    @staticmethod\n    def _get_shell_item_type(shell_item):\n        try:\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        if isinstance(shell_item, pyfwsi.volume):\n            item_type = \"Volume\"\n\n        elif isinstance(shell_item, pyfwsi.file_entry):\n            item_type = \"Directory\"\n\n        elif isinstance(shell_item, pyfwsi.network_location):\n            item_type = \"Network Location\"\n\n        elif isinstance(shell_item, pyfwsi.root_folder):\n            item_type = \"Root Folder\"\n\n        elif isinstance(shell_item, pyfwsi.control_panel_category):\n            item_type = \"Control Panel Category\"\n\n        elif isinstance(shell_item, pyfwsi.control_panel_item):\n            item_type = \"Control Panel Item\"\n\n        elif isinstance(shell_item, pyfwsi.users_property_view):\n            item_type = \"Users Property View\"\n\n        else:\n            item_type = \"unknown\"\n\n        return item_type\n\n    @staticmethod\n    def _check_known_guids(guid):\n        if guid in KNOWN_GUIDS:\n            path_segment = KNOWN_GUIDS[guid]\n        else:\n            path_segment = \"{{{0:s}}}\".format(guid)\n        return path_segment\n\n    @staticmethod\n    def _get_entry_string(fwps_record):\n        if fwps_record.entry_name:\n            entry_string = fwps_record.entry_name\n        else:\n            entry_string = f\"{fwps_record.entry_type:d}\"\n        return entry_string\n\n    @staticmethod\n    def _parse_shell_item_path_segment(self, shell_item):\n        \"\"\"Parses a shell item path segment.\n        Args:\n          shell_item (pyfwsi.item): shell item.\n        Returns:\n          str: shell item path segment.\n        \"\"\"\n\n        try:\n            import pyfwsi\n            import pyfwps\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        path_segment = None\n        full_path = None\n        location_description = None\n\n        if isinstance(shell_item, pyfwsi.volume):\n            if shell_item.name:\n                path_segment = shell_item.name\n            elif shell_item.identifier:\n                path_segment = self._check_known_guids(shell_item.identifier)\n\n        elif isinstance(shell_item, pyfwsi.file_entry):\n            long_name = \"\"\n            for extension_block in shell_item.extension_blocks:\n                if isinstance(extension_block, pyfwsi.file_entry_extension):\n                    long_name = extension_block.long_name\n\n            if long_name:\n                path_segment = long_name\n            elif shell_item.name:\n                path_segment = shell_item.name\n\n        elif isinstance(shell_item, pyfwsi.network_location):\n            if shell_item.location:\n                path_segment = shell_item.location\n            if shell_item.description:\n                location_description = shell_item.description\n                if shell_item.comments:\n                    location_description += f\", {shell_item.comments}\"\n\n        elif isinstance(shell_item, pyfwsi.root_folder):\n            if shell_item.shell_folder_identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.shell_folder_identifier]\n            elif hasattr(shell_item, \"identifier\") and shell_item.identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.identifier]\n            else:\n                path_segment = \"{{{0:s}}}\".format(shell_item.shell_folder_identifier)\n\n        elif isinstance(shell_item, pyfwsi.users_property_view):\n            # Users property view\n            if shell_item.delegate_folder_identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.delegate_folder_identifier]\n            elif hasattr(shell_item, \"identifier\") and shell_item.identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.identifier]\n\n            # Variable\n            elif hasattr(shell_item, \"known_folder_identifier\") and shell_item.known_folder_identifier in KNOWN_GUIDS:\n                path_segment = KNOWN_GUIDS[shell_item.known_folder_identifier]\n\n            # Variable: Users property view\n            elif shell_item.property_store_data:\n                fwps_store = pyfwps.store()\n                fwps_store.copy_from_byte_stream(shell_item.property_store_data)\n\n                for fwps_set in iter(fwps_store.sets):\n                    if fwps_set.identifier == \"b725f130-47ef-101a-a5f1-02608c9eebac\":\n                        for fwps_record in iter(fwps_set.records):\n                            entry_string = self._get_entry_string(fwps_record)\n\n                            # PKEY_DisplayName: {b725f130-47ef-101a-a5f1-02608c9eebac}/10\n                            if entry_string == \"10\":\n                                if fwps_record.value_type == 0x0001:\n                                    value_string = \"<VT_NULL>\"\n                                elif fwps_record.value_type in (\n                                    0x0003,\n                                    0x0013,\n                                    0x0014,\n                                    0x0015,\n                                ):\n                                    value_string = str(fwps_record.get_data_as_integer())\n                                elif fwps_record.value_type in (0x0008, 0x001E, 0x001F):\n                                    value_string = fwps_record.get_data_as_string()\n                                elif fwps_record.value_type == 0x000B:\n                                    value_string = str(fwps_record.get_data_as_boolean())\n                                elif fwps_record.value_type == 0x0040:\n                                    filetime = fwps_record.get_data_as_integer()\n                                    value_string = self._FormatFiletimeValue(filetime)\n                                elif fwps_record.value_type == 0x0042:\n                                    # TODO: add support\n                                    value_string = \"<VT_STREAM>\"\n                                elif fwps_record.value_type == 0x0048:\n                                    value_string = fwps_record.get_data_as_guid()\n                                elif fwps_record.value_type & 0xF000 == 0x1000:\n                                    # TODO: add support\n                                    value_string = \"<VT_VECTOR>\"\n                                else:\n                                    value_string = None\n\n                                path_segment = value_string\n\n                    elif fwps_set.identifier == \"28636aa6-953d-11d2-b5d6-00c04fd918d0\":\n                        for fwps_record in iter(fwps_set.records):\n                            entry_string = self._get_entry_string(fwps_record)\n\n                            # PKEY_ParsingPath: {28636aa6-953d-11d2-b5d6-00c04fd918d0}/30\n                            if entry_string == \"30\":\n                                full_path = fwps_record.get_data_as_string()\n\n        elif isinstance(shell_item, pyfwsi.control_panel_category):\n            path_segment = self._check_known_guids(str(shell_item.identifier))\n\n        elif isinstance(shell_item, pyfwsi.control_panel_item):\n            path_segment = self._check_known_guids(shell_item.identifier)\n\n        if path_segment is None:\n            path_segment = \"<UNKNOWN: 0x{0:02x}>\".format(shell_item.class_type)\n\n        return path_segment, full_path, location_description\n\n    def iter_sk(self, key, reg_path, codepage=DEFAULT_CODEPAGE, base_path=\"\", path=\"\"):\n        try:\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        last_write = convert_wintime(key.header.last_modified, as_json=True)\n\n        mru_val = key.get_value(\"MRUListEx\")\n        mru_order = self._parse_mru(mru_val)\n        base_path = path\n\n        if key.get_value(\"NodeSlot\"):\n            node_slot = str(key.get_value(\"NodeSlot\"))\n        else:\n            node_slot = \"\"\n\n        for v in key.iter_values(trim_values=False):\n            if re.match(r\"\\d+\", v.name):\n                slot = v.name\n                byte_stream = v.value\n                shell_items = pyfwsi.item_list()\n                shell_items.copy_from_byte_stream(byte_stream, ascii_codepage=codepage)\n                for item in shell_items.items:\n                    shell_type = self._get_shell_item_type(item)\n                    value, full_path, location_description = self._parse_shell_item_path_segment(self, item)\n                    if not path:\n                        path = value\n                        base_path = \"\"\n                    else:\n                        path += f\"\\\\{value}\"\n\n                    creation_time = None\n                    access_time = None\n                    modification_time = None\n\n                    if len(item.extension_blocks) > 0:\n                        for extension_block in item.extension_blocks:\n                            if isinstance(extension_block, pyfwsi.file_entry_extension):\n                                try:\n                                    creation_time = extension_block.get_creation_time()\n                                    if self.as_json:\n                                        creation_time = creation_time.isoformat()\n                                except OSError:\n                                    logger.exception(f\"Malformed creation time for {path}\")\n                                try:\n                                    access_time = extension_block.get_access_time()\n                                    if self.as_json:\n                                        access_time = access_time.isoformat()\n                                except OSError:\n                                    logger.exception(f\"Malformed access time for {path}\")\n\n                    try:\n                        if hasattr(item, \"modification_time\"):\n                            modification_time = item.get_modification_time()\n                            if self.as_json:\n                                modification_time = modification_time.isoformat()\n                    except OSError:\n                        logger.exception(f\"Malformed modification time for {path}\")\n\n                    value_name = v.name\n                    mru_order_location = mru_order.split(\"-\").index(value_name)\n                    entry = {\n                        \"value\": value,\n                        \"slot\": slot,\n                        \"reg_path\": reg_path,\n                        \"value_name\": value_name,\n                        \"node_slot\": node_slot,\n                        \"shell_type\": shell_type,\n                        \"path\": path,\n                        \"full path\": full_path if full_path else None,\n                        \"location description\": (location_description if location_description else None),\n                        \"creation_time\": creation_time,\n                        \"access_time\": access_time,\n                        \"modification_time\": modification_time,\n                        \"last_write\": last_write,\n                        \"mru_order\": mru_order,\n                        \"mru_order_location\": mru_order_location,\n                    }\n\n                    self.entries.append(entry)\n                    sk_reg_path = f\"{reg_path}\\\\{value_name}\"\n                    sk = self.registry_hive.get_key(sk_reg_path)\n                    self.iter_sk(sk, sk_reg_path, codepage, base_path, path)\n                    path = base_path\n\n    def run(self, codepage=DEFAULT_CODEPAGE):\n        try:\n            # TODO: handle this import requirement in a better way\n            # flake8: noqa\n            import pyfwsi\n        except ModuleNotFoundError as ex:\n            logger.exception(\n                \"Plugin `shellbag_plugin` has missing modules, install regipy using\"\n                \" `pip install regipy[full]` in order to install plugin dependencies. \"\n                \"This might take some time... \"\n            )\n            raise ex\n\n        try:\n            shellbag_usrclass_subkey = self.registry_hive.get_key(USRCLASS_SHELLBAG)\n            self.iter_sk(shellbag_usrclass_subkey, USRCLASS_SHELLBAG, codepage=codepage)\n        except RegistryKeyNotFoundException as ex:\n            logger.error(f\"Could not find {self.NAME} plugin data at: {USRCLASS_SHELLBAG}: {ex}\")\n"
  },
  {
    "path": "regipy/plugins/utils.py",
    "content": "import json\nimport logging\nfrom dataclasses import asdict\nfrom typing import Any, Callable, Union\n\nfrom regipy import NKRecord\nfrom regipy.plugins.plugin import PLUGINS\nfrom regipy.plugins.validation_status import (\n    is_plugin_validated,\n    warn_unvalidated_plugin,\n)\n\nlogger = logging.getLogger(__name__)\n\n\n# Type alias for value mapping specification\n# Can be:\n#   - str: simple rename (registry \"Foo\" -> entry \"foo\")\n#   - tuple[str, Callable]: rename + transform function\nValueSpec = Union[str, tuple[str, Callable[[Any], Any]]]\n\n\ndef extract_values(\n    registry_key,\n    value_map: dict[str, ValueSpec],\n    entry: dict[str, Any],\n) -> None:\n    \"\"\"\n    Extract registry values into an entry dict using a declarative mapping.\n\n    Args:\n        registry_key: Registry key to iterate values from\n        value_map: Mapping of registry value names to output specifications\n        entry: Dict to populate with extracted values\n\n    Example value_map:\n        {\n            \"ProfileName\": \"profile_name\",  # Simple rename\n            \"Enabled\": (\"enabled\", lambda v: v == 1),  # Convert with function\n            \"DateCreated\": (\"date_created\", parse_date_func),  # Custom transform\n        }\n    \"\"\"\n    for value in registry_key.iter_values():\n        name = value.name\n        if name not in value_map:\n            continue\n\n        spec = value_map[name]\n        if isinstance(spec, str):\n            entry[spec] = value.value\n        else:\n            output_name, converter = spec\n            entry[output_name] = converter(value.value)\n\n\ndef dump_hive_to_json(\n    registry_hive,\n    output_path,\n    name_key_entry: NKRecord,\n    verbose=False,\n    fetch_values=True,\n):\n    \"\"\"\n    Write the hive subkeys to a JSON-lines file, one line per entry.\n    :param registry_hive: a RegistryHive object\n    :param output_path: Output path to save the JSON\n    :param name_key_entry: The NKRecord to start iterating from\n    :param verbose: verbosity\n    :return: The result, as dict\n    \"\"\"\n    with open(output_path, mode=\"w\") as writer:\n        for subkey_count, entry in enumerate(\n            registry_hive.recurse_subkeys(name_key_entry, as_json=True, fetch_values=fetch_values)\n        ):\n            writer.write(\n                json.dumps(\n                    asdict(entry),\n                    separators=(\n                        \",\",\n                        \":\",\n                    ),\n                )\n            )\n            writer.write(\"\\n\")\n        return subkey_count\n\n\ndef run_relevant_plugins(\n    registry_hive,\n    as_json=False,\n    plugins=None,\n    include_unvalidated=False,\n):\n    \"\"\"\n    Execute the relevant plugins on the hive\n\n    :param registry_hive: a RegistryHive object\n    :param as_json: Whether to return result as json\n    :param plugins: List of plugin to execute (names according to the NAME field in each plugin)\n    :param include_unvalidated: Whether to include plugins that don't have validation test cases.\n                                If False (default), only validated plugins will be executed.\n                                Unvalidated plugins may return incomplete or inaccurate data.\n    :return: The result, as dict\n    \"\"\"\n    plugin_results = {}\n    for plugin_class in PLUGINS:\n        plugin = plugin_class(registry_hive, as_json=as_json)\n\n        # If the list of plugins is defined, but the plugin is not in the list skip it.\n        if plugins and plugin.NAME not in plugins:\n            continue\n\n        # Check validation status\n        if not is_plugin_validated(plugin.NAME):\n            if not include_unvalidated:\n                logger.debug(f\"Skipping unvalidated plugin: {plugin.NAME}\")\n                continue\n            # Always warn when running unvalidated plugins\n            warn_unvalidated_plugin(plugin.NAME)\n\n        if plugin.can_run():\n            try:\n                plugin.run()\n                plugin_results[plugin.NAME] = plugin.entries\n            except ModuleNotFoundError:\n                logger.error(f\"Plugin {plugin.NAME} has missing dependencies\")\n    return plugin_results\n"
  },
  {
    "path": "regipy/plugins/validated_plugins.json",
    "content": "[\n    \"active_control_set\",\n    \"amcache\",\n    \"app_paths\",\n    \"background_activity_moderator\",\n    \"backuprestore_plugin\",\n    \"boot_entry_list\",\n    \"bootkey\",\n    \"codepage\",\n    \"computer_name\",\n    \"crash_dump\",\n    \"diag_sr\",\n    \"disable_last_access\",\n    \"disablesr_plugin\",\n    \"domain_sid\",\n    \"execution_policy\",\n    \"host_domain_name\",\n    \"image_file_execution_options\",\n    \"installed_programs_ntuser\",\n    \"installed_programs_software\",\n    \"last_logon_plugin\",\n    \"local_sid\",\n    \"lsa_packages\",\n    \"mounted_devices\",\n    \"network_data\",\n    \"network_drives_plugin\",\n    \"networklist\",\n    \"ntuser_classes_installer\",\n    \"ntuser_persistence\",\n    \"ntuser_shellbag_plugin\",\n    \"pagefile\",\n    \"previous_winver_plugin\",\n    \"print_demon_plugin\",\n    \"processor_architecture\",\n    \"profilelist_plugin\",\n    \"ras_tracing\",\n    \"routes\",\n    \"safeboot_configuration\",\n    \"samparse\",\n    \"services\",\n    \"shimcache\",\n    \"shutdown\",\n    \"software_classes_installer\",\n    \"software_plugin\",\n    \"spp_clients_plugin\",\n    \"susclient_plugin\",\n    \"terminal_services_history\",\n    \"timezone_data\",\n    \"timezone_data2\",\n    \"typed_paths\",\n    \"typed_urls\",\n    \"uac_plugin\",\n    \"usb_devices\",\n    \"usbstor_plugin\",\n    \"user_assist\",\n    \"usrclass_shellbag_plugin\",\n    \"wdigest\",\n    \"windows_defender\",\n    \"winrar_plugin\",\n    \"winscp_saved_sessions\",\n    \"winver_plugin\",\n    \"word_wheel_query\",\n    \"wsl\"\n]"
  },
  {
    "path": "regipy/plugins/validation_status.py",
    "content": "\"\"\"\nPlugin validation status tracking.\n\nThis module tracks which plugins have been validated with test cases.\nPlugins without validation may return invalid or incomplete data.\n\nValidation status is stored in validated_plugins.json, which is generated\nby the validation test framework and shipped with the package.\n\"\"\"\n\nimport json\nimport logging\nfrom pathlib import Path\n\nlogger = logging.getLogger(__name__)\n\n# Load validated plugins from JSON file shipped with the package\n_VALIDATED_PLUGINS_FILE = Path(__file__).parent / \"validated_plugins.json\"\n\ntry:\n    with open(_VALIDATED_PLUGINS_FILE) as f:\n        VALIDATED_PLUGINS: set[str] = set(json.load(f))\nexcept FileNotFoundError:\n    logger.warning(f\"Validated plugins file not found: {_VALIDATED_PLUGINS_FILE}\")\n    VALIDATED_PLUGINS = set()\n\n\ndef is_plugin_validated(plugin_name: str) -> bool:\n    \"\"\"Check if a plugin has validation test cases.\"\"\"\n    return plugin_name in VALIDATED_PLUGINS\n\n\ndef get_validated_plugins() -> set[str]:\n    \"\"\"Get set of all validated plugin names.\"\"\"\n    return VALIDATED_PLUGINS.copy()\n\n\ndef get_unvalidated_plugins(plugin_names: list[str]) -> list[str]:\n    \"\"\"Get list of plugin names that don't have validation.\"\"\"\n    return [name for name in plugin_names if name not in VALIDATED_PLUGINS]\n\n\ndef warn_unvalidated_plugin(plugin_name: str) -> None:\n    \"\"\"Log a warning for an unvalidated plugin.\"\"\"\n    logger.warning(\n        f\"Plugin '{plugin_name}' does not have a validation test case. \"\n        \"Results may be incomplete or inaccurate. Use at your own discretion.\"\n    )\n"
  },
  {
    "path": "regipy/py.typed",
    "content": ""
  },
  {
    "path": "regipy/recovery.py",
    "content": "import logging\nimport os\nfrom io import BytesIO\n\nfrom construct import Int32ul\n\nfrom regipy import boomerang_stream\nfrom regipy.exceptions import RegistryRecoveryException\nfrom regipy.hive_types import DIRT_TRANSACTION_LOG_MAGIC, HVLE_TRANSACTION_LOG_MAGIC\nfrom regipy.registry import RegistryHive\nfrom regipy.structs import REGF_HEADER_SIZE, TRANSACTION_LOG\n\nlogger = logging.getLogger(__name__)\n\n\ndef _parse_hvle_block(hive_path, transaction_log_stream, log_size, expected_sequence_number):\n    \"\"\"\n\n    :param hive_path:\n    :param transaction_log_stream:\n    :param log_size:\n    :param expected_sequence_number:\n    :return:\n    \"\"\"\n    recovered_dirty_pages_count = 0\n    restored_hive_buffer = BytesIO(open(hive_path, \"rb\").read())\n\n    hvle_block_start_offset = transaction_log_stream.tell()\n\n    while hvle_block_start_offset < log_size:\n        logger.info(f\"Parsing hvle block at {hex(hvle_block_start_offset)}\")\n        with boomerang_stream(transaction_log_stream) as x:\n            if x.read(4) != b\"HvLE\":\n                logger.info(\"Reached a non HvLE object. stopping\")\n                break\n\n        logger.info(f\"Parsing HvLE block at {hex(hvle_block_start_offset)}\")\n        parsed_hvle_block = TRANSACTION_LOG.parse_stream(transaction_log_stream)\n        logger.info(f\"Currently at start of dirty pages: {transaction_log_stream.tell()}\")\n        logger.info(f\"seq number: {parsed_hvle_block.sequence_number}\")\n        logger.info(f\"dirty pages: {parsed_hvle_block.dirty_pages_count}\")\n\n        if parsed_hvle_block.sequence_number == expected_sequence_number:\n            logger.info(\"This hvle block holds valid dirty blocks\")\n            expected_sequence_number += 1\n\n        for dirty_page_entry in parsed_hvle_block.dirty_pages_references:\n            # Write the actual dirty page to the original hive\n            target_offset = REGF_HEADER_SIZE + dirty_page_entry.offset\n            restored_hive_buffer.seek(target_offset)\n            transaction_log_stream_offset = transaction_log_stream.tell()\n            dirty_page_buffer = transaction_log_stream.read(dirty_page_entry.size)\n            restored_hive_buffer.write(dirty_page_buffer)\n            logger.info(\n                f\"Restored {dirty_page_entry.size} bytes to offset {hex(target_offset)} \"\n                f\"from offset {hex(transaction_log_stream_offset)}\"\n            )\n            recovered_dirty_pages_count += 1\n\n        # TODO: update hive flags from hvle to original header\n\n        # Update sequence numbers are at offsets 4 & 8:\n        restored_hive_buffer.seek(4)\n        restored_hive_buffer.write(Int32ul.build(expected_sequence_number))\n        restored_hive_buffer.write(Int32ul.build(expected_sequence_number))\n\n        # Update hbins size from hvle to original header at offset 40\n        restored_hive_buffer.seek(40)\n        restored_hive_buffer.write(Int32ul.build(parsed_hvle_block.hive_bin_size))\n\n        transaction_log_stream.seek(hvle_block_start_offset + parsed_hvle_block.log_size)\n        hvle_block_start_offset = hvle_block_start_offset + parsed_hvle_block.log_size\n\n    return restored_hive_buffer, recovered_dirty_pages_count\n\n\ndef _parse_dirt_block(hive_path, transaction_log, hbins_data_size):\n    restored_hive_buffer = BytesIO(open(hive_path, \"rb\").read())\n    recovered_dirty_pages_count = 0\n\n    dirty_vector_length = hbins_data_size // 4096\n\n    if transaction_log.read(4) != b\"DIRT\":\n        raise RegistryRecoveryException(\"Expected DIRT signature!\")\n\n    log_file_base = 1024  # 512 + len(b'DIRT') + dirty_vector_length\n    primary_file_base = 4096\n    bitmap = transaction_log.read(dirty_vector_length)\n\n    bit_counter = 0\n    bitmap_offset = 0\n\n    # Tuples of offset in primary and offset in transaction log\n    offsets = []\n    while bit_counter < dirty_vector_length * 8:\n        is_bit_set = ((bitmap[bit_counter // 8] >> (bit_counter % 8)) & 1) != 0\n        if is_bit_set:\n            # We skip the basic block for the offsets\n            registry_offset = primary_file_base + (bit_counter * 512)\n\n            # And also the DIRT signature in the transaction log\n            transaction_log_offset = log_file_base + (bitmap_offset * 512)\n\n            offsets.append((registry_offset, transaction_log_offset))\n            bitmap_offset += 1\n\n        bit_counter += 1\n\n    for registry_offset, transaction_log_offset in offsets:\n        logger.debug(f\"Reading 512 bytes from {transaction_log_offset} writing to {registry_offset}\")\n\n        restored_hive_buffer.seek(registry_offset)\n        transaction_log.seek(transaction_log_offset)\n\n        restored_hive_buffer.write(transaction_log.read(512))\n\n        recovered_dirty_pages_count += 1\n    return restored_hive_buffer, recovered_dirty_pages_count\n\n\ndef _parse_transaction_log(registry_hive, hive_path, transaction_log_path):\n    log_size = os.path.getsize(transaction_log_path)\n    logger.info(f\"Log Size: {log_size}\")\n\n    expected_sequence_number = registry_hive.header.secondary_sequence_num\n\n    with open(transaction_log_path, \"rb\") as transaction_log:\n        # Skip the REGF header\n        transaction_log.seek(512, 0)\n\n        # Read the header of the transaction log vector and determine its type\n        with boomerang_stream(transaction_log) as s:\n            magic = s.read(4)\n\n        if magic == HVLE_TRANSACTION_LOG_MAGIC:\n            # This is an HvLE block\n            restored_hive_buffer, recovered_dirty_pages_count = _parse_hvle_block(\n                hive_path, transaction_log, log_size, expected_sequence_number\n            )\n        elif magic == DIRT_TRANSACTION_LOG_MAGIC:\n            # This is an old transaction log - DIRT\n            hbins_data_size = registry_hive.header.hive_bins_data_size\n            restored_hive_buffer, recovered_dirty_pages_count = _parse_dirt_block(hive_path, transaction_log, hbins_data_size)\n        else:\n            raise RegistryRecoveryException(f\"The transaction log vector magic was not expected: {magic}\")\n    return restored_hive_buffer, recovered_dirty_pages_count\n\n\ndef apply_transaction_logs(\n    hive_path,\n    primary_log_path,\n    secondary_log_path=None,\n    restored_hive_path=None,\n    verbose=False,\n):\n    \"\"\"\n    Apply transactions logs\n    :param hive_path: The path to the original hive\n    :param primary_log_path: The path to the primary log path\n    :param secondary_log_path: The path to the secondary log path (optional).\n    :param restored_hive_path: Path to save the restored hive\n    :param verbose: verbosity\n    :return:\n    \"\"\"\n    recovered_dirty_pages_total_count = 0\n\n    if not restored_hive_path:\n        restored_hive_path = f\"{hive_path}.restored\"\n\n    registry_hive = RegistryHive(hive_path)\n\n    if secondary_log_path:\n        registry_hive = RegistryHive(hive_path)\n        restored_hive_buffer, recovered_dirty_pages_count = _parse_transaction_log(registry_hive, hive_path, secondary_log_path)\n        # Write to disk the modified registry hive\n        with open(restored_hive_path, \"wb\") as f:\n            restored_hive_buffer.seek(0)\n            f.write(restored_hive_buffer.read())\n\n        recovered_dirty_pages_total_count += recovered_dirty_pages_count\n        logger.info(f\"Recovered {recovered_dirty_pages_count} pages from secondary transaction log\")\n\n    # Parse the primary transaction log\n    log_size = os.path.getsize(primary_log_path)\n    logger.info(f\"Log Size: {log_size}\")\n\n    # If no secondary transaction log was give, apply the first transaction log on the original hive\n    target_hive = restored_hive_path if secondary_log_path else hive_path\n    restored_hive_buffer, recovered_dirty_pages_count = _parse_transaction_log(registry_hive, target_hive, primary_log_path)\n    logger.info(f\"Recovered {recovered_dirty_pages_count} pages from primary transaction log\")\n\n    recovered_dirty_pages_total_count += recovered_dirty_pages_count\n\n    # Write to disk the modified registry hive\n    with open(restored_hive_path, \"wb\") as f:\n        restored_hive_buffer.seek(0)\n        f.write(restored_hive_buffer.read())\n\n    return restored_hive_path, recovered_dirty_pages_total_count\n"
  },
  {
    "path": "regipy/regdiff.py",
    "content": "import logging\nimport os\nfrom typing import Any\n\nfrom regipy.exceptions import RegistryKeyNotFoundException\nfrom regipy.registry import NKRecord, RegistryHive\nfrom regipy.utils import calculate_sha1, convert_wintime\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_subkeys_and_timestamps(registry_hive):\n    subkeys_and_timestamps = set()\n    for subkey in registry_hive.recurse_subkeys():\n        subkey_path = subkey.path\n        ts = subkey.timestamp\n        subkeys_and_timestamps.add((subkey_path, ts))\n    return subkeys_and_timestamps\n\n\ndef get_values_from_tuples(value_tuples, value_name_list):\n    for value_name, value_data in value_tuples:\n        if value_name in value_name_list:\n            yield value_name, value_data\n\n\ndef get_timestamp_for_subkeys(registry_hive, subkey_list):\n    for subkey_path in subkey_list:\n        try:\n            subkey = registry_hive.get_key(subkey_path)\n        except RegistryKeyNotFoundException:\n            logger.exception(f\"Could not obtain timestamp for subkey {subkey_path}\")\n            continue\n        yield subkey_path, convert_wintime(subkey.header.last_modified, as_json=True)\n\n\ndef _get_name_value_tuples(subkey: NKRecord) -> set[tuple[str, Any]]:\n    \"\"\"\n    Iterate over value in a subkey and return a set of tuples containing value names and values\n    :param subkey: NKRecord to iterate over\n    :return: A set of tuples containing value names and values\n    \"\"\"\n    values_tuple: set[tuple[str, Any]] = set()\n    for value in subkey.iter_values(as_json=True):\n        if not value.value:\n            continue\n\n        if isinstance(value.value, list):\n            values_tuple.update({(value.name, x) for x in value.value})\n        else:\n            values_tuple.add((value.name, value.value))\n    return values_tuple\n\n\ndef compare_hives(first_hive_path, second_hive_path, verbose=False):\n    # The list will contain tuples, in the following format: (Difference type, first value, second value, description)\n    found_differences = []\n\n    # Compare hash, verify they are indeed different\n    first_hive_sha1 = calculate_sha1(first_hive_path)\n    second_hive_sha1 = calculate_sha1(second_hive_path)\n\n    if first_hive_sha1 == second_hive_sha1:\n        logger.info(\"Hives have the same hash!\")\n        return found_differences\n\n    # Compare header parameters\n    first_registry_hive = RegistryHive(first_hive_path)\n    second_registry_hive = RegistryHive(second_hive_path)\n    if first_registry_hive.header.hive_bins_data_size != second_registry_hive.header.hive_bins_data_size:\n        found_differences.append(\n            (\n                \"different_hive_bin_data_size\",\n                first_registry_hive.header.hive_bins_data_size,\n                second_registry_hive.header.hive_bins_data_size,\n                \"\",\n            )\n        )\n\n    # Enumerate subkeys for each hive and start comparing\n    logger.info(f\"Enumerating subkeys in {os.path.basename(first_hive_path)}\")\n    first_hive_subkeys = get_subkeys_and_timestamps(first_registry_hive)\n\n    logger.info(f\"Enumerating subkeys in {os.path.basename(second_hive_path)}\")\n    second_hive_subkeys = get_subkeys_and_timestamps(second_registry_hive)\n\n    # Get a set of keys present in one hive and not the other and vice versa\n    first_hive_subkey_names = {x[0] for x in first_hive_subkeys if x[0] is not None}\n    second_hive_subkey_names = {x[0] for x in second_hive_subkeys if x[0] is not None}\n\n    found_differences.extend(\n        (\"new_subkey\", ts, None, subkey_path)\n        for subkey_path, ts in get_timestamp_for_subkeys(\n            first_registry_hive, first_hive_subkey_names - second_hive_subkey_names\n        )\n    )\n\n    found_differences.extend(\n        (\"new_subkey\", None, ts, subkey_path)\n        for subkey_path, ts in get_timestamp_for_subkeys(\n            second_registry_hive, second_hive_subkey_names - first_hive_subkey_names\n        )\n    )\n\n    # Remove duplicate keys from each of the sets\n    first_hive_diff_subkeys = first_hive_subkeys - second_hive_subkeys\n    second_hive_diff_subkeys = second_hive_subkeys - first_hive_subkeys\n\n    # Find subkeys that exist in both hives, but were modified. Look for new values and subkeys\n    for path_1, ts_1 in first_hive_diff_subkeys:\n        for path_2, ts_2 in second_hive_diff_subkeys:\n            if path_1 and path_1 == path_2 and ts_1 != ts_2:\n                first_subkey_nk_record = first_registry_hive.get_key(path_1)\n                second_subkey_nk_record = second_registry_hive.get_key(path_2)\n\n                # Compare values between the subkeys\n                first_subkey_values = set()\n                second_subkey_values = set()\n\n                if first_subkey_nk_record.values_count:\n                    first_subkey_values = _get_name_value_tuples(first_subkey_nk_record)\n\n                if second_subkey_nk_record.values_count:\n                    second_subkey_values = _get_name_value_tuples(second_subkey_nk_record)\n\n                # If one hive or the other contain values, and they are different, compare values\n                if (first_subkey_values or second_subkey_values) and (first_subkey_values != second_subkey_values):\n                    first_hive_value_names = {x[0] for x in first_subkey_values}\n                    second_hive_value_names = {x[0] for x in second_subkey_values}\n\n                    values_in_first_but_not_in_second = first_hive_value_names - second_hive_value_names\n                    values_in_second_but_not_in_first = second_hive_value_names - first_hive_value_names\n\n                    # If there are value names that are present in the first subkey but not the second\n                    # Iterate over all values in the first subkey\n                    # If the value name is one of those that is not on the second subkey, add it to the set\n                    if values_in_first_but_not_in_second:\n                        found_differences.extend(\n                            (\"new_value\", f\"{n}: {d} @ {ts_1}\", None, path_1)\n                            for n, d in get_values_from_tuples(first_subkey_values, values_in_first_but_not_in_second)\n                        )\n\n                    if values_in_second_but_not_in_first:\n                        found_differences.extend(\n                            (\"new_value\", None, f\"{n}: {d} @ {ts_2}\", path_1)\n                            for n, d in get_values_from_tuples(second_subkey_values, values_in_second_but_not_in_first)\n                        )\n\n                # We do not compare subkeys for each subkey, because we would have detected those.\n    return found_differences\n"
  },
  {
    "path": "regipy/registry.py",
    "content": "import binascii\nimport datetime as dt\nimport logging\nfrom dataclasses import asdict, dataclass, field\nfrom io import BytesIO\nfrom typing import Optional, Union\n\nfrom construct import (\n    Bytes,\n    ConstError,\n    CString,\n    EnumIntegerString,\n    GreedyRange,\n    Int32sl,\n    Int32ul,\n    Int64ul,\n    StreamError,\n)\n\nfrom regipy.exceptions import (\n    NoRegistrySubkeysException,\n    RegipyGeneralException,\n    RegistryKeyNotFoundException,\n    RegistryParsingException,\n    RegistryValueNotFoundException,\n    UnidentifiedHiveException,\n)\nfrom regipy.hive_types import SUPPORTED_HIVE_TYPES\nfrom regipy.security_utils import convert_sid, get_acls\nfrom regipy.structs import (\n    BIG_DATA_BLOCK,\n    CM_KEY_NODE,\n    DEFAULT_VALUE,\n    FAST_LEAF_SIGNATURE,\n    HASH_LEAF_SIGNATURE,\n    HBIN_HEADER,\n    INDEX_LEAF,\n    INDEX_ROOT,\n    INDEX_ROOT_SIGNATURE,\n    LEAF_INDEX_SIGNATURE,\n    LF_LH_SK_ELEMENT,\n    REGF_HEADER,\n    REGF_HEADER_SIZE,\n    SECURITY_DESCRIPTOR,\n    SID,\n    VALUE_KEY,\n    VALUE_TYPE_ENUM,\n    SECURITY_KEY_v1_1,\n)\nfrom regipy.utils import (\n    MAX_LEN,\n    boomerang_stream,\n    convert_wintime,\n    identify_hive_type,\n    trim_registry_data_for_error_msg,\n    try_decode_binary,\n)\n\nlogger = logging.getLogger(__name__)\n\n\n@dataclass\nclass Cell:\n    \"\"\"\n    Represents a Registry cell header\n    \"\"\"\n\n    offset: int\n    cell_type: str\n    size: int\n\n\n@dataclass\nclass VKRecord:\n    \"\"\"\n    The VK Record contains a value\n    \"\"\"\n\n    value_type: EnumIntegerString\n    value_type_str: str\n    value: bytes\n    size: int = 0\n    is_corrupted: bool = False\n\n\n@dataclass\nclass LIRecord:\n    data: bytes\n\n\n@dataclass\nclass Value:\n    name: str\n    value: Union[str, int, bytes]\n    value_type: str\n    is_corrupted: bool = False\n\n\n@dataclass\nclass Subkey:\n    subkey_name: str\n    path: str\n    timestamp: dt.datetime\n    values_count: int\n    values: list[Value] = field(default_factory=list)\n\n    # This field will be used if a partial hive was given, if not it would be None.\n    actual_path: Optional[str] = None\n\n\nclass RIRecord:\n    data = None\n    header = None\n\n    def __init__(self, stream):\n        self.header = INDEX_ROOT.parse_stream(stream)\n\n\nclass RegistryHive:\n    CONTROL_SETS = [r\"\\ControlSet001\", r\"\\ControlSet002\"]\n\n    def __init__(self, hive_path, hive_type=None, partial_hive_path=None):\n        \"\"\"\n        Represents a registry hive\n        :param hive_path: Path to the registry hive\n        :param hive_type: The hive type can be specified if this is a partial hive,\n                          or for some other reason regipy cannot identify the hive type\n        :param partial_hive_path: The path from which the partial hive actually starts, for example:\n                                  hive_type=ntuser partial_hive_path=\"/Software\" would mean\n                                  this is actually a HKCU hive, starting from HKCU/Software\n        \"\"\"\n\n        self.partial_hive_path = None\n        self.hive_type = None\n\n        with open(hive_path, \"rb\") as f:\n            self._stream = BytesIO(f.read())\n\n        with boomerang_stream(self._stream) as s:\n            self.header = REGF_HEADER.parse_stream(s)\n\n            # Get the first cell in root HBin, which is the root NKRecord:\n            root_hbin = self.get_hbin_at_offset()\n            root_hbin_cell = next(root_hbin.iter_cells(s))\n            self.root = NKRecord(root_hbin_cell, s)\n        self.name = self.header.file_name\n\n        if hive_type:\n            if hive_type.lower() in SUPPORTED_HIVE_TYPES:\n                self.hive_type = hive_type\n            else:\n                raise UnidentifiedHiveException(\n                    f\"{hive_type} is not a supported hive type: only the following are supported: {SUPPORTED_HIVE_TYPES}\"\n                )\n        else:\n            try:\n                self.hive_type = identify_hive_type(self.name)\n            except UnidentifiedHiveException:\n                logger.info(f\"Hive type for {hive_path} was not identified: {self.name}\")\n\n        if partial_hive_path:\n            self.partial_hive_path = partial_hive_path\n\n    def recurse_subkeys(\n        self,\n        nk_record=None,\n        path_root=None,\n        as_json=False,\n        is_init=True,\n        fetch_values=True,\n    ):\n        \"\"\"\n        Recurse over a subkey, and yield all of its subkeys and values\n        :param nk_record: an instance of NKRecord from which to start iterating, if None, will start from Root\n        :param path_root: If we are iterating an incomplete hive, for example a hive tree starting\n                          from ControlSet001 and not SYSTEM, there is no way to know that.\n                          This string will be added as prefix to all paths.\n        :param as_json: Whether to normalize the data as JSON or not\n        :param fetch_values: If False, subkey values will not be returned, but the iteration will be faster\n        \"\"\"\n        # If None, will start iterating from Root NK entry\n        if not nk_record:\n            nk_record = self.root\n\n        # Iterate over subkeys\n        if nk_record.header.subkey_count:\n            for subkey in nk_record.iter_subkeys():\n                if path_root:\n                    subkey_path = rf\"{path_root}\\{subkey.name}\" if path_root else rf\"\\{subkey.name}\"\n                else:\n                    subkey_path = f\"\\\\{subkey.name}\"\n\n                # Leaf Index records do not contain subkeys\n                if isinstance(subkey, LIRecord):\n                    continue\n\n                if subkey.subkey_count:\n                    yield from self.recurse_subkeys(\n                        nk_record=subkey,\n                        path_root=subkey_path,\n                        as_json=as_json,\n                        is_init=False,\n                        fetch_values=fetch_values,\n                    )\n\n                values = []\n                if fetch_values and subkey.values_count:\n                    try:\n                        if as_json:\n                            values = [asdict(x) for x in subkey.iter_values(as_json=as_json)]\n                        else:\n                            values = list(subkey.iter_values(as_json=as_json))\n                    except RegistryParsingException:\n                        logger.exception(f\"Failed to parse hive value at path: {trim_registry_data_for_error_msg(path_root)}\")\n\n                ts = convert_wintime(subkey.header.last_modified)\n                yield Subkey(\n                    subkey_name=subkey.name,\n                    path=subkey_path,\n                    timestamp=ts.isoformat() if as_json else ts,\n                    values=values,\n                    values_count=subkey.values_count,\n                    actual_path=(f\"{self.partial_hive_path}{subkey_path}\" if self.partial_hive_path else None),\n                )\n\n        if is_init:\n            # Get the values of the subkey\n            values = []\n            if nk_record.values_count:\n                try:\n                    if as_json:\n                        values = [asdict(x) for x in nk_record.iter_values(as_json=as_json)]\n                    else:\n                        values = list(nk_record.iter_values(as_json=as_json))\n                except RegistryParsingException as ex:\n                    logger.exception(f\"Failed to parse hive value at path: {trim_registry_data_for_error_msg(path_root)}: {ex}\")\n                    values = []\n\n            ts = convert_wintime(nk_record.header.last_modified)\n            subkey_path = path_root or \"\\\\\"\n            yield Subkey(\n                subkey_name=nk_record.name,\n                path=subkey_path,\n                timestamp=ts.isoformat() if as_json else ts,\n                values=values,\n                values_count=len(values),\n                actual_path=(f\"{self.partial_hive_path}\\\\{subkey_path}\" if self.partial_hive_path else None),\n            )\n\n    def get_hbin_at_offset(self, offset=0):\n        \"\"\"\n        This offset is from start of data (meaning that it starts from 4096)\n        If not offset is given, will return the root Hbin\n        :return:\n        \"\"\"\n        self._stream.seek(REGF_HEADER_SIZE + offset)\n        return HBin(self._stream)\n\n    def get_key(self, key_path):\n        if self.partial_hive_path:\n            if key_path.startswith(self.partial_hive_path):\n                key_path = key_path.partition(self.partial_hive_path)[-1]\n            else:\n                raise RegistryKeyNotFoundException(f\"Did not find subkey at {key_path}, because this is a partial hive\")\n\n        logger.debug(f\"Getting key: {key_path}\")\n\n        # If the key path is \\ we are just refering to root\n        if key_path == \"\\\\\":\n            return self.root\n\n        # If the path contain slashes, this is a full path. Split it\n        if \"\\\\\" in key_path:\n            key_path_parts = key_path.split(\"\\\\\")[1:]\n        else:\n            key_path_parts = [key_path]\n\n        previous_key_name = []\n\n        subkey = self.root.get_subkey(key_path_parts.pop(0), raise_on_missing=False)\n\n        if not subkey:\n            raise RegistryKeyNotFoundException(f\"Did not find subkey at {key_path}\")\n\n        if not key_path_parts:\n            return subkey\n\n        for path_part in key_path_parts:\n            new_path = \"\\\\\".join(previous_key_name)\n            previous_key_name.append(subkey.name)\n            subkey = subkey.get_subkey(path_part, raise_on_missing=False)\n\n            if not subkey:\n                raise RegistryKeyNotFoundException(f\"Did not find {path_part} at {new_path}\")\n        return subkey\n\n    def get_control_sets(self, registry_path):\n        \"\"\"\n        Get the optional control sets for a registry hive\n        :param registry_path:\n        :return: A list of paths, including the control sets\n        \"\"\"\n\n        found_control_sets = []\n        for cs in self.CONTROL_SETS:\n            try:\n                found_control_sets.append(self.get_key(cs))\n            except RegistryKeyNotFoundException:\n                continue\n        result = [rf\"\\{subkey.name}\\{registry_path}\" for subkey in found_control_sets]\n        logger.debug(f\"Found control sets: {result}\")\n        return result\n\n\nclass HBin:\n    def __init__(self, stream):\n        \"\"\"\n        :param stream: a stream at the start of the hbin block\n        \"\"\"\n        self.header = HBIN_HEADER.parse_stream(stream)\n        self.hbin_data_offset = stream.tell()\n\n    def iter_cells(self, stream):\n        stream.seek(self.hbin_data_offset)\n        offset = stream.tell()\n        while offset < self.hbin_data_offset + self.header.size - HBIN_HEADER.sizeof():\n            hbin_cell_size = Int32sl.parse_stream(stream)\n\n            # If the cell size is positive, it means it is unallocated. We are not interested in those on a regular run\n            if hbin_cell_size >= 0:\n                continue\n\n            bytes_to_read = (hbin_cell_size * -1) - 4\n\n            cell_type = Bytes(2).parse_stream(stream)\n\n            # Yield the cell\n            yield Cell(cell_type=cell_type.decode(), offset=stream.tell(), size=bytes_to_read)\n\n            # Go to the next cell\n            offset += stream.tell() + bytes_to_read\n\n\nclass NKRecord:\n    \"\"\"\n    The NKRecord represents a Name Key entry\n    \"\"\"\n\n    def __init__(self, cell, stream):\n        stream.seek(cell.offset)\n        self.header = CM_KEY_NODE.parse_stream(stream)\n        self._stream = stream\n\n        # Sometimes the key names are ASCII and sometimes UTF-16 little endian\n        if self.header.flags.KEY_COMP_NAME:\n            # Compressed (ASCII) key name\n            self.name = self.header.key_name_string.decode(\"ascii\", errors=\"replace\")\n        else:\n            # Unicode (UTF-16) key name\n            self.name = self.header.key_name_string.decode(\"utf-16-le\", errors=\"replace\")\n            logger.debug(f'Unicode key name identified: \"{self.name}\"')\n\n        self.subkey_count = self.header.subkey_count\n        self.values_count = self.header.values_count\n        self.volatile_subkeys_count = self.header.volatile_subkey_count\n\n    def get_subkey(self, key_name, raise_on_missing=True):\n        if not self.subkey_count and raise_on_missing:\n            raise NoRegistrySubkeysException(f\"No subkeys for {self.header.key_name_string}\")\n\n        for subkey in self.iter_subkeys():\n            # This should not happen\n            if not isinstance(subkey, NKRecord):\n                raise RegipyGeneralException(f\"Unknown record type: {subkey}\")\n\n            if subkey.name.upper() == key_name.upper():\n                return subkey\n\n        if raise_on_missing:\n            raise NoRegistrySubkeysException(f\"No subkey {key_name} for {self.header.key_name_string}\")\n\n    def iter_subkeys(self):\n        if not self.header.subkey_count:\n            return None\n\n        # Go to the offset where the subkey list starts (+4 is because of the cell header)\n        target_offset = REGF_HEADER_SIZE + 4 + self.header.subkeys_list_offset\n        self._stream.seek(target_offset)\n\n        # Read the signature\n        try:\n            signature = Bytes(2).parse_stream(self._stream)\n        except StreamError as ex:\n            raise RegistryParsingException(f\"Bad subkey at offset {target_offset}: {ex}\")\n\n        # LF,  LH and RI contain subkeys\n        if signature in [\n            HASH_LEAF_SIGNATURE,\n            FAST_LEAF_SIGNATURE,\n            LEAF_INDEX_SIGNATURE,\n        ]:\n            yield from self._parse_subkeys(self._stream, signature=signature)\n        # RI contains pointers to arrays of subkeys\n        elif signature == INDEX_ROOT_SIGNATURE:\n            ri_record = RIRecord(self._stream)\n            if ri_record.header.element_count > 0:\n                for element in ri_record.header.elements:\n                    # We skip 6 because of the signature as well as the cell header\n                    element_target_offset = REGF_HEADER_SIZE + 4 + element.subkey_list_offset\n                    self._stream.seek(element_target_offset)\n                    yield from self._parse_subkeys(self._stream)\n\n    @staticmethod\n    def _parse_subkeys(stream, signature=None):\n        \"\"\"\n        Parse an LI , LF or LH Record\n        :param stream: A stream at the header of the LH or LF entry, skipping the signature\n        :return:\n        \"\"\"\n        if not signature:\n            signature = stream.read(2)\n\n        if signature in [HASH_LEAF_SIGNATURE, FAST_LEAF_SIGNATURE]:\n            subkeys = LF_LH_SK_ELEMENT.parse_stream(stream)\n        elif signature == LEAF_INDEX_SIGNATURE:\n            subkeys = INDEX_LEAF.parse_stream(stream)\n        else:\n            raise RegistryParsingException(f\"Expected a known signature, got: {signature} at offset {stream.tell()}\")\n\n        for subkey in subkeys.elements:\n            stream.seek(REGF_HEADER_SIZE + subkey.key_node_offset)\n\n            # This cell should always be allocated, therefor we expect a negative size\n            cell_size = Int32sl.parse_stream(stream) * -1\n\n            # We read to this offset and skip 2 bytes, because that is the cell size we just read\n            nk_cell = Cell(cell_type=\"nk\", offset=stream.tell() + 2, size=cell_size)\n            nk_record = NKRecord(cell=nk_cell, stream=stream)\n            yield nk_record\n\n    @staticmethod\n    def read_value(vk, stream):\n        \"\"\"\n        Read a registry value\n        :param vk: A parse VK record\n        :param stream: The registry stream\n        :return: A VKRecord\n        \"\"\"\n        stream.seek(REGF_HEADER_SIZE + 4 + vk.data_offset)\n        data_type = vk.data_type\n        data = stream.read(vk.data_size)\n        return VKRecord(\n            value_type=data_type,\n            value_type_str=str(data_type),\n            value=data,\n            size=vk.data_size,\n        )\n\n    @staticmethod\n    def _parse_indirect_block(stream, value):\n        # This is an indirect datablock (Bigger than 16344, therefor we handle it differently)\n        # The value inside the vk entry actually contains a pointer to the buffers containing the data\n        big_data_block_header = BIG_DATA_BLOCK.parse(value.value)\n\n        # Go to the start of the segment offset list\n        stream.seek(REGF_HEADER_SIZE + big_data_block_header.offset_to_list_of_segments)\n        buffer = BytesIO()\n\n        # Read them sequentially until we got all the size of the VK\n        value_size = value.size\n        while value_size > 0:\n            data_segment_offset = Int32ul.parse_stream(stream)\n            with boomerang_stream(stream) as tmpstream:\n                tmpstream.seek(REGF_HEADER_SIZE + 4 + data_segment_offset)\n                tmpbuffer = tmpstream.read(min(0x3FD8, value_size))\n                value_size -= len(tmpbuffer)\n                buffer.write(tmpbuffer)\n        buffer.seek(0)\n        return buffer.read()\n\n    def iter_values(self, as_json=False, max_len=MAX_LEN, trim_values=True):\n        \"\"\"\n        Get the values of a subkey. Will raise if no values exist\n        :param as_json: Whether to normalize the data as JSON or not\n        :param max_len: Max length of value to return\n        :param trim_values: whether to trim values to MAX_LEN\n        :return: List of values for the subkey\n        \"\"\"\n        if not self.values_count:\n            return\n\n        # Get the offset of the values key. We skip 4 because of Cell Header\n        target_offset = REGF_HEADER_SIZE + 4 + self.header.values_list_offset\n        self._stream.seek(target_offset)\n\n        for _ in range(self.values_count):\n            is_corrupted = False\n            try:\n                vk_offset = Int32ul.parse_stream(self._stream)\n            except StreamError:\n                logger.info(f\"Skipping bad registry VK at {self._stream.tell()}\")\n                raise RegistryParsingException(f\"Bad registry VK at {self._stream.tell()}\")\n\n            with boomerang_stream(self._stream) as substream:\n                actual_vk_offset = REGF_HEADER_SIZE + 4 + vk_offset\n                substream.seek(actual_vk_offset)\n                try:\n                    vk = VALUE_KEY.parse_stream(substream)\n                except (ConstError, StreamError):\n                    logger.error(f\"Could not parse VK at {substream.tell()}, registry hive is probably corrupted.\")\n                    return\n\n                value = self.read_value(vk, substream)\n\n                if vk.name_size == 0:\n                    value_name = \"(default)\"\n                elif vk.flags.VALUE_COMP_NAME:\n                    # Compressed (ASCII) value name\n                    value_name = vk.name.decode(\"ascii\", errors=\"replace\")\n                else:\n                    # Unicode (UTF-16) value name\n                    value_name = vk.name.decode(\"utf-16-le\", errors=\"replace\")\n                    logger.debug(f'Unicode value name identified: \"{value_name}\"')\n\n                # If the value is bigger than this value, it means this is a DEVPROP structure\n                # https://doxygen.reactos.org/d0/dba/devpropdef_8h_source.html\n                # https://sourceforge.net/p/mingw-w64/mingw-w64/ci/668a1d3e85042c409e0c292e621b3dc0aa26177c/tree/\n                # mingw-w64-headers/include/devpropdef.h?diff=dd86a3b7594dadeef9d6a37c4b6be3ca42ef7e94\n                # We currently do not support these, We are going to make the best effort to dump as string.\n                # This int casting will always work because the data_type is construct's EnumIntegerString\n                if int(vk.data_type) > 0xFFFF0000:\n                    data_type = VALUE_TYPE_ENUM.parse(Int32ul.build(int(vk.data_type) & 0xFFFF))\n                    logger.info(f\"Value at {hex(actual_vk_offset)} contains DEVPROP structure of type {data_type}\")\n\n                # Skip this unknown data type, research pending :)\n                # TODO: Add actual parsing\n                elif int(vk.data_type) == 0x200000:\n                    logger.info(f\"Skipped unknown data type value at {actual_vk_offset}\")\n                    continue\n                else:\n                    data_type = str(vk.data_type)\n\n                if data_type in [\"REG_SZ\", \"REG_EXPAND\", \"REG_EXPAND_SZ\"]:\n                    if vk.data_size >= 0x80000000:\n                        # data is contained in the data_offset field\n                        value.size -= 0x80000000\n                        actual_value = vk.data_offset\n                    elif vk.data_size > 0x3FD8 and value.value[:2] == b\"db\":\n                        data = self._parse_indirect_block(substream, value)\n                        actual_value = try_decode_binary(data, as_json=as_json, trim_values=trim_values)\n                    else:\n                        actual_value = try_decode_binary(value.value, as_json=as_json, trim_values=trim_values)\n                elif data_type in [\"REG_BINARY\", \"REG_NONE\"]:\n                    if vk.data_size >= 0x80000000:\n                        # data is contained in the data_offset field\n                        actual_value = vk.data_offset\n                    elif vk.data_size > 0x3FD8 and value.value[:2] == b\"db\":\n                        try:\n                            actual_value = self._parse_indirect_block(substream, value)\n\n                            actual_value = (\n                                try_decode_binary(actual_value, as_json=True, trim_values=trim_values)\n                                if as_json\n                                else actual_value\n                            )\n                        except ConstError:\n                            logger.error(f\"Bad value at {actual_vk_offset}\")\n                            continue\n                    else:\n                        # Return the actual data\n                        actual_value = binascii.b2a_hex(value.value).decode()[:max_len] if trim_values else value.value\n                elif data_type == \"REG_SZ\":\n                    actual_value = try_decode_binary(value.value, as_json=as_json, trim_values=trim_values)\n                elif data_type == \"REG_DWORD\":\n                    # If the data size is bigger than 0x80000000, data is actually stored in the VK data offset.\n                    actual_value = vk.data_offset if vk.data_size >= 0x80000000 else Int32ul.parse(value.value)\n                elif data_type == \"REG_QWORD\":\n                    actual_value = vk.data_offset if vk.data_size >= 0x80000000 else Int64ul.parse(value.value)\n                elif data_type == \"REG_MULTI_SZ\":\n                    parsed_value = GreedyRange(CString(\"utf-16-le\")).parse(value.value)\n                    # Because the ListContainer object returned by Construct cannot be turned into a list,\n                    # we do this trick\n                    actual_value = [x for x in parsed_value if x]\n                # We currently dumps this as hex string or raw\n                # TODO: Add actual parsing\n                elif data_type in [\n                    \"REG_RESOURCE_REQUIREMENTS_LIST\",\n                    \"REG_RESOURCE_LIST\",\n                ]:\n                    actual_value = binascii.b2a_hex(value.value).decode()[:max_len] if trim_values else value.value\n                elif data_type == \"REG_FILETIME\":\n                    actual_value = convert_wintime(Int64ul.parse(value.value), as_json=as_json)\n                else:\n                    actual_value = try_decode_binary(value.value, as_json=as_json, trim_values=trim_values)\n                yield Value(\n                    name=value_name,\n                    value_type=data_type,\n                    value=actual_value,\n                    is_corrupted=is_corrupted,\n                )\n\n    def get_value(\n        self,\n        value_name=DEFAULT_VALUE,\n        as_json=False,\n        raise_on_missing=False,\n        case_sensitive=True,\n    ):\n        \"\"\"\n        Get a value by name. Will raise if raise_on_missing is set,\n        if no value name is given, will return the content of the default value\n        :param value_name: The value name to look for\n        :param as_json: Whether to normalize the data as JSON or not\n        :param raise_on_missing: Will raise exception if value is missing, else will return None\n        :return:\n        \"\"\"\n        value_name = value_name if case_sensitive else value_name.lower()\n        for value in self.iter_values(as_json=as_json, trim_values=False):\n            v = value.name if case_sensitive else value.name.lower()\n            if v == value_name:\n                return value.value\n\n        if raise_on_missing:\n            raise RegistryValueNotFoundException(f\"Did not find the value {value_name} on subkey {self.name}\")\n        return None\n\n    def get_values(self, as_json=False, trim_values=False):\n        return list(self.iter_values(as_json=as_json, trim_values=trim_values))\n\n    def get_security_key_info(self):\n        self._stream.seek(REGF_HEADER_SIZE + self.header.security_key_offset)\n        # TODO: If parsing fails, parse with SECURITY_KEY_v1_2\n        security_key = SECURITY_KEY_v1_1.parse_stream(self._stream)\n        security_descriptor = SECURITY_DESCRIPTOR.parse(security_key.security_descriptor)\n\n        with boomerang_stream(self._stream) as s:\n            security_base_offset = REGF_HEADER_SIZE + self.header.security_key_offset + 24\n\n            s.seek(security_base_offset + security_descriptor.owner)\n            owner_sid = convert_sid(SID.parse_stream(s))\n\n            s.seek(security_base_offset + security_descriptor.group)\n            group_sid = convert_sid(SID.parse_stream(s))\n\n            sacl_aces = None\n            if security_descriptor.offset_sacl > 0:\n                s.seek(security_base_offset + security_descriptor.offset_sacl)\n                sacl_aces = get_acls(s)\n\n            dacl_aces = None\n            if security_descriptor.offset_dacl > 0:\n                s.seek(security_base_offset + security_descriptor.offset_dacl)\n                dacl_aces = get_acls(s)\n            return {\n                \"owner\": owner_sid,\n                \"group\": group_sid,\n                \"dacl\": dacl_aces,\n                \"sacl\": sacl_aces,\n            }\n\n    def get_class_name(self) -> str:\n        \"\"\"\n        Gets the key class name as would be returned via\n        the `lpClass` argument of the `RegQueryInfoKey()` function.\n        \"\"\"\n\n        # Get the offset of the class name string. We skip 4 because of Cell Header\n        read_offset = REGF_HEADER_SIZE + 4 + self.header.class_name_offset\n\n        self._stream.seek(read_offset)\n        class_name = self._stream.read(self.header.class_name_size)\n\n        return class_name.decode(\"utf-16-le\", errors=\"replace\")\n\n    def __dict__(self):\n        return {\n            \"name\": self.name,\n            \"subkey_count\": self.subkey_count,\n            \"value_count\": self.values_count,\n            \"values\": ({x[\"name\"]: x[\"value\"] for x in self.iter_values()} if self.values_count else None),\n            \"subkeys\": ({x[\"name\"] for x in self.iter_subkeys()} if self.subkey_count else None),\n            \"timestamp\": convert_wintime(self.header.last_modified, as_json=True),\n            \"volatile_subkeys\": self.volatile_subkeys_count,\n        }\n"
  },
  {
    "path": "regipy/security_utils.py",
    "content": "from typing import Any\n\nfrom construct import Int64ub\n\nfrom regipy.structs import ACE, ACL, SID\n\n\ndef convert_sid(sid: Any, strip_rid: bool = False) -> str:\n    identifier_authority = Int64ub.parse(b\"\\x00\\x00\" + sid.identifier_authority)\n    sub_authorities = sid.subauthority[:-1] if strip_rid else sid.subauthority\n    sub_identifier_authorities = \"-\".join(str(x) for x in sub_authorities)\n    return f\"S-{sid.revision}-{identifier_authority}-{sub_identifier_authorities}\"\n\n\ndef get_acls(s):\n    aces = []\n    dacl = ACL.parse_stream(s)\n    for _ in range(dacl.ace_count):\n        parsed_ace = ACE.parse_stream(s)\n        ace_sid = SID.parse(parsed_ace.sid)\n\n        aces.append(\n            {\n                \"ace_type\": str(parsed_ace.type),\n                \"flags\": dict(parsed_ace.flags),\n                \"access_mask\": dict(parsed_ace.access_mask),\n                \"sid\": convert_sid(ace_sid),\n            }\n        )\n    return aces\n"
  },
  {
    "path": "regipy/structs.py",
    "content": "from construct import (\n    Array,\n    Bytes,\n    Const,\n    Enum,\n    FlagsEnum,\n    Int8ul,\n    Int16ul,\n    Int32ul,\n    Int64ul,\n    PaddedString,\n    Struct,\n    this,\n)\n\nREGF_HEADER = Struct(\n    \"signature\" / Const(b\"regf\"),\n    \"primary_sequence_num\" / Int32ul,\n    \"secondary_sequence_num\" / Int32ul,\n    \"last_modification_time\" / Int64ul,\n    \"major_version\" / Int32ul,\n    \"minor_version\" / Int32ul,\n    \"file_type\" / Int32ul,\n    \"file_format\" / Int32ul,\n    \"root_key_offset\" / Int32ul,\n    \"hive_bins_data_size\" / Int32ul,\n    \"clustering_factor\" / Int32ul,\n    \"file_name\" / PaddedString(64, \"utf-16-le\"),\n    \"padding\" * Bytes(396),\n    \"checksum\" / Int32ul,\n).compile()\n\nREGF_HEADER_SIZE = 4096\n\nHBIN_HEADER = Struct(\n    \"signature\" / Const(b\"hbin\"),\n    \"offset\" / Int32ul,\n    \"size\" / Int32ul,\n    \"unknown\" * Int32ul,\n    \"unknown\" * Int32ul,\n    \"timestamp\" / Int64ul,\n    \"unknown\" * Int32ul,\n).compile()\n\nCM_KEY_NODE_SIZE = 76\nCM_KEY_NODE = Struct(\n    \"flags\"\n    / FlagsEnum(\n        Int16ul,\n        KEY_VOLATILE=0x0001,\n        KEY_HIVE_EXIT=0x0002,\n        KEY_HIVE_ENTRY=0x0004,\n        KEY_NO_DELETE=0x0008,\n        KEY_SYM_LINK=0x0010,\n        KEY_COMP_NAME=0x0020,\n        KEY_PREDEF_HANDLE=0x0040,\n    ),\n    \"last_modified\" / Int64ul,\n    \"access_bits\" / Bytes(4),\n    \"parent_key_offset\" / Int32ul,\n    \"subkey_count\" / Int32ul,\n    \"volatile_subkey_count\" / Int32ul,\n    \"subkeys_list_offset\" / Int32ul,\n    \"volatile_subkeys_list_offset\" / Int32ul,\n    \"values_count\" / Int32ul,\n    \"values_list_offset\" / Int32ul,\n    \"security_key_offset\" / Int32ul,\n    \"class_name_offset\" / Int32ul,\n    \"largest_sk_name\" / Int32ul,\n    \"largest_sk_class_name\" / Int32ul,\n    \"largest_value_name\" / Int32ul,\n    \"largest_value_data\" / Int32ul,\n    \"unknown\" * Bytes(4),\n    \"key_name_size\" / Int16ul,\n    \"class_name_size\" / Int16ul,\n    \"key_name_string\" / Bytes(this.key_name_size),\n).compile()\n\nSUBKEY_LIST_HEADER = Struct(\n    \"signature\" / Bytes(2),\n    \"element_count\" / Int16ul,\n).compile()\n\nHASH_LEAF = Struct(\n    \"element_count\" / Int16ul,\n    \"elements\"\n    / Array(\n        this.element_count,\n        Struct(\n            \"key_node_offset\" / Int32ul,\n            \"name_hash\" / Int32ul,\n        ),\n    ),\n).compile()\n\nFAST_LEAF = Struct(\n    \"element_count\" / Int16ul,\n    \"elements\"\n    / Array(\n        this.element_count,\n        Struct(\n            \"key_node_offset\" / Int32ul,\n            \"name_hint\" / Int32ul,\n        ),\n    ),\n).compile()\n\nLEAF_INDEX_SIGNATURE = b\"li\"\nINDEX_LEAF = Struct(\n    \"element_count\" / Int16ul,\n    \"elements\"\n    / Array(\n        this.element_count,\n        Struct(\n            \"key_node_offset\" / Int32ul,\n        ),\n    ),\n).compile()\n\nINDEX_ROOT_SIGNATURE = b\"ri\"\nINDEX_ROOT = Struct(\n    \"element_count\" / Int16ul,\n    \"elements\"\n    / Array(\n        this.element_count,\n        Struct(\n            \"subkey_list_offset\" / Int32ul,\n        ),\n    ),\n).compile()\n\nKEY_SECURITY = Struct(\n    \"reserved\" * Bytes(2),\n    \"forward_link\" / Int32ul,\n    \"static_link\" / Int32ul,\n    \"reference_count\" / Int32ul,\n    \"security_descriptor_size\" / Int32ul,\n    \"security_descriptor\" / Bytes(this.security_descriptor_size),\n).compile()\n\nVALUE_TYPE_ENUM = Enum(\n    Int32ul,\n    REG_NONE=0,\n    REG_SZ=1,\n    REG_EXPAND_SZ=2,\n    REG_BINARY=3,\n    REG_DWORD=4,\n    REG_DWORD_BIG_ENDIAN=5,\n    REG_LINK=6,\n    REG_MULTI_SZ=7,\n    REG_RESOURCE_LIST=8,\n    REG_FULL_RESOURCE_DESCRIPTOR=9,\n    REG_RESOURCE_REQUIREMENTS_LIST=10,\n    REG_QWORD=11,\n    REG_FILETIME=16,\n)\n\nVALUE_KEY = Struct(\n    \"signature\" / Const(b\"vk\"),\n    \"name_size\" / Int16ul,\n    \"data_size\" / Int32ul,\n    \"data_offset\" / Int32ul,\n    \"data_type\" / VALUE_TYPE_ENUM,\n    \"flags\" / FlagsEnum(Int16ul, VALUE_COMP_NAME=0x0001),\n    \"padding\" * Int16ul,\n    \"name\" / Bytes(this.name_size),\n).compile()\n\nHASH_LEAF_SIGNATURE = b\"lh\"\nFAST_LEAF_SIGNATURE = b\"lf\"\nLF_LH_SK_ELEMENT = Struct(\n    \"element_count\" / Int16ul,\n    \"elements\" / Array(this.element_count, Struct(\"key_node_offset\" / Int32ul, \"hash_value\" / Int32ul)),\n).compile()\n\nDIRTY_PAGES_REFERENCES = Struct(\"offset\" / Int32ul, \"size\" / Int32ul)\n\nTRANSACTION_LOG = Struct(\n    \"signature\" / Const(b\"HvLE\"),\n    \"log_size\" / Int32ul,\n    \"flags\" / Int32ul,\n    \"sequence_number\" / Int32ul,\n    \"hive_bin_size\" / Int32ul,\n    \"dirty_pages_count\" / Int32ul,\n    \"hash_1\" / Int64ul,\n    \"hash_2\" / Int64ul,\n    \"dirty_pages_references\" / Array(this.dirty_pages_count, DIRTY_PAGES_REFERENCES),\n)\n\nBIG_DATA_SIGNATURE = b\"db\"\nBIG_DATA_BLOCK = Struct(\n    \"signature\" / Const(BIG_DATA_SIGNATURE),\n    \"number_of_segments\" / Int16ul,\n    \"offset_to_list_of_segments\" / Int32ul,\n)\n\n# This is the default name of a registry subkey\nDEFAULT_VALUE = \"(default)\"\n\nSECURITY_KEY_v1_1 = Struct(\n    \"unknown\" * Bytes(4),\n    \"signature\" / Const(b\"sk\"),\n    \"unknown\" * Bytes(2),\n    \"prev_sk_offset\" / Int32ul,\n    \"next_sk_offset\" / Int32ul,\n    \"reference_count\" / Int32ul,\n    \"security_descriptor_size\" / Int32ul,\n    \"security_descriptor\" / Bytes(this.security_descriptor_size),\n)\n\nSECURITY_KEY_v1_2 = Struct(\n    \"signature\" / Const(b\"sk\"),\n    \"unknown\" * Bytes(2),\n    \"prev_sk_offset\" / Int32ul,\n    \"next_sk_offset\" / Int32ul,\n    \"reference_count\" / Int32ul,\n    \"security_descriptor_size\" / Int32ul,\n    \"security_descriptor\" / Bytes(this.security_descriptor_size),\n    \"ref_count\" / Int32ul,\n    \"sdlen\" / Int32ul,\n)\n\n# References:\n# https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor\n# https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-control\nSECURITY_DESCRIPTOR = Struct(\n    \"revision\" / Bytes(1),\n    \"sbz1\" / Bytes(1),\n    \"control\"\n    / FlagsEnum(\n        Int16ul,\n        SE_DACL_AUTO_INHERIT_REQ=0x0100,\n        SE_DACL_AUTO_INHERITED=0x0400,\n        SE_DACL_DEFAULTED=0x0008,\n        SE_DACL_PRESENT=0x0004,\n        SE_DACL_PROTECTED=0x1000,\n        SE_GROUP_DEFAULTED=0x0002,\n        SE_OWNER_DEFAULTED=0x0001,\n        SE_RM_CONTROL_VALID=0x4000,\n        SE_SACL_AUTO_INHERIT_REQ=0x0200,\n        SE_SACL_AUTO_INHERITED=0x0800,\n        SE_SACL_DEFAULTED=0x0008,\n        SE_SACL_PRESENT=0x0010,\n        SE_SACL_PROTECTED=0x2000,\n        SE_SELF_RELATIVE=0x8000,\n    ),\n    \"owner\" / Int32ul,\n    \"group\" / Int32ul,\n    \"offset_sacl\" / Int32ul,\n    \"offset_dacl\" / Int32ul,\n)\n\n# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/c6ce4275-3d90-4890-ab3a-514745e4637e\n# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/f992ad60-0fe4-4b87-9fed-beb478836861\n# https://flatcap.org/linux-ntfs/ntfs/attributes/security_descriptor.html\nSID = Struct(\n    \"revision\" / Int8ul,\n    \"sub_authority_count\" / Int8ul,\n    \"identifier_authority\" / Bytes(6),\n    \"subauthority\" / Int32ul[this.sub_authority_count],\n)\n\nACL = Struct(\n    \"revision\" / Int8ul,\n    \"sbz1\" * Int8ul,\n    \"acl_size\" / Int16ul,\n    \"ace_count\" / Int16ul,\n    \"sbz2\" * Int16ul,\n).compile()\n\nACE = Struct(\n    \"type\"\n    / Enum(\n        Int8ul,\n        ACCESS_ALLOWED=0,\n        ACCESS_DENIED=1,\n        SYSTEM_AUDIT=2,\n        SYSTEM_ALARM=3,\n        ACCESS_ALLOWED_COMPOUND=4,\n        ACCESS_ALLOWED_OBJECT=5,\n        ACCESS_DENIED_OBJECT=6,\n        SYSTEM_AUDIT_OBJECT=7,\n        SYSTEM_ALARM_OBJECT=8,\n        ACCESS_ALLOWED_CALLBACK=9,\n        ACCESS_DENIED_CALLBACK=10,\n        ACCESS_ALLOWED_CALLBACK_OBJECT=11,\n        ACCESS_DENIED_CALLBACK_OBJECT=12,\n        SYSTEM_ALARM_CALLBACK=14,\n        SYSTEM_AUDIT_CALLBACK_OBJECT=15,\n        SYSTEM_ALARM_CALLBACK_OBJECT=16,\n    ),\n    \"flags\"\n    / FlagsEnum(\n        Int8ul,\n        OBJECT_INHERIT_ACE=0x1,\n        CONTAINER_INHERIT_ACE=0x2,\n        NO_PROPAGATE_INHERIT_ACE=0x4,\n        INHERIT_ONLY_ACE=0x8,\n    ),\n    \"size\" / Int16ul,\n    \"access_mask\"\n    / FlagsEnum(\n        Int32ul,\n        DELETE=0x00010000,\n        READ_CONTROL=0x00020000,\n        WRITE_DAC=0x00040000,\n        WRITE_OWNER=0x00080000,\n        SYNCHRONIZE=0x00100000,\n        ACCESS_SYSTEM_SECURITY=0x01000000,\n        MAXIMUM_ALLOWED=0x02000000,\n        GENERIC_ALL=0x10000000,\n        GENERIC_EXECUTE=0x20000000,\n        GENERIC_WRITE=0x40000000,\n        GENERIC_READ=0x80000000,\n    ),\n    \"sid\" / Bytes(this.size - 8),\n).compile()\n"
  },
  {
    "path": "regipy/utils.py",
    "content": "import binascii\nimport datetime as dt\nimport hashlib\nimport logging\nimport struct\nimport sys\nfrom collections.abc import Generator\nfrom contextlib import contextmanager\nfrom dataclasses import asdict\nfrom io import TextIOWrapper\nfrom typing import Union\n\nimport pytz\n\nfrom regipy.exceptions import (\n    NoRegistrySubkeysException,\n    RegipyGeneralException,\n    RegistryKeyNotFoundException,\n    UnidentifiedHiveException,\n)\nfrom regipy.hive_types import (\n    AMCACHE_HIVE_TYPE,\n    BCD_HIVE_TYPE,\n    NTUSER_HIVE_TYPE,\n    SAM_HIVE_TYPE,\n    SECURITY_HIVE_TYPE,\n    SOFTWARE_HIVE_TYPE,\n    SYSTEM_HIVE_TYPE,\n    USRCLASS_HIVE_TYPE,\n)\n\nlogger = logging.getLogger(__name__)\n\n# Max size of string to return when as_json=True\nMAX_LEN: int = 256\n\n# The max string lenth when raising registry exceptions that include registry data\n# As the length can be arbitrarly large\nMAX_LEN_ERR_MSG_REGVALUE: int = 20\n\n\ndef calculate_sha1(file_path):\n    sha1 = hashlib.sha1()\n    with open(file_path, \"rb\") as f:\n        while True:\n            data = f.read(1024**2)\n            if not data:\n                break\n            sha1.update(data)\n    return sha1.hexdigest()\n\n\ndef calculate_xor32_checksum(b: bytes) -> int:\n    \"\"\"\n    Calculate xor32 checksum from buffer\n    :param b: buffer\n    :return: The calculated checksum\n    \"\"\"\n    checksum = 0\n    if len(b) % 4 != 0:\n        raise RegipyGeneralException(f\"Buffer must be multiples of four, {len(b)} length buffer given\")\n\n    for i in range(0, len(b), 4):\n        checksum = (b[i] + (b[i + 1] << 0x08) + (b[i + 2] << 0x10) + (b[i + 3] << 0x18)) ^ checksum\n    return checksum\n\n\n@contextmanager\ndef boomerang_stream(stream: TextIOWrapper) -> Generator[TextIOWrapper, None, None]:\n    \"\"\"\n    Yield a stream that goes back to the original offset after exiting the \"with\" context\n    :param stream: The stream\n    \"\"\"\n    current_offset = stream.tell()\n    yield stream\n    stream.seek(current_offset)\n\n\ndef convert_filetime(dw_low_date_time, dw_high_date_time):\n    \"\"\" \"\"\"\n    if dw_high_date_time is None or dw_low_date_time is None:\n        return None\n    try:\n        date = dt.datetime(1601, 1, 1, 0, 0, 0)\n        temp_time = dw_high_date_time\n        temp_time <<= 32\n        temp_time |= dw_low_date_time\n        date = pytz.utc.localize(date + dt.timedelta(microseconds=temp_time / 10))\n        return date.isoformat()\n    except OverflowError:\n        return None\n\n\n# Convert FILETIME to datetime.\n# Based on http://code.activestate.com/recipes/511425-filetime-to-datetime/\ndef convert_filetime2(dte):\n    try:\n        epoch = dt.datetime(1601, 1, 1, 0, 0, 0)\n        nt_timestamp = struct.unpack(\"<Q\", binascii.unhexlify(dte))[0]\n        epoch = dt.datetime(1601, 1, 1, 0, 0, 0)\n        nt_datetime = epoch + dt.timedelta(microseconds=nt_timestamp / 10)\n\n        return nt_datetime.strftime(\"%Y-%m-%d %H:%M:%S\")\n\n    except OverflowError:\n        return None\n\n\ndef convert_wintime(wintime: int, as_json=False) -> Union[dt.datetime, str]:\n    \"\"\"\n    Get an integer containing a FILETIME date\n    :param wintime: integer representing a FILETIME timestamp\n    :param as_json: whether to return the date as string or not\n    :return: datetime\n    \"\"\"\n    # http://stackoverflow.com/questions/4869769/convert-64-bit-windows-date-time-in-python\n    us = wintime / 10\n    try:\n        date = dt.datetime(1601, 1, 1, tzinfo=pytz.utc) + dt.timedelta(microseconds=us)\n    except OverflowError:\n        # If date is too big, it is probably corrupted' let's return the smallest possible windows timestamp.\n        date = dt.datetime(1601, 1, 1, tzinfo=pytz.utc)\n    return date.isoformat() if as_json else date\n\n\ndef get_subkey_values_from_list(registry_hive, entries_list, as_json=False, trim_values=True):\n    \"\"\"\n    Return a list of registry subkeys given a list of paths\n    :param registry_hive: A RegistryHive object\n    :param entries_list: A list of paths as strings\n    :param as_json: whether to return the subkey as json\n    :param trim_values: Wether to trim values to MAX_LEN\n    :return: A dict with each subkey and its values\n    \"\"\"\n    result = {}\n    for path in entries_list:\n        try:\n            subkey = registry_hive.get_key(path)\n        except (RegistryKeyNotFoundException, NoRegistrySubkeysException) as ex:\n            logger.debug(f\"Could not find subkey: {path} ({ex})\")\n            continue\n        ts = convert_wintime(subkey.header.last_modified, as_json=as_json)\n\n        values = []\n        if subkey.values_count:\n            if as_json:\n                values = [asdict(x) for x in subkey.iter_values(as_json=as_json, trim_values=trim_values)]\n            else:\n                values = list(subkey.iter_values(as_json=as_json, trim_values=trim_values))\n\n        if subkey.values_count:\n            result[path] = {\"timestamp\": ts, \"values\": values}\n    return result\n\n\ndef identify_hive_type(name: str) -> str:\n    hive_name = name.lower()\n    if hive_name.endswith(\"ntuser.dat\"):\n        return NTUSER_HIVE_TYPE\n    elif hive_name == SYSTEM_HIVE_TYPE:\n        return SYSTEM_HIVE_TYPE\n    elif hive_name.endswith(\"system32\\\\config\\\\software\"):\n        return SOFTWARE_HIVE_TYPE\n    elif hive_name == r\"\\systemroot\\system32\\config\\sam\":\n        return SAM_HIVE_TYPE\n    elif hive_name.endswith(r\"\\system32\\config\\security\"):\n        return SECURITY_HIVE_TYPE\n    elif hive_name.endswith(r\"\\boot\\bcd\"):\n        return BCD_HIVE_TYPE\n    elif hive_name == r\"\\microsoft\\windows\\usrclass.dat\":\n        return USRCLASS_HIVE_TYPE\n    elif \"amcache\" in hive_name.lower():\n        return AMCACHE_HIVE_TYPE\n    else:\n        raise UnidentifiedHiveException(f\"Could not identify hive: {name}\")\n\n\ndef try_decode_binary(data, as_json=False, max_len=MAX_LEN, trim_values=True):\n    try:\n        value = data.decode(\"utf-16-le\").rstrip(\"\\x00\")\n    except UnicodeDecodeError:\n        try:\n            value = data.decode().rstrip(\"\\x00\")\n        except Exception as ex:\n            logger.warning(f\"Could not parse data as string, formating to hex: {ex}\")\n            value = binascii.b2a_hex(data).decode() if as_json else data\n\n    if trim_values:\n        value = value[:max_len]\n\n    return value\n\n\ndef _setup_logging(verbose):\n    logging.basicConfig(\n        stream=sys.stderr,\n        level=logging.DEBUG if verbose else logging.INFO,\n    )\n\n\ndef trim_registry_data_for_error_msg(s: str, max_len: int = MAX_LEN_ERR_MSG_REGVALUE) -> str:\n    # Registry values included in Registry expections might be arbitrarly large,\n    return s[:max_len] + f\"... (trimmed original value from {len(s)} length)\"\n"
  },
  {
    "path": "regipy_mcp_server/README.md",
    "content": "# Regipy MCP Server\n\n**Windows Registry Analysis for Claude Desktop**\n\nEnable Claude to analyze Windows registry hives and answer forensic questions in natural language.\n\n## Quick Start (Windows)\n\n### 1. Configure Claude Desktop\n\nEdit `%APPDATA%\\Claude\\claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"regipy\": {\n      \"command\": \"C:\\\\Users\\\\YourUsername\\\\anaconda3\\\\envs\\\\regipy\\\\python.exe\",\n      \"args\": [\n        \"path\\\\to\\\\regipy_mcp_server\\\\server.py\",\n        \"--hives-dir\",\n        \"C:\\\\path\\\\to\\\\your\\\\hives\"\n      ]\n    }\n  }\n}\n```\n\n### 2. Restart Claude Desktop\nCompletely quit and restart Claude Desktop (right-click tray icon → Quit)\n\n### 3. Test It\nAsk Claude: **\"What registry hives are available?\"**\n\n## Demo: Claude Desktop in Action\n\n### Example 1: Listing Available Hives\n\n**Question:** \"What registry hives are currently available in the system?\"\n\n**Claude's Response:**\n\n![Available Hives](screenshots/hives_list.png)\n\nClaude automatically:\n- Called the `list_available_hives` MCP tool\n- Categorized 8 hives by type (SYSTEM, SOFTWARE, NTUSER, SAM, etc.)\n- Explained what each hive contains\n\n### Example 2: Finding Persistence Mechanisms\n\n**Question:** \"What are the persistence mechanisms?\"\n\n**Claude's Response:**\n\n![Persistence Analysis](screenshots/persistence.png)\n\nClaude automatically:\n- Called the `answer_forensic_question` tool\n- Ran both SOFTWARE and NTUSER persistence plugins\n- Found VMware Tools, Adobe ARM, McAfee components\n- Flagged suspicious entry: `svchost` in an unusual location\n- Organized results by system-wide vs user-specific\n\n### Example 3: Recent Software and Boot Time\n\n**Question:** \"What is the most recent software installed on the machine and when was it last booted?\"\n\n**Claude's Response:**\n\n![Recent Software](screenshots/installed_software.png)\n\nClaude automatically:\n- Ran `installed_programs_software` plugin\n- Ran `shutdown` plugin\n- Found Adobe Reader X (10.1.0) was most recent (Aug 28, 2011)\n- Showed full software timeline\n- Reported last shutdown: April 4, 2012\n\n## What You Can Ask\n\n**System Information:**\n- \"What is the hostname?\"\n- \"What is the timezone?\"\n- \"What is the Windows version?\"\n\n**Security & Forensics:**\n- \"What are the persistence mechanisms?\"\n- \"What user accounts exist?\"\n- \"What services are configured?\"\n\n**User Activity:**\n- \"What USB devices were connected?\"\n- \"What programs are installed?\"\n- \"What files were recently accessed?\"\n\n**Investigation:**\n- \"What programs have been executed?\"\n- \"Show me the network configuration\"\n- \"When was the system last booted?\"\n\n## How It Works\n\n1. **Auto-discovers** registry hives from a directory\n2. **Auto-loads** 75+ regipy plugins\n3. **Maps** natural language questions to the right plugins\n4. **Returns** structured forensic data to Claude\n\n## Files\n\n- `server.py` - Main MCP server (530+ lines)\n- `README.md` - This file\n- `claude_desktop_config.example.json` - Configuration template\n- `test_local.py` - Test the server without Claude Desktop\n- `screenshots/` - Demo screenshots from Claude Desktop\n\n## Requirements\n\n- Python 3.8+ (conda environment recommended)\n- regipy >= 6.0.0\n- mcp >= 1.0.0\n- Claude Desktop app\n- Windows registry hive files\n\n## Configuration\n\nThe server accepts the hives directory via:\n1. **Command-line argument** (recommended): `--hives-dir C:\\path\\to\\hives`\n2. **Environment variable**: `REGIPY_HIVE_DIRECTORY=C:\\path\\to\\hives`\n3. **MCP tool**: Call `set_hive_directory` from Claude at runtime\n\n## Supported Hive Types\n\n- **SYSTEM** - Computer name, timezone, services, USB devices, network config (28 plugins)\n- **SOFTWARE** - Windows version, installed programs, persistence (21 plugins)\n- **NTUSER** - User activity, recent docs, typed URLs, UserAssist (21 plugins)\n- **SAM** - User accounts, password hashes (2 plugins)\n- **SECURITY** - Domain SID (1 plugin)\n- **AMCACHE** - Application execution history (1 plugin)\n- **USRCLASS** - Shell bags (1 plugin)\n- **BCD** - Boot configuration (1 plugin)\n\nTotal: **75+ plugins** for comprehensive forensic analysis\n\n## Architecture\n\n```\nClaude Desktop\n    ↓ (MCP Protocol)\nRegipy MCP Server\n    ↓ (Auto-discovers)\nRegistry Hives → 75 Plugins → Structured Results\n    ↓\nClaude presents findings in natural language\n```\n\n## Troubleshooting\n\n### Server not connecting?\n1. Run `test_local.py` to verify the server works\n2. Check config file location: `%APPDATA%\\Claude\\claude_desktop_config.json`\n3. Ensure you completely quit Claude Desktop (not just closed window)\n4. See `docs/WINDOWS_SETUP.md` for detailed troubleshooting\n\n### No hives loaded?\n1. Verify hive directory exists and contains hive files\n2. Check paths use double backslashes in JSON: `C:\\\\path\\\\to\\\\hives`\n3. Make sure hive files aren't corrupted\n\n## License\n\nUses the regipy library for registry parsing. Designed for authorized forensic analysis only.\n\n## Credits\n\n- Built on [regipy](https://github.com/mkorman90/regipy) by Martin Korman\n- Uses [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic\n- Designed for [Claude Desktop](https://claude.ai/download)\n"
  },
  {
    "path": "regipy_mcp_server/claude_desktop_config.example.json",
    "content": "{\n  \"mcpServers\": {\n    \"regipy\": {\n      \"command\": \"C:\\\\Users\\\\marti\\\\anaconda3\\\\envs\\\\regipy\\\\python.exe\",\n      \"args\": [\n        \"c:\\\\Users\\\\marti\\\\Documents\\\\GitHub\\\\regipy\\\\regipy_mcp_server\\\\server.py\",\n        \"--hives-dir\",\n        \"C:\\\\Users\\\\marti\\\\Downloads\\\\hives\"\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "regipy_mcp_server/server.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nRegipy MCP Server - Windows Registry Analysis for Claude Desktop\n\nThis server enables Claude to analyze Windows registry hives and answer\nforensic questions about system configuration, user activity, and persistence.\n\"\"\"\n\nimport logging\nimport os\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom mcp.server.fastmcp import FastMCP\n\n# Import all plugins so they auto-register via __init_subclass__\nimport regipy.plugins  # noqa\nfrom regipy.plugins.plugin import PLUGINS\nfrom regipy.plugins.utils import run_relevant_plugins\nfrom regipy.registry import RegistryHive\n\n# Configure logging\nlogging.basicConfig(level=logging.INFO)\nlogger = logging.getLogger(__name__)\n\n# Initialize MCP server\nmcp = FastMCP(\"regipy-forensics\")\n\n# Global state for loaded hives\n_loaded_hives: dict[str, RegistryHive] = {}\n_hive_directory: Optional[str] = None\n\n\ndef _load_hives_from_directory(directory: str) -> dict[str, RegistryHive]:\n    \"\"\"\n    Auto-discover and load all registry hives from a directory.\n\n    Skips transaction logs (.log, .log1, .log2) and only loads actual hive files.\n    Returns a dict mapping file paths to RegistryHive objects.\n    \"\"\"\n    hives = {}\n    directory_path = Path(directory)\n\n    if not directory_path.exists():\n        logger.error(f\"Hive directory does not exist: {directory}\")\n        return hives\n\n    # Common registry hive file patterns\n    skip_extensions = {\".log\", \".log1\", \".log2\", \".sav\", \".regtrans-ms\", \".blf\", \".tmp\"}\n\n    for file_path in directory_path.iterdir():\n        if not file_path.is_file():\n            continue\n\n        # Skip transaction logs and backup files\n        if file_path.suffix.lower() in skip_extensions:\n            continue\n\n        try:\n            logger.info(f\"Attempting to load hive: {file_path}\")\n            hive = RegistryHive(str(file_path))\n            hives[str(file_path)] = hive\n            logger.info(f\"Successfully loaded {file_path.name} as {hive.hive_type}\")\n        except Exception as e:\n            logger.warning(f\"Failed to load {file_path}: {e}\")\n            continue\n\n    return hives\n\n\ndef _initialize_hives(hive_dir: Optional[str] = None):\n    \"\"\"\n    Initialize hives from the specified directory or environment variable.\n\n    Priority:\n    1. Command-line argument (hive_dir parameter)\n    2. REGIPY_HIVE_DIRECTORY environment variable\n    3. If neither is set, hives must be loaded manually via set_hive_directory tool\n    \"\"\"\n    global _loaded_hives, _hive_directory\n\n    # Try parameter first, then environment variable\n    _hive_directory = hive_dir or os.getenv(\"REGIPY_HIVE_DIRECTORY\")\n\n    if not _hive_directory:\n        logger.warning(\"No hive directory specified. Use set_hive_directory tool or pass --hives-dir argument.\")\n        return\n\n    logger.info(f\"Loading hives from: {_hive_directory}\")\n    _loaded_hives = _load_hives_from_directory(_hive_directory)\n    logger.info(f\"Loaded {len(_loaded_hives)} hive(s)\")\n\n\ndef _get_hives_by_type(hive_type: str) -> list[tuple[str, RegistryHive]]:\n    \"\"\"Get all loaded hives of a specific type.\"\"\"\n    return [(path, hive) for path, hive in _loaded_hives.items() if hive.hive_type == hive_type]\n\n\ndef _serialize_datetime(obj):\n    \"\"\"Convert datetime objects to ISO format strings.\"\"\"\n    if isinstance(obj, datetime):\n        return obj.isoformat()\n    return obj\n\n\ndef _serialize_plugin_results(results):\n    \"\"\"Recursively serialize plugin results, converting datetimes to strings.\"\"\"\n    if isinstance(results, dict):\n        return {k: _serialize_plugin_results(v) for k, v in results.items()}\n    elif isinstance(results, list):\n        return [_serialize_plugin_results(item) for item in results]\n    elif isinstance(results, datetime):\n        return results.isoformat()\n    else:\n        return results\n\n\n@mcp.tool()\ndef set_hive_directory(directory: str) -> str:\n    \"\"\"\n    Set the directory containing registry hives and load them.\n\n    Args:\n        directory: Path to directory containing registry hive files\n\n    Returns:\n        Summary of loaded hives\n    \"\"\"\n    global _loaded_hives, _hive_directory\n\n    _hive_directory = directory\n    _loaded_hives = _load_hives_from_directory(directory)\n\n    if not _loaded_hives:\n        return f\"No valid registry hives found in {directory}\"\n\n    # Group by hive type\n    by_type = {}\n    for path, hive in _loaded_hives.items():\n        hive_type = hive.hive_type or \"unknown\"\n        if hive_type not in by_type:\n            by_type[hive_type] = []\n        by_type[hive_type].append(Path(path).name)\n\n    result = [f\"Loaded {len(_loaded_hives)} hive(s) from {directory}:\\n\"]\n    for hive_type, files in sorted(by_type.items()):\n        result.append(f\"\\n{hive_type}:\")\n        for file in files:\n            result.append(f\"  - {file}\")\n\n    return \"\\n\".join(result)\n\n\n@mcp.tool()\ndef list_available_hives() -> str:\n    \"\"\"\n    List all currently loaded registry hives and their types.\n\n    Returns:\n        Summary of available hives grouped by type\n    \"\"\"\n    if not _loaded_hives:\n        return \"No hives loaded. Use set_hive_directory to load hives.\"\n\n    by_type = {}\n    for path, hive in _loaded_hives.items():\n        hive_type = hive.hive_type or \"unknown\"\n        if hive_type not in by_type:\n            by_type[hive_type] = []\n        by_type[hive_type].append(\n            {\"filename\": Path(path).name, \"path\": path, \"root_key\": hive.root.name if hive.root else \"N/A\"}\n        )\n\n    result = [f\"Available hives ({len(_loaded_hives)} total):\\n\"]\n    for hive_type, hives in sorted(by_type.items()):\n        result.append(f\"\\n{hive_type} ({len(hives)} file(s)):\")\n        for hive_info in hives:\n            result.append(f\"  - {hive_info['filename']}\")\n\n    return \"\\n\".join(result)\n\n\n@mcp.tool()\ndef list_available_plugins(hive_type: Optional[str] = None) -> str:\n    \"\"\"\n    List all available regipy plugins, optionally filtered by hive type.\n    Shows which plugins can run based on currently loaded hives.\n\n    Args:\n        hive_type: Optional hive type to filter by (e.g., 'SYSTEM', 'SOFTWARE', 'NTUSER')\n\n    Returns:\n        List of plugins with descriptions and compatibility info\n    \"\"\"\n    # Get loaded hive types\n    loaded_types = {hive.hive_type for hive in _loaded_hives.values()}\n\n    # Group plugins by hive type\n    plugins_by_type = {}\n    for plugin_class in PLUGINS:\n        compat_hive = plugin_class.COMPATIBLE_HIVE or \"unknown\"\n\n        # Filter by requested hive type if specified\n        if hive_type and compat_hive != hive_type:\n            continue\n\n        if compat_hive not in plugins_by_type:\n            plugins_by_type[compat_hive] = []\n\n        can_run = compat_hive in loaded_types\n        plugins_by_type[compat_hive].append(\n            {\"name\": plugin_class.NAME, \"description\": plugin_class.DESCRIPTION or \"No description\", \"can_run\": can_run}\n        )\n\n    # Format output\n    result = []\n    total_plugins = sum(len(plugins) for plugins in plugins_by_type.values())\n    runnable = sum(1 for plugins in plugins_by_type.values() for p in plugins if p[\"can_run\"])\n\n    result.append(f\"Total plugins: {total_plugins} ({runnable} can run with loaded hives)\\n\")\n\n    for hive_type_name, plugins in sorted(plugins_by_type.items()):\n        can_run_count = sum(1 for p in plugins if p[\"can_run\"])\n        status = \"✓ AVAILABLE\" if can_run_count > 0 else \"✗ No hive loaded\"\n        result.append(f\"\\n{hive_type_name} ({len(plugins)} plugins) - {status}:\")\n\n        for plugin in sorted(plugins, key=lambda x: x[\"name\"]):\n            status_icon = \"✓\" if plugin[\"can_run\"] else \"✗\"\n            result.append(f\"  {status_icon} {plugin['name']}\")\n            result.append(f\"      {plugin['description']}\")\n\n    return \"\\n\".join(result)\n\n\n@mcp.tool()\ndef run_plugin(plugin_name: str) -> dict:\n    \"\"\"\n    Run a specific regipy plugin on the appropriate hive.\n\n    The plugin will automatically select the correct hive based on its\n    COMPATIBLE_HIVE setting. For example, 'computer_name' will run on\n    the SYSTEM hive, 'typed_urls' will run on NTUSER hives.\n\n    Args:\n        plugin_name: Name of the plugin to run (e.g., 'computer_name', 'timezone_data')\n\n    Returns:\n        Plugin results as a dictionary\n    \"\"\"\n    if not _loaded_hives:\n        return {\"error\": \"No hives loaded. Use set_hive_directory first.\"}\n\n    # Find the plugin class\n    plugin_class = None\n    for p in PLUGINS:\n        if plugin_name == p.NAME:\n            plugin_class = p\n            break\n\n    if not plugin_class:\n        available = [p.NAME for p in PLUGINS]\n        return {\"error\": f\"Plugin '{plugin_name}' not found\", \"available_plugins\": sorted(available)}\n\n    # Get hives of the compatible type\n    compatible_hives = _get_hives_by_type(plugin_class.COMPATIBLE_HIVE)\n\n    if not compatible_hives:\n        return {\"error\": f\"No {plugin_class.COMPATIBLE_HIVE} hive loaded\", \"required_hive_type\": plugin_class.COMPATIBLE_HIVE}\n\n    # Run plugin on all compatible hives\n    all_results = {}\n    for path, hive in compatible_hives:\n        try:\n            plugin = plugin_class(hive, as_json=True)\n            if plugin.can_run():\n                plugin.run()\n                results = _serialize_plugin_results(plugin.entries)\n                all_results[Path(path).name] = results\n        except Exception as e:\n            logger.error(f\"Error running {plugin_name} on {path}: {e}\")\n            all_results[Path(path).name] = {\"error\": str(e)}\n\n    return {\n        \"plugin\": plugin_name,\n        \"description\": plugin_class.DESCRIPTION,\n        \"hive_type\": plugin_class.COMPATIBLE_HIVE,\n        \"results\": all_results,\n    }\n\n\n@mcp.tool()\ndef run_all_plugins_for_hive(hive_type: str) -> dict:\n    \"\"\"\n    Run all compatible plugins for a specific hive type.\n\n    Args:\n        hive_type: Type of hive (e.g., 'SYSTEM', 'SOFTWARE', 'NTUSER', 'SAM')\n\n    Returns:\n        Combined results from all plugins\n    \"\"\"\n    if not _loaded_hives:\n        return {\"error\": \"No hives loaded. Use set_hive_directory first.\"}\n\n    compatible_hives = _get_hives_by_type(hive_type)\n\n    if not compatible_hives:\n        available_types = {h.hive_type for h in _loaded_hives.values()}\n        return {\"error\": f\"No {hive_type} hive loaded\", \"available_types\": sorted(available_types)}\n\n    # Run all plugins on all compatible hives\n    all_results = {}\n    for path, hive in compatible_hives:\n        try:\n            results = run_relevant_plugins(hive, as_json=True)\n            serialized_results = _serialize_plugin_results(results)\n            all_results[Path(path).name] = serialized_results\n        except Exception as e:\n            logger.error(f\"Error running plugins on {path}: {e}\")\n            all_results[Path(path).name] = {\"error\": str(e)}\n\n    return {\"hive_type\": hive_type, \"files_analyzed\": len(all_results), \"results\": all_results}\n\n\n@mcp.tool()\ndef list_relevant_plugins(question: str) -> dict:\n    \"\"\"\n    List plugins relevant to a forensic question.\n\n    Returns a list of available plugins with their descriptions that could help\n    answer the given question. Use this to discover which plugins to run, then\n    call run_plugin() for each one.\n\n    Args:\n        question: Natural language forensic question\n\n    Returns:\n        Dict with available plugins and their descriptions\n    \"\"\"\n    if not _loaded_hives:\n        return {\"error\": \"No hives loaded. Use set_hive_directory first.\"}\n\n    # Get loaded hive types\n    loaded_types = {hive.hive_type for hive in _loaded_hives.values()}\n\n    # Build list of available plugins with their descriptions\n    available_plugins = []\n    for plugin_class in PLUGINS:\n        if plugin_class.COMPATIBLE_HIVE not in loaded_types:\n            continue\n\n        available_plugins.append(\n            {\n                \"name\": plugin_class.NAME,\n                \"description\": plugin_class.DESCRIPTION or \"No description\",\n                \"hive_type\": plugin_class.COMPATIBLE_HIVE,\n            }\n        )\n\n    return {\n        \"question\": question,\n        \"available_plugins\": available_plugins,\n        \"total_count\": len(available_plugins),\n        \"instruction\": \"Based on the question, select relevant plugin(s) and call run_plugin() for each one.\",\n    }\n\n\n@mcp.tool()\ndef get_registry_key(key_path: str, hive_type: Optional[str] = None) -> dict:\n    \"\"\"\n    Get a specific registry key and its values.\n\n    Args:\n        key_path: Path to the registry key (e.g., 'ControlSet001\\\\Control\\\\TimeZoneInformation')\n        hive_type: Optional hive type to search in. If not specified, searches all loaded hives.\n\n    Returns:\n        Key information including values and subkeys\n    \"\"\"\n    if not _loaded_hives:\n        return {\"error\": \"No hives loaded. Use set_hive_directory first.\"}\n\n    # Determine which hives to search\n    if hive_type:\n        hives_to_search = _get_hives_by_type(hive_type)\n    else:\n        hives_to_search = list(_loaded_hives.items())\n\n    if not hives_to_search:\n        return {\"error\": \"No hives available for search\"}\n\n    results = {}\n    for path, hive in hives_to_search:\n        try:\n            subkey = hive.get_key(key_path)\n            if subkey:\n                results[Path(path).name] = {\n                    \"found\": True,\n                    \"path\": subkey.path,\n                    \"timestamp\": _serialize_datetime(subkey.timestamp),\n                    \"values\": [{\"name\": v.name, \"value\": v.value, \"type\": v.value_type} for v in (subkey.values or [])],\n                    \"subkey_count\": len(list(subkey.iter_subkeys())) if hasattr(subkey, \"iter_subkeys\") else 0,\n                }\n        except Exception as e:\n            results[Path(path).name] = {\"found\": False, \"error\": str(e)}\n\n    if not any(r.get(\"found\") for r in results.values()):\n        return {\"key_path\": key_path, \"found\": False, \"searched_hives\": [Path(p).name for p, _ in hives_to_search]}\n\n    return {\"key_path\": key_path, \"results\": results}\n\n\nif __name__ == \"__main__\":\n    import argparse\n\n    # Parse command-line arguments\n    parser = argparse.ArgumentParser(description=\"Regipy MCP Server - Windows Registry Analysis\")\n    parser.add_argument(\n        \"--hives-dir\", type=str, help=\"Directory containing registry hive files (default: from REGIPY_HIVE_DIRECTORY env var)\"\n    )\n    args = parser.parse_args()\n\n    # Initialize hives with command-line argument or environment variable\n    _initialize_hives(args.hives_dir)\n\n    # Run the MCP server\n    mcp.run()\nelse:\n    # When imported as a module, initialize from environment variable\n    _initialize_hives()\n"
  },
  {
    "path": "regipy_mcp_server/test_local.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nLocal test script for Regipy MCP Server\nTests the server functions directly without Claude Desktop\n\"\"\"\n\nimport io\nimport os\nimport sys\n\n# Fix Windows console encoding\nif sys.platform == \"win32\":\n    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding=\"utf-8\")\n\n# Set the hive directory\nos.environ[\"REGIPY_HIVE_DIRECTORY\"] = r\"C:\\Users\\marti\\Downloads\\hives\"\n\n# Add server to path\nsys.path.insert(0, os.path.dirname(__file__))\n\nprint(\"=\" * 70)\nprint(\"Regipy MCP Server - Local Test\")\nprint(\"=\" * 70)\n\n# Import after setting environment variable\nfrom server import (  # noqa: E402\n    PLUGINS,\n    _initialize_hives,\n    _loaded_hives,\n    answer_forensic_question,\n    list_available_hives,\n    list_available_plugins,\n    run_plugin,\n)\n\nprint(\"\\n✓ Server imported successfully\")\nprint(f\"✓ Discovered {len(PLUGINS)} plugins\")\n\n# Initialize hives\nprint(\"\\n📂 Loading hives from: C:\\\\Users\\\\marti\\\\Downloads\\\\hives\")\n_initialize_hives()\n\nif _loaded_hives:\n    print(f\"✓ Loaded {len(_loaded_hives)} hive file(s)\")\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TEST 1: List Available Hives\")\n    print(\"=\" * 70)\n    result = list_available_hives()\n    print(result)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TEST 2: List Available Plugins (SYSTEM hive)\")\n    print(\"=\" * 70)\n    result = list_available_plugins(hive_type=\"SYSTEM\")\n    print(result[:500] + \"...\" if len(result) > 500 else result)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TEST 3: Run Computer Name Plugin\")\n    print(\"=\" * 70)\n    result = run_plugin(\"computer_name\")\n    print(result)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TEST 4: Answer Forensic Question - Hostname\")\n    print(\"=\" * 70)\n    result = answer_forensic_question(\"What is the hostname?\")\n    print(result)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"TEST 5: Answer Forensic Question - Timezone\")\n    print(\"=\" * 70)\n    result = answer_forensic_question(\"What is the timezone?\")\n    print(result)\n\n    print(\"\\n\" + \"=\" * 70)\n    print(\"✅ All tests completed!\")\n    print(\"=\" * 70)\n    print(\"\\nNext steps:\")\n    print(\"1. Configure Claude Desktop with the config from claude_desktop_config.example.json\")\n    print(\"2. Restart Claude Desktop\")\n    print(\"3. Ask Claude: 'What registry hives are available?'\")\n\nelse:\n    print(\"\\n❌ No hives loaded!\")\n    print(\"\\nTroubleshooting:\")\n    print(\"1. Check that C:\\\\Users\\\\marti\\\\Downloads\\\\hives exists\")\n    print(\"2. Verify it contains registry hive files (SYSTEM, SOFTWARE, NTUSER.DAT, etc.)\")\n    print(\"3. Make sure files aren't corrupted\")\n"
  },
  {
    "path": "regipy_tests/__init__.py",
    "content": "# flake8: noqa\nfrom .validation.validation_tests import shimcache_validation\nfrom .validation.validation_tests import ntuser_persistence_validation\nfrom .validation.validation_tests import software_persistence_validation\nfrom .validation.validation_tests import ntuser_userassist_validation\nfrom .validation.validation_tests import amcache_validation\nfrom .validation.validation_tests import bam_validation\nfrom .validation.validation_tests import word_wheel_query_ntuser_validation\nfrom .validation.validation_tests import computer_name_plugin_validation\nfrom .validation.validation_tests import uac_status_plugin_validation\nfrom .validation.validation_tests import software_classes_installer_plugin_validation\nfrom .validation.validation_tests import ntuser_classes_installer_plugin_validation\nfrom .validation.validation_tests import ras_tracing_plugin_validation\nfrom .validation.validation_tests import installed_programs_software_plugin_validation\nfrom .validation.validation_tests import last_logon_plugin_validation\nfrom .validation.validation_tests import typed_urls_plugin_validation\nfrom .validation.validation_tests import profile_list_plugin_validation\nfrom .validation.validation_tests import print_demon_plugin_validation\nfrom .validation.validation_tests import services_plugin_validation\nfrom .validation.validation_tests import local_sid_plugin_validation\nfrom .validation.validation_tests import boot_key_plugin_validation\nfrom .validation.validation_tests import host_domain_name_plugin_validation\nfrom .validation.validation_tests import domain_sid_plugin_validation\nfrom .validation.validation_tests import boot_entry_list_plugin_validation\nfrom .validation.validation_tests import wdigest_plugin_validation\nfrom .validation.validation_tests import winrar_plugin_validation\nfrom .validation.validation_tests import network_drives_plugin_validation\nfrom .validation.validation_tests import winscp_saved_sessions_plugin_validation\nfrom .validation.validation_tests import usbstor_plugin_validation\nfrom .validation.validation_tests import typed_paths_plugin_validation\nfrom .validation.validation_tests import shell_bag_ntuser_plugin_validation\nfrom .validation.validation_tests import shell_bag_usrclass_plugin_validation\nfrom .validation.validation_tests import network_data_plugin_validation\nfrom .validation.validation_tests import active_control_set_validation\nfrom .validation.validation_tests import backuprestore_plugin_validation\nfrom .validation.validation_tests import codepage_validation\nfrom .validation.validation_tests import crash_dump_validation\nfrom .validation.validation_tests import diag_sr_validation\nfrom .validation.validation_tests import disable_last_access_validation\nfrom .validation.validation_tests import disablesr_plugin_validation\nfrom .validation.validation_tests import image_file_execution_options_validation\nfrom .validation.validation_tests import installed_programs_ntuser_validation\nfrom .validation.validation_tests import previous_winver_plugin_validation\nfrom .validation.validation_tests import processor_architecture_validation\nfrom .validation.validation_tests import routes_validation\nfrom .validation.validation_tests import safeboot_configuration_validation\nfrom .validation.validation_tests import shutdown_validation\nfrom .validation.validation_tests import susclient_plugin_validation\nfrom .validation.validation_tests import spp_clients_plugin_validation\nfrom .validation.validation_tests import shutdown_validation\nfrom .validation.validation_tests import terminal_services_history_validation\nfrom .validation.validation_tests import timezone_data_validation\nfrom .validation.validation_tests import timezone_data2_validation\nfrom .validation.validation_tests import winrar_plugin_validation\nfrom .validation.validation_tests import winver_plugin_validation\nfrom .validation.validation_tests import wsl_plugin_validation\nfrom .validation.validation_tests import usb_devices_plugin_validation\nfrom .validation.validation_tests import mounted_devices_plugin_validation\nfrom .validation.validation_tests import pagefile_plugin_validation\nfrom .validation.validation_tests import lsa_packages_plugin_validation\nfrom .validation.validation_tests import app_paths_plugin_validation\nfrom .validation.validation_tests import networklist_plugin_validation\nfrom .validation.validation_tests import execution_policy_plugin_validation\nfrom .validation.validation_tests import windows_defender_plugin_validation\nfrom .validation.validation_tests import samparse_plugin_validation\n"
  },
  {
    "path": "regipy_tests/cli_tests.py",
    "content": "import json\nfrom tempfile import mktemp\n\nfrom click.testing import CliRunner\n\nfrom regipy.cli import parse_header, registry_dump, run_plugins\n\n\ndef test_cli_registry_parse_header(ntuser_hive):\n    runner = CliRunner()\n    result = runner.invoke(parse_header, [ntuser_hive])\n    assert result.exit_code == 0\n    assert len(result.output.splitlines()) == 27\n\n\ndef test_cli_registry_dump(ntuser_hive):\n    runner = CliRunner()\n\n    start_date = \"2012-04-03T00:00:00.000000\"\n    end_date = \"2012-04-03T23:59:59.999999\"\n\n    output_file_path = mktemp()\n    result = runner.invoke(\n        registry_dump,\n        [ntuser_hive, \"-d\", \"-s\", start_date, \"-e\", end_date, \"-o\", output_file_path],\n    )\n    assert result.exit_code == 0\n\n    with open(output_file_path) as f:\n        output = f.readlines()\n\n    assert json.loads(output[0]) == {\n        \"subkey_name\": \".Default\",\n        \"path\": \"\\\\AppEvents\\\\EventLabels\\\\.Default\",\n        \"timestamp\": \"2012-04-03T21:19:54.733216+00:00\",\n        \"values_count\": 2,\n        \"values\": [],\n        \"actual_path\": None,\n    }\n\n    assert json.loads(output[-1]) == {\n        \"subkey_name\": \"System\",\n        \"path\": \"\\\\System\",\n        \"timestamp\": \"2012-04-03T21:19:54.847482+00:00\",\n        \"values_count\": 0,\n        \"values\": [],\n        \"actual_path\": None,\n    }\n\n    # The output file contains one extra line, because of line breaks\n    assert len(output) == 1490\n    assert result.output.strip().endswith(\"(1489 subkeys enumerated)\")\n\n\ndef test_cli_run_plugins(ntuser_hive):\n    runner = CliRunner()\n\n    output_file_path = mktemp()\n    result = runner.invoke(run_plugins, [ntuser_hive, \"-o\", output_file_path])\n    assert result.exit_code == 0\n\n    assert result.output.strip() == \"Loaded 75 plugins\\nFinished: 13/75 plugins matched the hive type\"\n\n    with open(output_file_path) as f:\n        output = json.loads(f.read())\n\n    assert set(output.keys()) == {\n        \"installed_programs_ntuser\",\n        \"network_drives_plugin\",\n        \"ntuser_classes_installer\",\n        \"ntuser_persistence\",\n        \"ntuser_shellbag_plugin\",\n        \"terminal_services_history\",\n        \"typed_paths\",\n        \"typed_urls\",\n        \"user_assist\",\n        \"winrar_plugin\",\n        \"winscp_saved_sessions\",\n        \"word_wheel_query\",\n        \"wsl\",\n    }\n"
  },
  {
    "path": "regipy_tests/conftest.py",
    "content": "import lzma\nimport os\nfrom pathlib import Path\nfrom tempfile import mktemp\n\nimport pytest\n\n\ndef extract_lzma(path):\n    tempfile_path = mktemp()\n    with open(tempfile_path, \"wb\") as tmp, lzma.open(path) as f:\n        tmp.write(f.read())\n    return tempfile_path\n\n\n@pytest.fixture()\ndef temp_output_file():\n    tempfile_path = mktemp()\n    yield tempfile_path\n    os.remove(tempfile_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef test_data_dir():\n    return str(Path(__file__).parent.joinpath(\"data\"))\n\n\n@pytest.fixture(scope=\"module\")\ndef ntuser_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"NTUSER.DAT.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef software_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SOFTWARE.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef system_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef sam_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SAM.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef security_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SECURITY.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef amcache_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"amcache.hve.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef bcd_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"BCD.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef second_hive_path(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"NTUSER_modified.DAT.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef transaction_ntuser(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"transactions_NTUSER.DAT.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef transaction_log(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"transactions_ntuser.dat.log1.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef transaction_system(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM_B.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef system_tr_log_1(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM_B.LOG1.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef system_tr_log_2(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM_B.LOG2.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef transaction_usrclass(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"UsrClass.dat.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef usrclass_tr_log_1(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"UsrClass.dat.LOG1.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef usrclass_tr_log_2(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"UsrClass.dat.LOG2.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef ntuser_software_partial(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"ntuser_software_partial.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef corrupted_system_hive(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"corrupted_system_hive.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef system_devprop(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM_2.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef ntuser_hive_2(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"NTUSER_with_winscp.DAT.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef shellbags_ntuser(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"NTUSER_BAGMRU.DAT.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n\n\n@pytest.fixture(scope=\"module\")\ndef system_hive_with_filetime(test_data_dir):\n    temp_path = extract_lzma(os.path.join(test_data_dir, \"SYSTEM_WIN_10_1709.xz\"))\n    yield temp_path\n    os.remove(temp_path)\n"
  },
  {
    "path": "regipy_tests/profiling.py",
    "content": "# flake8: noqa\nimport cProfile\nimport io\nimport lzma\nimport os\nimport pstats\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom pstats import SortKey\nfrom tempfile import mktemp\n\nimport logging\n\nfrom regipy.registry import RegistryHive\n\nlogger = logging.getLogger(__name__)\n\n\n@contextmanager\ndef profiling():\n    pr = cProfile.Profile()\n    pr.enable()\n    yield\n    pr.disable()\n    s = io.StringIO()\n    sortby = SortKey.CUMULATIVE\n    ps = pstats.Stats(pr, stream=s).sort_stats(sortby)\n    ps.print_stats()\n    print(s.getvalue())\n\n\n@contextmanager\ndef get_file_from_tests(file_name):\n    path = str(Path(__file__).parent.joinpath(\"data\").joinpath(file_name))\n    tempfile_path = mktemp()\n    with open(tempfile_path, \"wb\") as tmp:\n        with lzma.open(path) as f:\n            tmp.write(f.read())\n    yield tempfile_path\n    os.remove(tempfile_path)\n\n\nregistry_path = \"SYSTEM_2.xz\"\nprint(f\"Iterating over all subkeys in {registry_path}\")\nwith profiling():\n    with get_file_from_tests(registry_path) as reg:\n        registry_hive = RegistryHive(reg)\n        keys = [x for x in registry_hive.recurse_subkeys(fetch_values=False)]\nprint(f\"Done.\")\n\n\"\"\"\n\n=== Fetching subkey values:\n\nIterating over all subkeys in SYSTEM_2.xz\n         6681282 function calls (6548808 primitive calls) in 21.406 seconds\n\n   Ordered by: cumulative time\n\n   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n        1    0.009    0.009   21.440   21.440 /Users/martin/Projects/regipy/regipy_tests/profiling.py:47(<listcomp>)\n115672/20016    0.326    0.000   21.431    0.001 /Users/martin/Projects/regipy/regipy/registry.py:126(recurse_subkeys)\n    66844    1.067    0.000   19.524    0.000 /Users/martin/Projects/regipy/regipy/registry.py:413(iter_values)\n    49779    0.280    0.000   10.845    0.000 /Users/martin/Projects/regipy/regipy/registry.py:378(read_value)\n  1101844   10.603    0.000   10.603    0.000 {method 'read' of '_io.BytesIO' objects}\n    30464    0.357    0.000    5.337    0.000 /Users/martin/Projects/regipy/regipy/utils.py:149(try_decode_binary)\n   116294    3.587    0.000    5.021    0.000 {method 'decode' of 'bytes' objects}\n   150968    0.252    0.000    2.785    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:290(parse_stream)\n187671/150968    0.117    0.000    2.359    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:311(_parsereport)\n    74151    0.051    0.000    1.779    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:786(_parse)\n    42243    0.024    0.000    1.434    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/encodings/utf_16_le.py:15(decode)\n    24363    0.039    0.000    1.413    0.000 /Users/martin/Projects/regipy/regipy/registry.py:322(iter_subkeys)\n    42243    1.410    0.000    1.410    0.000 {built-in method _codecs.utf_16_le_decode}\n    24367    0.116    0.000    1.333    0.000 /Users/martin/Projects/regipy/regipy/registry.py:350(_parse_subkeys)\n    49779    0.026    0.000    0.943    0.000 :79(parseall)\n    49779    0.579    0.000    0.917    0.000 :26(parse_struct_1)\n    20015    0.127    0.000    0.837    0.000 /Users/martin/Projects/regipy/regipy/registry.py:292(__init__)\n    20015    0.012    0.000    0.584    0.000 :124(parseall)\n    20015    0.378    0.000    0.572    0.000 :23(parse_struct_1)\n    \n\n=== Without fetching subkeys:\n\nIterating over all subkeys in SYSTEM_2.xz\n         2068855 function calls (1973084 primitive calls) in 5.288 seconds\n\n   Ordered by: cumulative time\n\n   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n        1    0.037    0.037    4.969    4.969 /Users/martin/Projects/regipy/regipy_tests/profiling.py:47(<listcomp>)\n115672/20016    0.462    0.000    4.932    0.000 /Users/martin/Projects/regipy/regipy/registry.py:126(recurse_subkeys)\n    24363    0.097    0.000    4.104    0.000 /Users/martin/Projects/regipy/regipy/registry.py:324(iter_subkeys)\n    24367    0.312    0.000    3.900    0.000 /Users/martin/Projects/regipy/regipy/registry.py:352(_parse_subkeys)\n    48737    0.322    0.000    3.121    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:290(parse_stream)\n    48737    0.119    0.000    2.640    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:311(_parsereport)\n    20015    0.343    0.000    2.370    0.000 /Users/martin/Projects/regipy/regipy/registry.py:294(__init__)\n    24372    0.038    0.000    2.334    0.000 /opt/anaconda3/envs/regipy/lib/python3.9/site-packages/construct/core.py:786(_parse)\n    20015    0.033    0.000    1.683    0.000 :124(parseall)\n    20015    1.123    0.000    1.650    0.000 :23(parse_struct_1)\n     4353    0.045    0.000    0.610    0.000 :76(parseall)\n\n\"\"\"\n"
  },
  {
    "path": "regipy_tests/test_packaging.py",
    "content": "\"\"\"\nTests to verify package configuration and prevent packaging issues.\n\nThese tests ensure that all Python packages are correctly declared in\npyproject.toml, preventing ImportError when installed from PyPI.\n\"\"\"\n\nimport pathlib\nimport sys\n\nif sys.version_info >= (3, 11):\n    import tomllib\nelse:\n    import tomli as tomllib\n\n\ndef test_all_packages_included_in_pyproject():\n    \"\"\"\n    Verify all Python packages with __init__.py are listed in pyproject.toml.\n\n    This catches the case where a new subpackage is added but not included\n    in the packages list, which would cause ImportError when installed from PyPI.\n    \"\"\"\n    # Find all packages (directories with __init__.py)\n    regipy_root = pathlib.Path(__file__).parent.parent / \"regipy\"\n    actual_packages = set()\n    for init_file in regipy_root.rglob(\"__init__.py\"):\n        package_dir = init_file.parent\n        # Convert path to dotted package name\n        relative = package_dir.relative_to(regipy_root.parent)\n        package_name = str(relative).replace(\"/\", \".\").replace(\"\\\\\", \".\")\n        actual_packages.add(package_name)\n\n    # Read packages from pyproject.toml\n    pyproject_path = pathlib.Path(__file__).parent.parent / \"pyproject.toml\"\n    with open(pyproject_path, \"rb\") as f:\n        pyproject = tomllib.load(f)\n\n    declared_packages = set(pyproject[\"tool\"][\"setuptools\"][\"packages\"])\n\n    # Check for missing packages\n    missing = actual_packages - declared_packages\n    assert not missing, f\"Packages missing from pyproject.toml: {missing}\"\n\n    # Check for extra packages (declared but don't exist)\n    extra = declared_packages - actual_packages\n    assert not extra, f\"Packages in pyproject.toml but don't exist: {extra}\"\n\n\ndef test_all_packages_importable():\n    \"\"\"Test that all declared packages can be imported.\"\"\"\n    import importlib\n\n    pyproject_path = pathlib.Path(__file__).parent.parent / \"pyproject.toml\"\n    with open(pyproject_path, \"rb\") as f:\n        pyproject = tomllib.load(f)\n\n    packages = pyproject[\"tool\"][\"setuptools\"][\"packages\"]\n\n    for package in packages:\n        try:\n            importlib.import_module(package)\n        except ImportError as e:\n            raise AssertionError(f\"Failed to import {package}: {e}\") from e\n"
  },
  {
    "path": "regipy_tests/test_utils.py",
    "content": "\"\"\"Unit tests for regipy.plugins.utils module\"\"\"\n\nfrom unittest.mock import MagicMock\n\nfrom regipy.plugins.utils import extract_values\n\n\ndef _make_mock_value(name, value):\n    \"\"\"Helper to create a mock registry value\"\"\"\n    mock = MagicMock()\n    mock.name = name\n    mock.value = value\n    return mock\n\n\ndef _make_mock_key(values):\n    \"\"\"Helper to create a mock registry key with values\"\"\"\n    mock = MagicMock()\n    mock.iter_values.return_value = [_make_mock_value(name, val) for name, val in values]\n    return mock\n\n\nclass TestExtractValues:\n    \"\"\"Tests for extract_values utility function\"\"\"\n\n    def test_simple_rename(self):\n        \"\"\"Test simple string rename mapping\"\"\"\n        key = _make_mock_key([(\"ProfileName\", \"MyNetwork\")])\n        entry = {}\n\n        extract_values(key, {\"ProfileName\": \"profile_name\"}, entry)\n\n        assert entry == {\"profile_name\": \"MyNetwork\"}\n\n    def test_multiple_simple_renames(self):\n        \"\"\"Test multiple string rename mappings\"\"\"\n        key = _make_mock_key(\n            [\n                (\"ProfileName\", \"MyNetwork\"),\n                (\"Description\", \"Home WiFi\"),\n            ]\n        )\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"ProfileName\": \"profile_name\",\n                \"Description\": \"description\",\n            },\n            entry,\n        )\n\n        assert entry == {\n            \"profile_name\": \"MyNetwork\",\n            \"description\": \"Home WiFi\",\n        }\n\n    def test_callable_converter(self):\n        \"\"\"Test tuple with callable converter\"\"\"\n        key = _make_mock_key([(\"Enabled\", 1)])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"Enabled\": (\"enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        assert entry == {\"enabled\": True}\n\n    def test_callable_converter_false(self):\n        \"\"\"Test callable converter returning False\"\"\"\n        key = _make_mock_key([(\"Enabled\", 0)])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"Enabled\": (\"enabled\", lambda v: v == 1),\n            },\n            entry,\n        )\n\n        assert entry == {\"enabled\": False}\n\n    def test_lookup_converter(self):\n        \"\"\"Test callable that performs lookup\"\"\"\n        categories = {0: \"Public\", 1: \"Private\", 2: \"Domain\"}\n        key = _make_mock_key([(\"Category\", 1)])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"Category\": (\"category\", lambda v: categories.get(v, f\"Unknown ({v})\")),\n            },\n            entry,\n        )\n\n        assert entry == {\"category\": \"Private\"}\n\n    def test_lookup_converter_unknown(self):\n        \"\"\"Test lookup converter with unknown value\"\"\"\n        categories = {0: \"Public\", 1: \"Private\", 2: \"Domain\"}\n        key = _make_mock_key([(\"Category\", 99)])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"Category\": (\"category\", lambda v: categories.get(v, f\"Unknown ({v})\")),\n            },\n            entry,\n        )\n\n        assert entry == {\"category\": \"Unknown (99)\"}\n\n    def test_unmapped_values_ignored(self):\n        \"\"\"Test that values not in value_map are ignored\"\"\"\n        key = _make_mock_key(\n            [\n                (\"ProfileName\", \"MyNetwork\"),\n                (\"UnknownField\", \"SomeValue\"),\n            ]\n        )\n        entry = {}\n\n        extract_values(key, {\"ProfileName\": \"profile_name\"}, entry)\n\n        assert entry == {\"profile_name\": \"MyNetwork\"}\n        assert \"UnknownField\" not in entry\n\n    def test_preserves_existing_entry_values(self):\n        \"\"\"Test that existing entry values are preserved\"\"\"\n        key = _make_mock_key([(\"ProfileName\", \"MyNetwork\")])\n        entry = {\"type\": \"profile\", \"key_path\": \"/some/path\"}\n\n        extract_values(key, {\"ProfileName\": \"profile_name\"}, entry)\n\n        assert entry == {\n            \"type\": \"profile\",\n            \"key_path\": \"/some/path\",\n            \"profile_name\": \"MyNetwork\",\n        }\n\n    def test_empty_value_map(self):\n        \"\"\"Test with empty value_map\"\"\"\n        key = _make_mock_key([(\"ProfileName\", \"MyNetwork\")])\n        entry = {}\n\n        extract_values(key, {}, entry)\n\n        assert entry == {}\n\n    def test_empty_registry_key(self):\n        \"\"\"Test with registry key that has no values\"\"\"\n        key = _make_mock_key([])\n        entry = {}\n\n        extract_values(key, {\"ProfileName\": \"profile_name\"}, entry)\n\n        assert entry == {}\n\n    def test_mixed_simple_and_callable(self):\n        \"\"\"Test mixing simple rename and callable converters\"\"\"\n        key = _make_mock_key(\n            [\n                (\"ProfileName\", \"MyNetwork\"),\n                (\"Enabled\", 1),\n                (\"Category\", 2),\n            ]\n        )\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"ProfileName\": \"profile_name\",\n                \"Enabled\": (\"enabled\", lambda v: v != 0),\n                \"Category\": (\"category\", lambda v: {0: \"Public\", 1: \"Private\", 2: \"Domain\"}.get(v)),\n            },\n            entry,\n        )\n\n        assert entry == {\n            \"profile_name\": \"MyNetwork\",\n            \"enabled\": True,\n            \"category\": \"Domain\",\n        }\n\n    def test_converter_with_bytes(self):\n        \"\"\"Test converter that handles bytes (like MAC address)\"\"\"\n\n        def format_mac(val):\n            if isinstance(val, bytes) and len(val) == 6:\n                return \":\".join(f\"{b:02X}\" for b in val)\n            return val\n\n        key = _make_mock_key([(\"MacAddress\", b\"\\x00\\x1a\\x2b\\x3c\\x4d\\x5e\")])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"MacAddress\": (\"mac_address\", format_mac),\n            },\n            entry,\n        )\n\n        assert entry == {\"mac_address\": \"00:1A:2B:3C:4D:5E\"}\n\n    def test_converter_returns_none(self):\n        \"\"\"Test converter that returns None\"\"\"\n        key = _make_mock_key([(\"DateCreated\", b\"\\x00\")])\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"DateCreated\": (\"date_created\", lambda v: None if len(v) < 16 else \"parsed\"),\n            },\n            entry,\n        )\n\n        assert entry == {\"date_created\": None}\n\n    def test_converter_with_integer_values(self):\n        \"\"\"Test various integer value conversions\"\"\"\n        key = _make_mock_key(\n            [\n                (\"Bias\", -300),\n                (\"DaylightBias\", -60),\n            ]\n        )\n        entry = {}\n\n        extract_values(\n            key,\n            {\n                \"Bias\": (\"bias_minutes\", lambda v: v),\n                \"DaylightBias\": (\"daylight_bias_minutes\", lambda v: v),\n            },\n            entry,\n        )\n\n        assert entry == {\n            \"bias_minutes\": -300,\n            \"daylight_bias_minutes\": -60,\n        }\n"
  },
  {
    "path": "regipy_tests/tests.py",
    "content": "import json\nimport os\nfrom tempfile import mkdtemp\n\nfrom regipy import NoRegistrySubkeysException\nfrom regipy.cli_utils import get_filtered_subkeys\nfrom regipy.hive_types import NTUSER_HIVE_TYPE\nfrom regipy.plugins.utils import dump_hive_to_json\nfrom regipy.recovery import apply_transaction_logs\nfrom regipy.regdiff import compare_hives\nfrom regipy.registry import NKRecord, RegistryHive\n\n\ndef test_parse_header(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n\n    assert isinstance(registry_hive, RegistryHive)\n    assert registry_hive.header.primary_sequence_num == 749\n    assert registry_hive.header.secondary_sequence_num == 749\n    assert registry_hive.header.last_modification_time == 129782982453388850\n    assert registry_hive.header.major_version == 1\n    assert registry_hive.header.minor_version == 3\n    assert registry_hive.header.root_key_offset == 32\n    assert registry_hive.header.hive_bins_data_size == 733184\n    assert registry_hive.header.minor_version == 3\n    assert registry_hive.header.file_name == \"?\\\\C:\\\\Users\\\\vibranium\\\\ntuser.dat\"\n    assert registry_hive.header.checksum == 476614345\n\n\ndef test_parse_root_key(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n\n    assert isinstance(registry_hive, RegistryHive)\n    assert isinstance(registry_hive.root, NKRecord)\n    assert registry_hive.root.name == \"CMI-CreateHive{6A1C4018-979D-4291-A7DC-7AED1C75B67C}\"\n    assert registry_hive.root.subkey_count == 11\n    assert dict(registry_hive.root.header) == {\n        \"access_bits\": b\"\\x02\\x00\\x00\\x00\",\n        \"class_name_offset\": 4294967295,\n        \"class_name_size\": 0,\n        \"flags\": {\n            \"KEY_COMP_NAME\": True,\n            \"KEY_HIVE_ENTRY\": True,\n            \"KEY_HIVE_EXIT\": False,\n            \"KEY_NO_DELETE\": True,\n            \"KEY_PREDEF_HANDLE\": False,\n            \"KEY_SYM_LINK\": False,\n            \"KEY_VOLATILE\": False,\n        },\n        \"key_name_size\": 52,\n        \"key_name_string\": b\"CMI-CreateHive{6A1C4018-979D-4291-A7DC-7AED1C75B67C}\",\n        \"largest_sk_class_name\": 0,\n        \"largest_sk_name\": 40,\n        \"largest_value_name\": 0,\n        \"last_modified\": 129780243434537497,\n        \"largest_value_data\": 0,\n        \"parent_key_offset\": 1968,\n        \"security_key_offset\": 1376,\n        \"subkey_count\": 11,\n        \"subkeys_list_offset\": 73760,\n        \"values_count\": 0,\n        \"values_list_offset\": 4294967295,\n        \"volatile_subkey_count\": 0,\n        \"volatile_subkeys_list_offset\": 4294967295,\n    }\n\n\ndef test_find_keys_ntuser(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    run_key = registry_hive.get_key(r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\")\n\n    assert run_key.name == \"Run\"\n    assert run_key.header.last_modified == 129779615948377168\n\n    values = list(run_key.iter_values(as_json=True))\n    assert values[0].name == \"Sidebar\"\n    assert values[0].value_type == \"REG_EXPAND_SZ\"\n\n\ndef test_find_keys_partial_ntuser_hive(ntuser_software_partial):\n    registry_hive = RegistryHive(\n        ntuser_software_partial,\n        hive_type=NTUSER_HIVE_TYPE,\n        partial_hive_path=r\"\\Software\",\n    )\n\n    run_key = registry_hive.get_key(r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\")\n    assert run_key.name == \"Run\"\n    assert run_key.header.last_modified == 132024690510209250\n\n    values = list(run_key.iter_values(as_json=True))\n    assert values[0].name == \"OneDrive\"\n    assert values[0].value_type == \"REG_SZ\"\n\n\ndef test_regdiff(ntuser_hive, second_hive_path):\n    found_differences = compare_hives(ntuser_hive, second_hive_path, verbose=True)\n    assert len(found_differences) == 7\n    assert len([x for x in found_differences if x[0] == \"new_subkey\"]) == 6\n    assert len([x for x in found_differences if x[0] == \"new_value\"]) == 1\n\n\ndef test_ntuser_emojis(transaction_ntuser):\n    # There are some cases where the Registry stores utf-16 emojis as subkey names :)\n    registry_hive = RegistryHive(transaction_ntuser)\n    international = registry_hive.get_key(r\"\\Control Panel\\International\")\n    subkeys = [x.name for x in international.iter_subkeys()]\n    assert subkeys == [\"Geo\", \"User Profile\", \"User Profile System Backup\", \"🌎🌏🌍\"]\n\n\ndef test_recurse_ntuser(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n\n    value_types = {\n        \"REG_BINARY\": 0,\n        \"REG_DWORD\": 0,\n        \"REG_EXPAND_SZ\": 0,\n        \"REG_MULTI_SZ\": 0,\n        \"REG_NONE\": 0,\n        \"REG_QWORD\": 0,\n        \"REG_SZ\": 0,\n    }\n\n    subkey_count = 0\n    values_count = 0\n    for subkey in registry_hive.recurse_subkeys(as_json=True):\n        subkey_values = subkey.values\n        subkey_count += 1\n        values_count += len(subkey_values or [])\n        if subkey_values:\n            for x in subkey_values:\n                value_types[x[\"value_type\"]] += 1\n\n    assert subkey_count == 1812\n    assert values_count == 4094\n    assert value_types == {\n        \"REG_BINARY\": 531,\n        \"REG_DWORD\": 1336,\n        \"REG_EXPAND_SZ\": 93,\n        \"REG_MULTI_SZ\": 303,\n        \"REG_NONE\": 141,\n        \"REG_QWORD\": 54,\n        \"REG_SZ\": 1636,\n    }\n\n\ndef test_recurse_partial_ntuser(ntuser_software_partial):\n    registry_hive = RegistryHive(\n        ntuser_software_partial,\n        hive_type=NTUSER_HIVE_TYPE,\n        partial_hive_path=r\"\\Software\",\n    )\n    for subkey_count, subkey in enumerate(registry_hive.recurse_subkeys(as_json=True)):\n        assert subkey.actual_path.startswith(registry_hive.partial_hive_path)\n    assert subkey_count == 6395\n\n\ndef test_recurse_ntuser_without_fetching_values(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    for subkey_count, subkey in enumerate(registry_hive.recurse_subkeys(as_json=True, fetch_values=False)):\n        assert subkey.values == []\n        assert subkey.values_count >= 0\n    assert subkey_count == 1811\n\n\ndef test_recurse_amcache(amcache_hive):\n    registry_hive = RegistryHive(amcache_hive)\n\n    value_types = {\n        \"REG_BINARY\": 0,\n        \"REG_DWORD\": 0,\n        \"REG_EXPAND_SZ\": 0,\n        \"REG_MULTI_SZ\": 0,\n        \"REG_NONE\": 0,\n        \"REG_QWORD\": 0,\n        \"REG_SZ\": 0,\n    }\n    subkey_count = 0\n    values_count = 0\n    for subkey in registry_hive.recurse_subkeys():\n        subkey_count += 1\n        subkey_values = subkey.values\n        values_count += len(subkey_values or [])\n        if subkey_values:\n            for x in subkey_values:\n                value_types[x.value_type] += 1\n    assert subkey_count == 2105\n    assert values_count == 17539\n    assert value_types == {\n        \"REG_BINARY\": 56,\n        \"REG_DWORD\": 1656,\n        \"REG_EXPAND_SZ\": 0,\n        \"REG_MULTI_SZ\": 140,\n        \"REG_NONE\": 0,\n        \"REG_QWORD\": 1254,\n        \"REG_SZ\": 14433,\n    }\n\n\ndef test_ntuser_apply_transaction_logs(transaction_ntuser, transaction_log):\n    output_path = os.path.join(mkdtemp(), \"recovered_hive.dat\")\n    restored_hive_path, recovered_dirty_pages_count = apply_transaction_logs(\n        transaction_ntuser, transaction_log, restored_hive_path=output_path\n    )\n    assert recovered_dirty_pages_count == 132\n\n    found_differences = compare_hives(transaction_ntuser, restored_hive_path)\n    assert len(found_differences) == 588\n    assert len([x for x in found_differences if x[0] == \"new_subkey\"]) == 527\n    assert len([x for x in found_differences if x[0] == \"new_value\"]) == 60\n\n\ndef test_system_apply_transaction_logs(transaction_system, system_tr_log_1, system_tr_log_2):\n    output_path = os.path.join(mkdtemp(), \"recovered_hive.dat\")\n    restored_hive_path, recovered_dirty_pages_count = apply_transaction_logs(\n        transaction_system,\n        primary_log_path=system_tr_log_1,\n        secondary_log_path=system_tr_log_2,\n        restored_hive_path=output_path,\n    )\n    assert recovered_dirty_pages_count == 315\n\n    found_differences = compare_hives(transaction_system, restored_hive_path)\n    assert len(found_differences) == 2511\n    assert len([x for x in found_differences if x[0] == \"new_subkey\"]) == 2458\n    assert len([x for x in found_differences if x[0] == \"new_value\"]) == 53\n\n\ndef test_system_hive_devprop_structure(system_devprop):\n    registry_hive = RegistryHive(system_devprop)\n    subkey = registry_hive.get_key(\n        \"\\\\ControlSet001\\\\Enum\\\\ACPI\\\\ACPI0003\\\\0\\\\Properties\\\\{83da6326-97a6-4088-9453-a1923f573b29}\\\\0003\"\n    )\n    assert subkey.values_count == 1\n    value = subkey.get_values()[0]\n    assert value.name == \"(default)\"\n    assert value.value == \"cmbatt.inf:db04a16c09a7808a:AcAdapter_Inst:6.3.9600.16384:ACPI\\\\ACPI0003\"\n    assert value.value_type == 18\n\n\ndef test_system_apply_transaction_logs_2(transaction_usrclass, usrclass_tr_log_1, usrclass_tr_log_2):\n    output_path = os.path.join(mkdtemp(), \"recovered_hive.dat\")\n    restored_hive_path, recovered_dirty_pages_count = apply_transaction_logs(\n        transaction_usrclass,\n        primary_log_path=usrclass_tr_log_1,\n        secondary_log_path=usrclass_tr_log_2,\n        restored_hive_path=output_path,\n    )\n    assert recovered_dirty_pages_count == 158\n\n    found_differences = compare_hives(transaction_usrclass, restored_hive_path)\n    assert len(found_differences) == 225\n    assert len([x for x in found_differences if x[0] == \"new_subkey\"]) == 93\n    assert len([x for x in found_differences if x[0] == \"new_value\"]) == 132\n\n\ndef test_hive_serialization(ntuser_hive, temp_output_file):\n    registry_hive = RegistryHive(ntuser_hive)\n    dump_hive_to_json(registry_hive, temp_output_file, registry_hive.root, verbose=False)\n    counter = 0\n    with open(temp_output_file) as dumped_hive:\n        for x in dumped_hive.readlines():\n            assert json.loads(x)\n            counter += 1\n    assert counter == 1812\n\n\ndef test_get_key(software_hive):\n    \"\"\"\n    # Refers to https://github.com/mkorman90/regipy/issues/144\n    \"\"\"\n    registry_hive = RegistryHive(software_hive)\n    # We verify the registry headers are similar, because this is the same subkey.\n    assert registry_hive.get_key(\"ODBC\").header == registry_hive.root.get_subkey(\"ODBC\").header\n    assert registry_hive.root.get_subkey(\"ODBC\").header == registry_hive.get_key(\"SOFTWARE\\\\ODBC\").header\n\n\ndef test_get_subkey_errors(software_hive):\n    registry_hive = RegistryHive(software_hive)\n    # Tests the NoRegistrySubkeysException that suppose to be raised\n    try:\n        registry_hive.get_key(\"ODBC\").get_subkey(\"xyz\")\n        raise AssertionError()\n    except NoRegistrySubkeysException:\n        assert True\n\n    # Tests value if raised_on_missing is set to False\n    assert registry_hive.get_key(\"ODBC\").get_subkey(\"xyz\", raise_on_missing=False) is None\n\n\ndef test_parse_security_info(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    run_key = registry_hive.get_key(r\"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\")\n\n    security_key_info = run_key.get_security_key_info()\n    assert security_key_info[\"owner\"] == \"S-1-5-18\"\n    assert security_key_info[\"group\"] == \"S-1-5-18\"\n    assert len(security_key_info[\"dacl\"]) == 4\n    assert security_key_info[\"dacl\"][0] == {\n        \"access_mask\": {\n            \"ACCESS_SYSTEM_SECURITY\": False,\n            \"DELETE\": True,\n            \"GENERIC_ALL\": False,\n            \"GENERIC_EXECUTE\": False,\n            \"GENERIC_READ\": False,\n            \"GENERIC_WRITE\": False,\n            \"MAXIMUM_ALLOWED\": False,\n            \"READ_CONTROL\": True,\n            \"SYNCHRONIZE\": False,\n            \"WRITE_DAC\": True,\n            \"WRITE_OWNER\": True,\n        },\n        \"ace_type\": \"ACCESS_ALLOWED\",\n        \"flags\": {\n            \"CONTAINER_INHERIT_ACE\": True,\n            \"INHERIT_ONLY_ACE\": False,\n            \"NO_PROPAGATE_INHERIT_ACE\": False,\n            \"OBJECT_INHERIT_ACE\": True,\n        },\n        \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1673\",\n    }\n\n    dacl_sids = [x[\"sid\"] for x in security_key_info[\"dacl\"]]\n    assert dacl_sids == [\n        \"S-1-5-21-2036804247-3058324640-2116585241-1673\",\n        \"S-1-5-18\",\n        \"S-1-5-32-544\",\n        \"S-1-5-12\",\n    ]\n\n\ndef test_parse_filetime_value(system_hive_with_filetime):\n    registry_hive = RegistryHive(system_hive_with_filetime)\n    subkey = registry_hive.get_key(\n        r\"\\ControlSet001\\Enum\\USBSTOR\\Disk&Ven_SanDisk&Prod_Cruzer&Rev_1.2\"\n        r\"0\\200608767007B7C08A6A&0\\Properties\\{83da6326-97a6-4088-9453-a1923f573b29}\\0064\"\n    )\n    val = subkey.get_value(\"(default)\", as_json=True)\n    assert val == \"2020-03-17T14:02:38.955490+00:00\"\n\n\ndef test_ntuser_filtered_timestamps_do_not_fetch_values(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    for subkey_count, entry in enumerate(\n        get_filtered_subkeys(\n            registry_hive,\n            registry_hive.root,\n            fetch_values=False,\n            start_date=\"2012-04-03T00:00:00.000000\",\n            end_date=\"2012-04-03T23:59:59.999999\",\n        )\n    ):\n        assert entry.values == []\n    assert subkey_count == 1489\n\n\ndef test_ntuser_filtered_timestamps_fetch_values(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    for subkey_count, entry in enumerate(\n        get_filtered_subkeys(\n            registry_hive,\n            registry_hive.root,\n            fetch_values=True,\n            start_date=\"2012-04-03T00:00:00.000000\",\n            end_date=\"2012-04-03T23:59:59.999999\",\n        )\n    ):\n        # values_count is parsed from the subkey header, so this test if effective.\n        if entry.values_count > 0:\n            assert len(entry.values) == entry.values_count\n    assert subkey_count == 1489\n\n\ndef test_ntuser_filtered_timestamps_no_filter(ntuser_hive):\n    registry_hive = RegistryHive(ntuser_hive)\n    for subkey_count, entry in enumerate(get_filtered_subkeys(registry_hive, registry_hive.root, fetch_values=False)):\n        assert entry.values == []\n    assert subkey_count == 1811\n"
  },
  {
    "path": "regipy_tests/validation/plugin_validation.md",
    "content": "\n# Regipy plugin validation results\n\n## Plugins with validation\n\n| plugin_name                   | plugin_description                                                                       | plugin_class_name               | test_case_name                                | success   |\n|-------------------------------|------------------------------------------------------------------------------------------|---------------------------------|-----------------------------------------------|-----------|\n| active_control_set            | Get information on SYSTEM hive control sets                                              | ActiveControlSetPlugin          | ActiveControlSetPluginValidationCase          | True      |\n| amcache                       | Parse Amcache                                                                            | AmCachePlugin                   | AmCachePluginValidationCase                   | True      |\n| app_paths                     | Parses application paths registry entries                                                | AppPathsPlugin                  | AppPathsPluginValidationCase                  | True      |\n| background_activity_moderator | Get the computer name                                                                    | BAMPlugin                       | BamValidationCase                             | True      |\n| backuprestore_plugin          | Gets the contents of the FilesNotToSnapshot, KeysNotToRestore, and FilesNotToBackup keys | BackupRestorePlugin             | BackupRestorePluginValidationCase             | True      |\n| boot_entry_list               | List the Windows BCD boot entries                                                        | BootEntryListPlugin             | BootEntryListPluginValidationCase             | True      |\n| bootkey                       | Get the Windows boot key                                                                 | BootKeyPlugin                   | BootKeyPluginValidationCase                   | True      |\n| codepage                      | Get codepage value                                                                       | CodepagePlugin                  | CodepagePluginValidationCase                  | True      |\n| computer_name                 | Get the computer name                                                                    | ComputerNamePlugin              | ComputerNamePluginValidationCase              | True      |\n| crash_dump                    | Get crash control information                                                            | CrashDumpPlugin                 | CrashDumpPluginValidationCase                 | True      |\n| diag_sr                       | Get Diag\\SystemRestore values and data                                                   | DiagSRPlugin                    | DiagSRPluginValidationCase                    | True      |\n| disable_last_access           | Get NTFSDisableLastAccessUpdate value                                                    | DisableLastAccessPlugin         | DisableLastAccessPluginValidationCase         | True      |\n| disablesr_plugin              | Gets the value that turns System Restore either on or off                                | DisableSRPlugin                 | DisableSRPluginValidationCase                 | True      |\n| domain_sid                    | Get the machine domain name and SID                                                      | DomainSidPlugin                 | DomainSidPluginValidationCase                 | True      |\n| execution_policy              | Parses PowerShell and script execution policies                                          | ExecutionPolicyPlugin           | ExecutionPolicyPluginValidationCase           | True      |\n| host_domain_name              | Get the computer host and domain names                                                   | HostDomainNamePlugin            | HostDomainNamePluginValidationCase            | True      |\n| image_file_execution_options  | Retrieve image file execution options - a persistence method                             | ImageFileExecutionOptions       | ImageFileExecutionOptionsValidationCase       | True      |\n| installed_programs_ntuser     | Retrieve list of installed programs and their install date from the NTUSER Hive          | InstalledProgramsNTUserPlugin   | InstalledProgramsNTUserPluginValidationCase   | True      |\n| installed_programs_software   | Retrieve list of installed programs and their install date from the SOFTWARE Hive        | InstalledProgramsSoftwarePlugin | InstalledProgramsSoftwarePluginValidationCase | True      |\n| last_logon_plugin             | Get the last logged on username                                                          | LastLogonPlugin                 | LastLogonPluginValidationCase                 | True      |\n| local_sid                     | Get the machine local SID                                                                | LocalSidPlugin                  | LocalSidPluginValidationCase                  | True      |\n| lsa_packages                  | Parses LSA security packages configuration                                               | LSAPackagesPlugin               | LSAPackagesPluginValidationCase               | True      |\n| mounted_devices               | Parses mounted device information                                                        | MountedDevicesPlugin            | MountedDevicesPluginValidationCase            | True      |\n| network_data                  | Get network data from many interfaces                                                    | NetworkDataPlugin               | NetworkDataPluginValidationCase               | True      |\n| network_drives_plugin         | Parse the user's mapped network drives                                                   | NetworkDrivesPlugin             | NetworkDrivesPluginValidationCase             | True      |\n| networklist                   | Parses network connection history                                                        | NetworkListPlugin               | NetworkListPluginValidationCase               | True      |\n| ntuser_classes_installer      | List of installed software from NTUSER hive                                              | NtuserClassesInstallerPlugin    | NtuserClassesInstallerPluginValidationCase    | True      |\n| ntuser_persistence            | Retrieve values from known persistence subkeys in NTUSER hive                            | NTUserPersistencePlugin         | NTUserPersistenceValidationCase               | True      |\n| ntuser_shellbag_plugin        | Parse NTUSER Shellbag items                                                              | ShellBagNtuserPlugin            | ShellBagNtuserPluginValidationCase            | True      |\n| pagefile                      | Parses pagefile configuration                                                            | PagefilePlugin                  | PagefilePluginValidationCase                  | True      |\n| previous_winver_plugin        | Get previous relevant OS information                                                     | PreviousWinVersionPlugin        | PreviousWinVersionPluginValidationCase        | True      |\n| print_demon_plugin            | Get list of installed printer ports, as could be taken advantage by cve-2020-1048        | PrintDemonPlugin                | PrintDemonPluginValidationCase                | True      |\n| processor_architecture        | Get processor architecture info from the System's environment key                        | ProcessorArchitecturePlugin     | ProcessorArchitecturePluginValidationCase     | True      |\n| profilelist_plugin            | Parses information about user profiles found in the ProfileList key                      | ProfileListPlugin               | ProfileListPluginValidationCase               | True      |\n| ras_tracing                   | Retrieve list of executables using ras                                                   | RASTracingPlugin                | RASTracingPluginValidationCase                | True      |\n| routes                        | Get list of routes                                                                       | RoutesPlugin                    | RoutesPluginValidationCase                    | True      |\n| safeboot_configuration        | Get safeboot configuration                                                               | SafeBootConfigurationPlugin     | SafeBootConfigurationPluginValidationCase     | True      |\n| samparse                      | Parses user accounts from SAM hive                                                       | SAMParsePlugin                  | SAMParsePluginValidationCase                  | True      |\n| services                      | Enumerate the services in the SYSTEM hive                                                | ServicesPlugin                  | ServicesPluginValidationCase                  | True      |\n| shimcache                     | Parse Shimcache artifact                                                                 | ShimCachePlugin                 | AmCacheValidationCase                         | True      |\n| shutdown                      | Get shutdown data                                                                        | ShutdownPlugin                  | ShutdownPluginValidationCase                  | True      |\n| software_classes_installer    | List of installed software from SOFTWARE hive                                            | SoftwareClassesInstallerPlugin  | SoftwareClassesInstallerPluginValidationCase  | True      |\n| software_plugin               | Retrieve values from known persistence subkeys in Software hive                          | SoftwarePersistencePlugin       | SoftwarePersistenceValidationCase             | True      |\n| spp_clients_plugin            | Determines volumes monitored by VSS                                                      | SppClientsPlugin                | SppClientsPluginValidationCase                | True      |\n| susclient_plugin              | Extracts SusClient* info, including HDD SN                                               | SusclientPlugin                 | SusclientPluginValidationCase                 | True      |\n| terminal_services_history     | Retrieve history of RDP connections                                                      | TSClientPlugin                  | TSClientPluginValidationCase                  | True      |\n| timezone_data                 | Get timezone data                                                                        | TimezoneDataPlugin              | TimezoneDataPluginValidationCase              | True      |\n| timezone_data2                | Get timezone data                                                                        | TimezoneDataPlugin2             | TimezoneDataPlugin2ValidationCase             | True      |\n| typed_paths                   | Retrieve the typed Paths from the history                                                | TypedPathsPlugin                | TypedPathsPluginValidationCase                | True      |\n| typed_urls                    | Retrieve the typed URLs from IE history                                                  | TypedUrlsPlugin                 | TypedUrlsPluginValidationCase                 | True      |\n| uac_plugin                    | Get the status of User Access Control                                                    | UACStatusPlugin                 | UACStatusPluginValidationCase                 | True      |\n| usb_devices                   | Parses USB device connection history                                                     | USBDevicesPlugin                | USBDevicesPluginValidationCase                | True      |\n| usbstor_plugin                | Parse the connected USB devices history                                                  | USBSTORPlugin                   | USBSTORPluginValidationCase                   | True      |\n| user_assist                   | Parse User Assist artifact                                                               | UserAssistPlugin                | NTUserUserAssistValidationCase                | True      |\n| usrclass_shellbag_plugin      | Parse USRCLASS Shellbag items                                                            | ShellBagUsrclassPlugin          | ShellBagUsrclassPluginValidationCase          | True      |\n| wdigest                       | Get WDIGEST configuration                                                                | WDIGESTPlugin                   | WDIGESTPluginValidationCase                   | True      |\n| windows_defender              | Parses Windows Defender configuration and exclusions                                     | WindowsDefenderPlugin           | WindowsDefenderPluginValidationCase           | True      |\n| winrar_plugin                 | Parse the WinRAR archive history                                                         | WinRARPlugin                    | WinRARPluginValidationCase                    | True      |\n| winscp_saved_sessions         | Retrieve list of WinSCP saved sessions                                                   | WinSCPSavedSessionsPlugin       | WinSCPSavedSessionsPluginValidationCase       | True      |\n| winver_plugin                 | Get relevant OS information                                                              | WinVersionPlugin                | WinVersionPluginValidationCase                | True      |\n| word_wheel_query              | Parse the word wheel query artifact                                                      | WordWheelQueryPlugin            | WordWheelQueryPluginValidationCase            | True      |\n| wsl                           | Get WSL information                                                                      | WSLPlugin                       | WSLPluginValidationCase                       | True      |\n\n## Plugins without validation\n**Starting regipy v5.0.0 - plugin validation replaces tests and is mandatary, being enforced by the build process**\n\n| plugin_name         | plugin_description                                | plugin_class_name       | test_case_name   | success   |\n|---------------------|---------------------------------------------------|-------------------------|------------------|-----------|\n| appcert_dlls        | Parses AppCertDLLs persistence entries            | AppCertDLLsPlugin       |                  | False     |\n| appcompat_flags     | Parses application compatibility flags and layers | AppCompatFlagsPlugin    |                  | False     |\n| appinit_dlls        | Parses AppInit_DLLs persistence entries           | AppInitDLLsPlugin       |                  | False     |\n| appkeys             | Parses application keyboard shortcuts             | AppKeysPlugin           |                  | False     |\n| comdlg32            | Parses Open/Save dialog MRU lists                 | ComDlg32Plugin          |                  | False     |\n| muicache            | Parses MUI Cache (application display names)      | MUICachePlugin          |                  | False     |\n| pending_file_rename | Parses pending file rename operations             | PendingFileRenamePlugin |                  | False     |\n| powershell_logging  | Parses PowerShell logging and execution policy    | PowerShellLoggingPlugin |                  | False     |\n| putty               | Parses PuTTY sessions and SSH host keys           | PuTTYPlugin             |                  | False     |\n| recentdocs          | Parses recently opened documents                  | RecentDocsPlugin        |                  | False     |\n| runmru              | Parses Run dialog MRU list                        | RunMRUPlugin            |                  | False     |\n| shares              | Parses network share configuration                | SharesPlugin            |                  | False     |\n| sysinternals        | Parses Sysinternals tools EULA acceptance         | SysinternalsPlugin      |                  | False     |\n    "
  },
  {
    "path": "regipy_tests/validation/plugin_validation.py",
    "content": "import json\nimport os\nimport sys\nfrom collections import defaultdict\nfrom contextlib import contextmanager\nfrom dataclasses import asdict\nfrom pathlib import Path\n\nfrom tabulate import tabulate\n\nfrom regipy.plugins.plugin import PLUGINS\nfrom regipy.registry import RegistryHive\nfrom regipy_tests.validation.utils import extract_lzma\nfrom regipy_tests.validation.validation import (\n    VALIDATION_CASES,\n    ValidationCase,\n    ValidationResult,\n)\n\n# Enable to raise exception on validation failure\n# As we are currently not enforcing validations - no raising exceptions by default\nENFORCE_VALIDATION = True\n\n# Generate a template for all plugins that have missing validation tests\nGENERATE_MISSING_VALIDATION_TEST_TEMPLATES = False\n\n# It is possible to get an ipdb breakpoint once an exception is raised, useful for debugging plugin results\n# The user will be dropped into the validation case context, accessing all properties using `self`.\nSHOULD_DEBUG = False\n\n\ntest_data_dir = str(Path(__file__).parent.parent.joinpath(\"data\"))\nvalidation_results_output_file = str(Path(__file__).parent.joinpath(\"plugin_validation.md\"))\nvalidated_plugins_json_file = str(Path(__file__).parent.parent.parent.joinpath(\"regipy/plugins/validated_plugins.json\"))\n\n\nclass PluginValidationCaseFailureException(Exception):\n    \"\"\"\n    raised when a plugin validation test case failed\n    \"\"\"\n\n    pass\n\n\n@contextmanager\ndef load_hive(hive_file_name):\n    temp_path = extract_lzma(os.path.join(test_data_dir, hive_file_name))\n    yield RegistryHive(temp_path)\n    os.remove(temp_path)\n\n\ndef validate_case(plugin_validation_case: ValidationCase, registry_hive: RegistryHive):\n    try:\n        plugin_validation_case_instance = plugin_validation_case(registry_hive)\n        return plugin_validation_case_instance.validate()\n    except AssertionError as ex:\n        msg = f\"Validation for {plugin_validation_case_instance.__class__.__name__} failed: {ex}\"\n        if ENFORCE_VALIDATION:\n            if SHOULD_DEBUG:\n                print(f\"[!] [ENFORCED] [DEBUG]: {msg}\")\n                plugin_validation_case_instance.debug()\n            raise PluginValidationCaseFailureException(msg)\n        else:\n            print(f\"[!] [NOT ENFORCED]: {msg}\")\n            if SHOULD_DEBUG:\n                plugin_validation_case_instance.debug()\n\n\ndef run_validations_for_hive_file(hive_file_name, validation_cases) -> list[ValidationResult]:\n    validation_results = []\n    with load_hive(hive_file_name) as registry_hive:\n        for validation_case in validation_cases:\n            validation_results.append(validate_case(validation_case, registry_hive))\n    return validation_results\n\n\ndef main():\n    # Map all existing validation cases\n    validation_cases_map: dict[str, ValidationCase] = {v.plugin.NAME: v for v in VALIDATION_CASES}\n    plugins_without_validation: set = {p.NAME for p in PLUGINS}.difference(set(validation_cases_map.keys()))\n\n    print(f\"[*] Loaded {len(validation_cases_map)} validation cases\")\n\n    if len(sys.argv) == 2:\n        plugin_name = sys.argv[1]\n        if plugin_name in validation_cases_map:\n            print(f\"Running validation for plugin {plugin_name}\")\n            validation_case: ValidationCase = validation_cases_map[plugin_name]\n            with load_hive(validation_case.test_hive_file_name) as registry_hive:\n                validate_case(validation_case, registry_hive)\n                return\n        print(f\"No ValidationCase for {plugin_name}\")\n        return\n\n    # Map all plugins according to registry hive test file, for performance.\n    # Also, warn about plugins without validation, this will be enforced in the future.\n    registry_hive_map = defaultdict(list)\n    for plugin in PLUGINS:\n        plugin_name = plugin.NAME\n        if plugin_name in validation_cases_map:\n            print(f\"[+] Plugin {plugin_name} has validation case\")\n            plugin_validation_case = validation_cases_map[plugin_name]\n\n            # Get hive filename from file, in the future group plugin validation by hive file\n            hive_file_name = plugin_validation_case.test_hive_file_name\n            registry_hive_map[hive_file_name].append(plugin_validation_case)\n        else:\n            print(f\"[!] {plugin_name} has NO validation case!\")\n\n    # Execute grouped by file, to save performance on extracting and loading the hive\n    print(\"\\n\\nRunning Validations:\")\n    validation_results: list[ValidationResult] = []\n    for registry_hive_file_name, validation_cases in registry_hive_map.items():\n        print(f\"\\n\\t[*] Validating {registry_hive_file_name} ({len(validation_cases)} validations):\")\n\n        validation_results.extend(run_validations_for_hive_file(registry_hive_file_name, validation_cases))\n\n    print()\n    validation_results_dict = sorted([asdict(v) for v in validation_results], key=lambda x: x[\"plugin_name\"])\n    print(f\"\\n[!] {len(validation_results_dict)}/{len(PLUGINS)} plugins have a validation case:\")\n    md_table_for_validation_results = tabulate(validation_results_dict, headers=\"keys\", tablefmt=\"github\")\n    print(md_table_for_validation_results)\n\n    if plugins_without_validation:\n        print(f\"\\n[!] {len(plugins_without_validation)}/{len(PLUGINS)} plugins have no validation case!\")\n    # Create empty validation results for plugins without validation\n    md_table_for_plugins_without_validation_results = tabulate(\n        sorted(\n            [\n                asdict(\n                    ValidationResult(\n                        plugin_name=p.NAME,\n                        plugin_description=p.DESCRIPTION,\n                        plugin_class_name=p.__name__,\n                        test_case_name=None,\n                        success=False,\n                    )\n                )\n                for p in PLUGINS\n                if p.NAME in plugins_without_validation\n            ],\n            key=lambda x: x[\"plugin_name\"],\n        ),\n        headers=\"keys\",\n        tablefmt=\"github\",\n    )\n    print(md_table_for_plugins_without_validation_results)\n\n    if GENERATE_MISSING_VALIDATION_TEST_TEMPLATES:\n        for p in PLUGINS:\n            if p.NAME in plugins_without_validation:\n                validation_template = f\"\"\"\nfrom regipy.plugins.{p.COMPATIBLE_HIVE}.{p.__name__.lower()} import {p.__name__}\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass {p.__name__}ValidationCase(ValidationCase):\n    plugin = {p.__name__}\n    test_hive_file_name = \"{p.COMPATIBLE_HIVE}.xz\"\n    exact_expected_result = None\n\n            \"\"\"\n                plugin_name = p.NAME\n                missing_test_target_path = str(\n                    Path(__file__).parent.joinpath(\"validation_tests\").joinpath(f\"{plugin_name}_validation.py\")\n                )\n                if not os.path.exists(missing_test_target_path):\n                    print(f\"Creating template for {plugin_name} target path: {missing_test_target_path}\")\n                    with open(missing_test_target_path, \"w+\") as f:\n                        f.write(validation_template)\n\n    # If we are enforcing validation, raise on plugins without validation\n    if not ENFORCE_VALIDATION and plugins_without_validation:\n        # fmt: off\n        raise PluginValidationCaseFailureException(\n            f\"{len(plugins_without_validation)} plugins are missing validation:\"\n            f\" {[p.__name__ for p in PLUGINS if p.NAME in plugins_without_validation]}\"\n        )\n    # fmt: on\n    # Generate markdown file `validation_results_output_file`\n    markdown_content = f\"\"\"\n# Regipy plugin validation results\n\n## Plugins with validation\n\n{md_table_for_validation_results}\n\n## Plugins without validation\n**Starting regipy v5.0.0 - plugin validation replaces tests and is mandatary, being enforced by the build process**\n\n{md_table_for_plugins_without_validation_results}\n    \"\"\"\n\n    # Write the content to a Markdown file\n    print(f\" ** Updated the validation results in {validation_results_output_file} **\")\n    with open(validation_results_output_file, \"w\") as f:\n        f.write(markdown_content)\n\n    # Generate validated_plugins.json for the package\n    validated_plugin_names = sorted(validation_cases_map.keys())\n    print(f\" ** Updated validated plugins JSON in {validated_plugins_json_file} **\")\n    with open(validated_plugins_json_file, \"w\") as f:\n        json.dump(validated_plugin_names, f, indent=4)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "regipy_tests/validation/utils.py",
    "content": "import lzma\nfrom tempfile import mktemp\n\n\ndef extract_lzma(path):\n    tempfile_path = mktemp()\n    with open(tempfile_path, \"wb\") as tmp, lzma.open(path) as f:\n        tmp.write(f.read())\n    return tempfile_path\n"
  },
  {
    "path": "regipy_tests/validation/validation.py",
    "content": "from dataclasses import dataclass\nfrom typing import Callable, Optional, Union\n\nfrom regipy.plugins.plugin import Plugin\nfrom regipy.registry import RegistryHive\n\nVALIDATION_CASES = set()\n\n\n@dataclass\nclass ValidationResult:\n    plugin_name: str\n    plugin_description: Optional[str]\n    plugin_class_name: str\n    test_case_name: Optional[str]\n    success: bool\n\n\nclass ValidationCase:\n    input_hive: RegistryHive = None\n    plugin: Plugin = None\n\n    plugin_instance: type[Plugin] = None\n\n    # Will hold the output of the plugin execution\n    plugin_output: Union[list, dict] = None\n\n    # These entries will be tested for presence in the plugin output\n    expected_entries: list[dict] = []\n\n    # The result here will be matched to the\n    exact_expected_result: Optional[Union[dict, list]] = None\n\n    # Optionally Implement a custom test for your plugin, which will be called during the validation step\n    # This test can replace the validation of entries, but not the count.\n    # The test must return True, or raise an AssertionError\n    custom_test: Optional[Callable] = None\n\n    # Expected entries count\n    expected_entries_count: int = None\n\n    def __init_subclass__(cls):\n        VALIDATION_CASES.add(cls)\n\n    def __init__(self, input_hive: RegistryHive) -> None:\n        self.input_hive = input_hive\n\n    def validate(self):\n        print(f\"\\tStarting validation for {self.plugin.NAME} ({self.__class__.__name__})\")\n        self.plugin_instance = self.plugin(self.input_hive, as_json=True)\n        self.plugin_instance.run()\n        self.plugin_output = self.plugin_instance.entries\n\n        assert self.exact_expected_result is not None or self.expected_entries is not None or self.custom_test is not None, (\n            \"Some output must be tested!\"\n        )\n\n        entries_found = True\n        for entry in self.expected_entries:\n            if entry not in self.plugin_output:\n                entries_found = False\n\n        assert entries_found\n\n        if self.exact_expected_result:\n            assert self.plugin_output == self.exact_expected_result, \"Expected exact plugin output!\"\n\n        if self.custom_test is not None:\n            self.custom_test()\n\n        # If we are verifying an exact result, there is no need to verify entries count\n        if not self.exact_expected_result:\n            output_entries_count = len(self.plugin_output)\n            assert self.expected_entries_count == output_entries_count, (\n                f\"No match for expected entries count: expected {self.expected_entries_count}, got {output_entries_count}\"\n            )\n\n        print(f\"\\tValidation passed for {self.plugin.NAME}\")\n        return ValidationResult(\n            plugin_name=self.plugin.NAME,\n            plugin_description=self.plugin.DESCRIPTION,\n            plugin_class_name=self.plugin.__name__,\n            test_case_name=self.__class__.__name__,\n            success=True,\n        )\n\n    def debug(self):\n        import ipdb\n\n        ipdb.set_trace()\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/__init_.py",
    "content": ""
  },
  {
    "path": "regipy_tests/validation/validation_tests/active_control_set_validation.py",
    "content": "from regipy.plugins.system.active_controlset import ActiveControlSetPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ActiveControlSetPluginValidationCase(ValidationCase):\n    plugin = ActiveControlSetPlugin\n    test_hive_file_name = \"SYSTEM_WIN_10_1709.xz\"\n\n    exact_expected_result = [\n        {\n            \"name\": \"Current\",\n            \"value\": 1,\n            \"value_type\": \"REG_DWORD\",\n            \"is_corrupted\": False,\n        },\n        {\n            \"name\": \"Default\",\n            \"value\": 1,\n            \"value_type\": \"REG_DWORD\",\n            \"is_corrupted\": False,\n        },\n        {\n            \"name\": \"Failed\",\n            \"value\": 0,\n            \"value_type\": \"REG_DWORD\",\n            \"is_corrupted\": False,\n        },\n        {\n            \"name\": \"LastKnownGood\",\n            \"value\": 1,\n            \"value_type\": \"REG_DWORD\",\n            \"is_corrupted\": False,\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/amcache_validation.py",
    "content": "from regipy.plugins.amcache.amcache import AmCachePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass AmCachePluginValidationCase(ValidationCase):\n    plugin = AmCachePlugin\n    test_hive_file_name = \"amcache.hve.xz\"\n\n    expected_entries = [\n        {\n            \"full_path\": \"C:\\\\Windows\\\\system32\\\\TPVMMondeu.dll\",\n            \"last_modified_timestamp_2\": \"2017-03-17T05:06:04.002722+00:00\",\n            \"program_id\": \"75a010066bb612ca7357ce31df8e9f0300000904\",\n            \"sha1\": \"056f4b9d9ec9b5dc548e1b460da889e44089d76f\",\n            \"timestamp\": \"2017-08-03T11:34:02.263418+00:00\",\n        }\n    ]\n    expected_entries_count = 1367\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/app_paths_plugin_validation.py",
    "content": "from regipy.plugins.software.apppaths import AppPathsPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass AppPathsPluginValidationCase(ValidationCase):\n    plugin = AppPathsPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\App Paths\\\\AcroRd32.exe\",\n            \"application\": \"AcroRd32.exe\",\n            \"architecture\": \"x64\",\n            \"last_write\": \"2011-08-28T22:41:26.262844+00:00\",\n            \"path\": \"C:\\\\Program Files\\\\Adobe\\\\Reader 10.0\\\\Reader\\\\AcroRd32.exe\",\n            \"app_path\": \"C:\\\\Program Files\\\\Adobe\\\\Reader 10.0\\\\Reader\\\\\",\n        }\n    ]\n    expected_entries_count = 34\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/backuprestore_plugin_validation.py",
    "content": "from regipy.plugins.system.backuprestore import BackupRestorePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_backup_restore_plugin_output(c: ValidationCase):\n    assert c.plugin_output.keys() == {\n        \"\\\\ControlSet002\\\\Control\\\\BackupRestore\\\\FilesNotToBackup\",\n        \"\\\\ControlSet002\\\\Control\\\\BackupRestore\\\\KeysNotToRestore\",\n        \"\\\\ControlSet001\\\\Control\\\\BackupRestore\\\\KeysNotToRestore\",\n        \"\\\\ControlSet002\\\\Control\\\\BackupRestore\\\\FilesNotToSnapshot\",\n        \"\\\\ControlSet001\\\\Control\\\\BackupRestore\\\\FilesNotToSnapshot\",\n        \"\\\\ControlSet001\\\\Control\\\\BackupRestore\\\\FilesNotToBackup\",\n    }\n\n    assert set(c.plugin_output[\"\\\\ControlSet001\\\\Control\\\\BackupRestore\\\\FilesNotToBackup\"].keys()) == {\n        \"BITS_metadata\",\n        \"Temporary Files\",\n        \"last_write\",\n        \"MS Distributed Transaction Coordinator\",\n        \"Kernel Dumps\",\n        \"BITS_BAK\",\n        \"Mount Manager\",\n        \"WER\",\n        \"VSS Service DB\",\n        \"Memory Page File\",\n        \"FVE_Log\",\n        \"Power Management\",\n        \"WUA\",\n        \"FVE_Wipe\",\n        \"Internet Explorer\",\n        \"BITS_LOG\",\n        \"FVE_Control\",\n        \"Netlogon\",\n        \"Offline Files Cache\",\n        \"ETW\",\n        \"VSS Service Alternate DB\",\n        \"VSS Default Provider\",\n        \"RAC\",\n    }\n\n\nclass BackupRestorePluginValidationCase(ValidationCase):\n    plugin = BackupRestorePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    custom_test = test_backup_restore_plugin_output\n    expected_entries_count = 6\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/bam_validation.py",
    "content": "from regipy.plugins.system.bam import BAMPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass BamValidationCase(ValidationCase):\n    plugin = BAMPlugin\n    test_hive_file_name = \"SYSTEM_WIN_10_1709.xz\"\n\n    expected_entries = [\n        {\n            \"sequence_number\": 9,\n            \"version\": 1,\n            \"sid\": \"S-1-5-90-0-1\",\n            \"executable\": \"\\\\Device\\\\HarddiskVolume2\\\\Windows\\\\System32\\\\dwm.exe\",\n            \"timestamp\": \"2020-04-19T09:09:35.731816+00:00\",\n            \"key_path\": \"\\\\ControlSet001\\\\Services\\\\bam\\\\state\\\\UserSettings\\\\S-1-5-90-0-1\",\n        }\n    ]\n    expected_entries_count = 55\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/boot_entry_list_plugin_validation.py",
    "content": "from regipy.plugins.bcd.boot_entry_list import BootEntryListPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass BootEntryListPluginValidationCase(ValidationCase):\n    plugin = BootEntryListPlugin\n    test_hive_file_name = \"BCD.xz\"\n    exact_expected_result = [\n        {\n            \"guid\": \"{733b62de-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x101FFFFF\",\n            \"name\": \"Linux Boot Manager\",\n            \"gpt_disk\": \"376e5397-7d1f-4e4f-a668-5a62c1269e60\",\n            \"gpt_partition\": \"24e0e103-9bc2-477e-a5e2-3e42d2bb134f\",\n            \"image_path\": \"\\\\EFI\\\\systemd\\\\systemd-bootx64.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{733b62e2-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x101FFFFF\",\n            \"name\": \"UEFI OS\",\n            \"gpt_disk\": \"376e5397-7d1f-4e4f-a668-5a62c1269e60\",\n            \"gpt_partition\": \"24e0e103-9bc2-477e-a5e2-3e42d2bb134f\",\n            \"image_path\": \"\\\\EFI\\\\BOOT\\\\BOOTX64.EFI\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{733b62e3-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x101FFFFF\",\n            \"name\": \"Windows Boot Manager\",\n            \"gpt_disk\": \"376e5397-7d1f-4e4f-a668-5a62c1269e60\",\n            \"gpt_partition\": \"24e0e103-9bc2-477e-a5e2-3e42d2bb134f\",\n            \"image_path\": \"\\\\EFI\\\\Microsoft\\\\Boot\\\\bootmgfw.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{733b62e4-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x10200004\",\n            \"name\": \"Windows Resume Application\",\n            \"gpt_disk\": \"0b2394a9-095e-487d-8d48-719ecd4d78ca\",\n            \"gpt_partition\": \"8e0f2c38-e4ea-47ba-b7fc-9d8c74dccf0b\",\n            \"image_path\": \"\\\\Windows\\\\system32\\\\winresume.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{733b62e5-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x10200003\",\n            \"name\": \"Windows 10\",\n            \"gpt_disk\": \"0b2394a9-095e-487d-8d48-719ecd4d78ca\",\n            \"gpt_partition\": \"8e0f2c38-e4ea-47ba-b7fc-9d8c74dccf0b\",\n            \"image_path\": \"\\\\Windows\\\\system32\\\\winload.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{733b62e6-f608-11eb-825c-c112f60133ab}\",\n            \"type\": \"0x10200003\",\n            \"name\": \"Windows Recovery Environment\",\n            \"gpt_disk\": \"00000001-0090-0000-0500-000006000000\",\n            \"gpt_partition\": \"00000003-0000-0000-0000-000000000000\",\n            \"image_path\": \"\\\\windows\\\\system32\\\\winload.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.976970+00:00\",\n        },\n        {\n            \"guid\": \"{9dea862c-5cdd-4e70-acc1-f32b344d4795}\",\n            \"type\": \"0x10100002\",\n            \"name\": \"Windows Boot Manager\",\n            \"gpt_disk\": \"0b2394a9-095e-487d-8d48-719ecd4d78ca\",\n            \"gpt_partition\": \"36be3955-63bf-4068-a6ab-00195cca3a22\",\n            \"image_path\": \"\\\\EFI\\\\Microsoft\\\\Boot\\\\bootmgfw.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.992594+00:00\",\n        },\n        {\n            \"guid\": \"{b2721d73-1db4-4c62-bf78-c548a880142d}\",\n            \"type\": \"0x10200005\",\n            \"name\": \"Windows Memory Diagnostic\",\n            \"gpt_disk\": \"0b2394a9-095e-487d-8d48-719ecd4d78ca\",\n            \"gpt_partition\": \"36be3955-63bf-4068-a6ab-00195cca3a22\",\n            \"image_path\": \"\\\\EFI\\\\Microsoft\\\\Boot\\\\memtest.efi\",\n            \"timestamp\": \"2021-08-09T02:13:30.976970+00:00\",\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/boot_key_plugin_validation.py",
    "content": "from regipy.plugins.system.bootkey import BootKeyPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass BootKeyPluginValidationCase(ValidationCase):\n    plugin = BootKeyPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = [\n        {\n            \"key\": \"e7f28d88f470cfed67dbcdb62ed1275b\",\n            \"timestamp\": \"2012-04-04T11:47:46.203124+00:00\",\n        },\n        {\n            \"key\": \"e7f28d88f470cfed67dbcdb62ed1275b\",\n            \"timestamp\": \"2012-04-04T11:47:46.203124+00:00\",\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/codepage_validation.py",
    "content": "from regipy.plugins.system.codepage import CodepagePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass CodepagePluginValidationCase(ValidationCase):\n    plugin = CodepagePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Control\\\\Nls\\\\CodePage\": {\n            \"last_write\": \"2009-07-14T04:37:09.460768+00:00\",\n            \"ACP\": \"1252\",\n        },\n        \"\\\\ControlSet002\\\\Control\\\\Nls\\\\CodePage\": {\n            \"last_write\": \"2009-07-14T04:37:09.460768+00:00\",\n            \"ACP\": \"1252\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/computer_name_plugin_validation.py",
    "content": "from regipy.plugins.system.computer_name import ComputerNamePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ComputerNamePluginValidationCase(ValidationCase):\n    plugin = ComputerNamePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    exact_expected_result = [\n        {\"name\": \"WKS-WIN732BITA\", \"timestamp\": \"2010-11-10T17:18:08.718750+00:00\"},\n        {\"name\": \"WIN-V5T3CSP8U4H\", \"timestamp\": \"2010-11-10T18:17:36.968750+00:00\"},\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/crash_dump_validation.py",
    "content": "from regipy.plugins.system.crash_dump import CrashDumpPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass CrashDumpPluginValidationCase(ValidationCase):\n    plugin = CrashDumpPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Control\\\\CrashControl\": {\n            \"last_write\": \"2012-04-04T11:47:36.984376+00:00\",\n            \"CrashDumpEnabled\": 2,\n            \"CrashDumpEnabledStr\": \"Kernel memory dump\",\n            \"LogEvent\": 1,\n            \"DumpFile\": \"%SystemRoot%\\\\MEMORY.DMP\",\n            \"MinidumpDir\": \"%SystemRoot%\\\\Minidump\",\n        },\n        \"\\\\ControlSet002\\\\Control\\\\CrashControl\": {\n            \"last_write\": \"2012-04-04T11:47:36.984376+00:00\",\n            \"CrashDumpEnabled\": 2,\n            \"CrashDumpEnabledStr\": \"Kernel memory dump\",\n            \"LogEvent\": 1,\n            \"DumpFile\": \"%SystemRoot%\\\\MEMORY.DMP\",\n            \"MinidumpDir\": \"%SystemRoot%\\\\Minidump\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/diag_sr_validation.py",
    "content": "from regipy.plugins.system.diag_sr import DiagSRPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass DiagSRPluginValidationCase(ValidationCase):\n    plugin = DiagSRPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Services\\\\VSS\\\\Diag\\\\SystemRestore\": {\n            \"last_write\": \"2012-03-31T04:00:22.998834+00:00\",\n            \"SrCreateRp (Enter)\": \"2012-03-31 04:00:01\",\n            \"SrCreateRp (Leave)\": \"2012-03-31 04:00:22\",\n        },\n        \"\\\\ControlSet002\\\\Services\\\\VSS\\\\Diag\\\\SystemRestore\": {\n            \"last_write\": \"2012-03-31T04:00:22.998834+00:00\",\n            \"SrCreateRp (Enter)\": \"2012-03-31 04:00:01\",\n            \"SrCreateRp (Leave)\": \"2012-03-31 04:00:22\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/disable_last_access_validation.py",
    "content": "from regipy.plugins.system.disablelastaccess import DisableLastAccessPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass DisableLastAccessPluginValidationCase(ValidationCase):\n    plugin = DisableLastAccessPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Control\\\\FileSystem\": {\n            \"last_write\": \"2009-07-14T04:37:09.429568+00:00\",\n            \"NtfsDisableLastAccessUpdate\": \"1\",\n            \"NtfsDisableLastAccessUpdateStr\": \"\",\n        },\n        \"\\\\ControlSet002\\\\Control\\\\FileSystem\": {\n            \"last_write\": \"2009-07-14T04:37:09.429568+00:00\",\n            \"NtfsDisableLastAccessUpdate\": \"1\",\n            \"NtfsDisableLastAccessUpdateStr\": \"\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/disablesr_plugin_validation.py",
    "content": "from regipy.plugins.software.disablesr import DisableSRPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass DisableSRPluginValidationCase(ValidationCase):\n    plugin = DisableSRPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n    exact_expected_result = {\n        \"\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\SystemRestore\": {\"last_write\": \"2012-03-31T04:00:23.006648+00:00\"}\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/domain_sid_plugin_validation.py",
    "content": "from regipy.plugins.security.domain_sid import DomainSidPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass DomainSidPluginValidationCase(ValidationCase):\n    plugin = DomainSidPlugin\n    test_hive_file_name = \"SECURITY.xz\"\n    exact_expected_result = [\n        {\n            \"domain_name\": \"WORKGROUP\",\n            \"domain_sid\": None,\n            \"machine_sid\": None,\n            \"timestamp\": \"2021-08-05T10:43:08.911000+00:00\",\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/execution_policy_plugin_validation.py",
    "content": "from regipy.plugins.software.execpolicy import ExecutionPolicyPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ExecutionPolicyPluginValidationCase(ValidationCase):\n    plugin = ExecutionPolicyPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries = [\n        {\n            \"type\": \"powershell\",\n            \"key_path\": \"\\\\Microsoft\\\\PowerShell\\\\1\\\\ShellIds\\\\Microsoft.PowerShell\",\n            \"last_write\": \"2010-11-10T18:09:15.781250+00:00\",\n            \"path\": \"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\",\n        },\n        {\n            \"type\": \"wsh\",\n            \"key_path\": \"\\\\Microsoft\\\\Windows Script Host\\\\Settings\",\n            \"last_write\": \"2009-07-14T04:37:08.306366+00:00\",\n            \"display_logo\": False,\n        },\n    ]\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/host_domain_name_plugin_validation.py",
    "content": "from regipy.plugins.system.host_domain_name import HostDomainNamePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass HostDomainNamePluginValidationCase(ValidationCase):\n    plugin = HostDomainNamePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = [\n        {\n            \"hostname\": \"WKS-WIN732BITA\",\n            \"domain\": \"shieldbase.local\",\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n        },\n        {\n            \"hostname\": \"WKS-WIN732BITA\",\n            \"domain\": \"shieldbase.local\",\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/image_file_execution_options_validation.py",
    "content": "from regipy.plugins.software.image_file_execution_options import (\n    ImageFileExecutionOptions,\n)\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ImageFileExecutionOptionsValidationCase(ValidationCase):\n    plugin = ImageFileExecutionOptions\n    test_hive_file_name = \"SOFTWARE.xz\"\n    expected_entries = [\n        {\n            \"name\": \"AcroRd32.exe\",\n            \"timestamp\": \"2011-08-28T22:41:26.348786+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"AcroRd32Info.exe\",\n            \"timestamp\": \"2011-08-28T22:41:26.342926+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"clview.exe\",\n            \"timestamp\": \"2010-11-10T10:33:42.573040+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"cnfnot32.exe\",\n            \"timestamp\": \"2010-11-10T10:33:43.369916+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"DllNXOptions\",\n            \"timestamp\": \"2009-07-14T04:41:12.790008+00:00\",\n            \"mscoree.dll\": 1,\n            \"mscorwks.dll\": 1,\n            \"mso.dll\": 1,\n            \"msjava.dll\": 1,\n            \"msci_uno.dll\": 1,\n            \"jvm.dll\": 1,\n            \"jvm_g.dll\": 1,\n            \"javai.dll\": 1,\n            \"vb40032.dll\": 1,\n            \"vbe6.dll\": 1,\n            \"ums.dll\": 1,\n            \"main123w.dll\": 1,\n            \"udtapi.dll\": 1,\n            \"mscorsvr.dll\": 1,\n            \"eMigrationmmc.dll\": 1,\n            \"eProcedureMMC.dll\": 1,\n            \"eQueryMMC.dll\": 1,\n            \"EncryptPatchVer.dll\": 1,\n            \"Cleanup.dll\": 1,\n            \"divx.dll\": 1,\n            \"divxdec.ax\": 1,\n            \"fullsoft.dll\": 1,\n            \"NSWSTE.dll\": 1,\n            \"ASSTE.dll\": 1,\n            \"NPMLIC.dll\": 1,\n            \"PMSTE.dll\": 1,\n            \"AVSTE.dll\": 1,\n            \"NAVOPTRF.dll\": 1,\n            \"DRMINST.dll\": 1,\n            \"TFDTCTT8.dll\": 1,\n            \"DJSMAR00.dll\": 1,\n            \"xlmlEN.dll\": 1,\n            \"ISSTE.dll\": 1,\n            \"symlcnet.dll\": 1,\n            \"ppw32hlp.dll\": 1,\n            \"Apitrap.dll\": 1,\n            \"Vegas60k.dll\": 1,\n        },\n        {\n            \"name\": \"dw20.exe\",\n            \"timestamp\": \"2010-11-10T10:33:42.619916+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"dwtrig20.exe\",\n            \"timestamp\": \"2010-11-10T10:33:42.619916+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n        {\n            \"name\": \"excel.exe\",\n            \"timestamp\": \"2010-11-10T10:33:43.135540+00:00\",\n            \"DisableExceptionChainValidation\": 0,\n        },\n    ]\n    expected_entries_count = 32\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/installed_programs_ntuser_validation.py",
    "content": "from regipy.plugins.ntuser.installed_programs_ntuser import (\n    InstalledProgramsNTUserPlugin,\n)\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass InstalledProgramsNTUserPluginValidationCase(ValidationCase):\n    plugin = InstalledProgramsNTUserPlugin\n    test_hive_file_name = \"NTUSER_with_winscp.DAT.xz\"\n    expected_entries = [\n        {\n            \"service_name\": \"ZoomUMX\",\n            \"timestamp\": \"2022-02-28T09:05:31.141524+00:00\",\n            \"registry_path\": \"\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\",\n            \"DisplayIcon\": \"C:\\\\Users\\\\tony\\\\AppData\\\\Roaming\\\\Zoom\\\\bin\\\\Zoom.exe\",\n            \"DisplayName\": \"Zoom\",\n            \"DisplayVersion\": \"5.9.3 (3169)\",\n            \"EstimatedSize\": 10000,\n            \"HelpLink\": \"https://support.zoom.us/home\",\n            \"URLInfoAbout\": \"https://zoom.us\",\n            \"URLUpdateInfo\": \"https://zoom.us\",\n            \"Publisher\": \"Zoom Video Communications, Inc.\",\n            \"UninstallString\": '\"C:\\\\Users\\\\tony\\\\AppData\\\\Roaming\\\\Zoom\\\\uninstall\\\\Installer.exe\" /uninstall',\n            \"InstallLocation\": \"C:\\\\Users\\\\tony\\\\AppData\\\\Roaming\\\\Zoom\\\\bin\",\n            \"NoModify\": 1,\n            \"NoRepair\": 1,\n        },\n    ]\n    expected_entries_count = 4\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/installed_programs_software_plugin_validation.py",
    "content": "from regipy.plugins.software.installed_programs import InstalledProgramsSoftwarePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass InstalledProgramsSoftwarePluginValidationCase(ValidationCase):\n    plugin = InstalledProgramsSoftwarePlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries_count = 67\n    expected_entries = [\n        {\n            \"registry_path\": \"\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\",\n            \"service_name\": \"AddressBook\",\n            \"timestamp\": \"2009-07-14T04:41:12.758808+00:00\",\n        },\n        {\n            \"service_name\": \"Connection Manager\",\n            \"timestamp\": \"2009-07-14T04:41:12.758808+00:00\",\n            \"registry_path\": \"\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\",\n            \"SystemComponent\": 1,\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/last_logon_plugin_validation.py",
    "content": "from regipy.plugins.software.last_logon import LastLogonPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass LastLogonPluginValidationCase(ValidationCase):\n    plugin = LastLogonPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = {\n        \"last_logged_on_provider\": \"{6F45DC1E-5384-457A-BC13-2CD81B0D28ED}\",\n        \"last_logged_on_sam_user\": \"SHIELDBASE\\\\rsydow\",\n        \"last_logged_on_user\": \"SHIELDBASE\\\\rsydow\",\n        \"last_write\": \"2012-04-04T12:20:41.453654+00:00\",\n        \"show_tablet_keyboard\": 0,\n    }\n\n    expected_entries_count = 5\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/local_sid_plugin_validation.py",
    "content": "from regipy.plugins.sam.local_sid import LocalSidPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass LocalSidPluginValidationCase(ValidationCase):\n    plugin = LocalSidPlugin\n\n    test_hive_file_name = \"SAM.xz\"\n    exact_expected_result = [\n        {\n            \"machine_sid\": \"S-1-5-21-1760460187-1592185332-161725925\",\n            \"timestamp\": \"2014-09-24T03:36:43.549302+00:00\",\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/lsa_packages_plugin_validation.py",
    "content": "from regipy.plugins.system.lsa_packages import LSAPackagesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass LSAPackagesPluginValidationCase(ValidationCase):\n    plugin = LSAPackagesPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\ControlSet001\\\\Control\\\\Lsa\",\n            \"last_write\": \"2012-04-04T11:47:46.203124+00:00\",\n            \"audit_base_objects\": False,\n            \"audit_base_directories\": False,\n            \"limit_blank_password_use\": True,\n            \"notification_packages\": [\"scecli\"],\n            \"security_packages\": [\n                \"kerberos\",\n                \"msv1_0\",\n                \"schannel\",\n                \"wdigest\",\n                \"tspkg\",\n                \"pku2u\",\n            ],\n            \"authentication_packages\": [\"msv1_0\"],\n            \"secure_boot\": 1,\n        }\n    ]\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/mounted_devices_plugin_validation.py",
    "content": "from regipy.plugins.system.mountdev import MountedDevicesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass MountedDevicesPluginValidationCase(ValidationCase):\n    plugin = MountedDevicesPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\MountedDevices\",\n            \"last_write\": \"2011-07-05T19:33:28.328124+00:00\",\n            \"value_name\": \"\\\\DosDevices\\\\C:\",\n            \"mount_point\": \"C:\",\n            \"mount_type\": \"drive_letter\",\n        }\n    ]\n    expected_entries_count = 11\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/network_data_plugin_validation.py",
    "content": "from regipy.plugins.system.network_data import NetworkDataPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NetworkDataPluginValidationCase(ValidationCase):\n    plugin = NetworkDataPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Services\\\\Tcpip\\\\Parameters\\\\Interfaces\": {\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n            \"interfaces\": [\n                {\n                    \"interface_name\": \"{698E50A9-4F58-4D86-B61D-F42E58DCACF6}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": [\"10.3.58.5\"],\n                    \"subnet_mask\": [\"255.255.255.0\"],\n                    \"default_gateway\": [\"10.3.58.1\"],\n                    \"name_server\": \"10.3.58.4\",\n                    \"domain\": 0,\n                },\n                {\n                    \"interface_name\": \"{6AAFC9A9-0542-4DB2-8760-CCFFA953737C}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": [\"192.168.1.123\"],\n                    \"subnet_mask\": [\"255.255.255.0\"],\n                    \"default_gateway\": [\"192.168.1.1\"],\n                    \"name_server\": \"192.168.1.112\",\n                    \"domain\": 0,\n                },\n                {\n                    \"interface_name\": \"{e29ac6c2-7037-11de-816d-806e6f6e6963}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": None,\n                    \"subnet_mask\": None,\n                    \"default_gateway\": None,\n                    \"name_server\": None,\n                    \"domain\": None,\n                },\n            ],\n        },\n        \"\\\\ControlSet002\\\\Services\\\\Tcpip\\\\Parameters\\\\Interfaces\": {\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n            \"interfaces\": [\n                {\n                    \"interface_name\": \"{698E50A9-4F58-4D86-B61D-F42E58DCACF6}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": [\"10.3.58.5\"],\n                    \"subnet_mask\": [\"255.255.255.0\"],\n                    \"default_gateway\": [\"10.3.58.1\"],\n                    \"name_server\": \"10.3.58.4\",\n                    \"domain\": 0,\n                },\n                {\n                    \"interface_name\": \"{6AAFC9A9-0542-4DB2-8760-CCFFA953737C}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": [\"192.168.1.123\"],\n                    \"subnet_mask\": [\"255.255.255.0\"],\n                    \"default_gateway\": [\"192.168.1.1\"],\n                    \"name_server\": \"192.168.1.112\",\n                    \"domain\": 0,\n                },\n                {\n                    \"interface_name\": \"{e29ac6c2-7037-11de-816d-806e6f6e6963}\",\n                    \"last_modified\": \"2011-09-17T13:43:23.770078+00:00\",\n                    \"incomplete_data\": False,\n                    \"dhcp_enabled\": False,\n                    \"ip_address\": None,\n                    \"subnet_mask\": None,\n                    \"default_gateway\": None,\n                    \"name_server\": None,\n                    \"domain\": None,\n                },\n            ],\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/network_drives_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.network_drives import NetworkDrivesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NetworkDrivesPluginValidationCase(ValidationCase):\n    plugin = NetworkDrivesPlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n    exact_expected_result = [\n        {\n            \"drive_letter\": \"p\",\n            \"last_write\": \"2012-04-03T22:08:18.840132+00:00\",\n            \"network_path\": \"\\\\\\\\controller\\\\public\",\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/networklist_plugin_validation.py",
    "content": "from regipy.plugins.software.networklist import NetworkListPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NetworkListPluginValidationCase(ValidationCase):\n    plugin = NetworkListPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries = [\n        {\n            \"type\": \"profile\",\n            \"key_path\": \"\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\NetworkList\\\\Profiles\\\\{1EDE3981-5784-4C61-B5A7-CF328A10043E}\",\n            \"profile_guid\": \"{1EDE3981-5784-4C61-B5A7-CF328A10043E}\",\n            \"last_write\": \"2012-04-04T11:48:39.392750+00:00\",\n            \"profile_name\": \"shieldbase.local\",\n            \"description\": \"shieldbase.local\",\n            \"managed\": True,\n            \"category\": \"Domain\",\n            \"date_created\": None,\n            \"name_type\": \"Wired\",\n            \"date_last_connected\": None,\n        }\n    ]\n    expected_entries_count = 6\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_classes_installer_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.classes_installer import NtuserClassesInstallerPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NtuserClassesInstallerPluginValidationCase(ValidationCase):\n    plugin = NtuserClassesInstallerPlugin\n    test_hive_file_name = \"NTUSER_with_winscp.DAT.xz\"\n    expected_entries = [\n        {\n            \"identifier\": \"8A4152964845CF540BEAEBD27F7A8519\",\n            \"is_hidden\": False,\n            \"product_name\": \"Microsoft Visual C++ Compiler Package for Python 2.7\",\n            \"timestamp\": \"2022-02-15T07:00:07.245646+00:00\",\n        }\n    ]\n\n    expected_entries_count = 1\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_persistence_validation.py",
    "content": "from regipy.plugins.ntuser.persistence import NTUserPersistencePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NTUserPersistenceValidationCase(ValidationCase):\n    plugin = NTUserPersistencePlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n\n    exact_expected_result = {\n        \"\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\": {\n            \"timestamp\": \"2012-04-03T21:19:54.837716+00:00\",\n            \"values\": [\n                {\n                    \"name\": \"Sidebar\",\n                    \"value_type\": \"REG_EXPAND_SZ\",\n                    \"value\": \"%ProgramFiles%\\\\Windows Sidebar\\\\Sidebar.exe /autoRun\",\n                    \"is_corrupted\": False,\n                }\n            ],\n        }\n    }\n    expected_entries_count = 1\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ntuser_userassist_validation.py",
    "content": "from regipy.plugins.ntuser.user_assist import UserAssistPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass NTUserUserAssistValidationCase(ValidationCase):\n    plugin = UserAssistPlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n\n    expected_entries = [\n        {\n            \"focus_count\": 1,\n            \"name\": \"%PROGRAMFILES(X86)%\\\\Microsoft Office\\\\Office14\\\\EXCEL.EXE\",\n            \"run_counter\": 4,\n            \"session_id\": 0,\n            \"timestamp\": \"2012-04-04T15:43:14.785000+00:00\",\n            \"total_focus_time_ms\": 47673,\n        },\n        {\n            \"focus_count\": 9,\n            \"name\": \"Microsoft.Windows.RemoteDesktop\",\n            \"run_counter\": 8,\n            \"session_id\": 0,\n            \"timestamp\": \"2012-04-03T22:06:58.124282+00:00\",\n            \"total_focus_time_ms\": 180000,\n        },\n    ]\n    expected_entries_count = 62\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/pagefile_plugin_validation.py",
    "content": "from regipy.plugins.system.pagefile import PagefilePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass PagefilePluginValidationCase(ValidationCase):\n    plugin = PagefilePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\ControlSet001\\\\Control\\\\Session Manager\\\\Memory Management\",\n            \"last_write\": \"2012-04-04T11:47:44.093750+00:00\",\n            \"clear_pagefile_at_shutdown\": False,\n            \"paging_files\": [\"?:\\\\pagefile.sys\"],\n            \"parsed_paging_files\": [{\"path\": \"?:\\\\pagefile.sys\"}],\n            \"existing_page_files\": [\"\\\\??\\\\C:\\\\pagefile.sys\"],\n        }\n    ]\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/previous_winver_plugin_validation.py",
    "content": "from regipy.plugins.system.previous_winver import PreviousWinVersionPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass PreviousWinVersionPluginValidationCase(ValidationCase):\n    plugin = PreviousWinVersionPlugin\n    test_hive_file_name = \"SYSTEM_WIN_10_1709.xz\"\n    exact_expected_result = [\n        {\n            \"key\": \"\\\\Setup\\\\Source OS (Updated on 1/6/2019 02:18:37)\",\n            \"update_date\": \"2019-01-06 02:18:37\",\n            \"BuildLab\": \"15063.rs2_release.170317-1834\",\n            \"BuildLabEx\": \"15063.0.amd64fre.rs2_release.170317-1834\",\n            \"CompositionEditionID\": \"Professional\",\n            \"CurrentBuild\": \"15063\",\n            \"CurrentBuildNumber\": \"15063\",\n            \"CurrentVersion\": \"6.3\",\n            \"EditionID\": \"Professional\",\n            \"InstallationType\": \"Client\",\n            \"InstallDate\": \"2017-07-12 07:18:28\",\n            \"ProductId\": \"00330-80111-62153-AA362\",\n            \"ProductName\": \"Windows 10 Pro\",\n            \"RegisteredOrganization\": 0,\n            \"RegisteredOwner\": \"Windows User\",\n        },\n        {\n            \"key\": \"\\\\Setup\\\\Source OS (Updated on 5/16/2019 00:55:20)\",\n            \"update_date\": \"2019-05-16 00:55:20\",\n            \"BuildLab\": \"17134.rs4_release.180410-1804\",\n            \"BuildLabEx\": \"17134.1.amd64fre.rs4_release.180410-1804\",\n            \"CompositionEditionID\": \"Enterprise\",\n            \"CurrentBuild\": \"17134\",\n            \"CurrentBuildNumber\": \"17134\",\n            \"CurrentVersion\": \"6.3\",\n            \"EditionID\": \"Professional\",\n            \"InstallationType\": \"Client\",\n            \"InstallDate\": \"2019-01-27 10:39:32\",\n            \"ProductId\": \"00330-80111-62153-AA442\",\n            \"ProductName\": \"Windows 10 Pro\",\n            \"RegisteredOrganization\": 0,\n            \"RegisteredOwner\": \"Windows User\",\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/print_demon_plugin_validation.py",
    "content": "from regipy.plugins.software.printdemon import PrintDemonPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass PrintDemonPluginValidationCase(ValidationCase):\n    plugin = PrintDemonPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = [\n        {\n            \"parameters\": [\"9600\", \"n\", \"8\", \"1\"],\n            \"port_name\": \"COM1:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": [\"9600\", \"n\", \"8\", \"1\"],\n            \"port_name\": \"COM2:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": [\"9600\", \"n\", \"8\", \"1\"],\n            \"port_name\": \"COM3:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": [\"9600\", \"n\", \"8\", \"1\"],\n            \"port_name\": \"COM4:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"FILE:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"LPT1:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"LPT2:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"LPT3:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"XPSPort:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"Ne00:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"Ne01:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n        {\n            \"parameters\": 0,\n            \"port_name\": \"nul:\",\n            \"timestamp\": \"2010-11-10T10:35:02.448040+00:00\",\n        },\n    ]\n\n    expected_entries_count = 12\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/processor_architecture_validation.py",
    "content": "from regipy.plugins.system.processor_architecture import ProcessorArchitecturePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ProcessorArchitecturePluginValidationCase(ValidationCase):\n    plugin = ProcessorArchitecturePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Control\\\\Session Manager\\\\Environment\": {\n            \"PROCESSOR_ARCHITECTURE\": \"x86\",\n            \"NUMBER_OF_PROCESSORS\": 49,\n            \"PROCESSOR_IDENTIFIER\": \"x86 Family 16 Model 8 Stepping 0, AuthenticAMD\",\n            \"PROCESSOR_REVISION\": \"0800\",\n        },\n        \"\\\\ControlSet002\\\\Control\\\\Session Manager\\\\Environment\": {\n            \"PROCESSOR_ARCHITECTURE\": \"x86\",\n            \"NUMBER_OF_PROCESSORS\": 49,\n            \"PROCESSOR_IDENTIFIER\": \"x86 Family 16 Model 8 Stepping 0, AuthenticAMD\",\n            \"PROCESSOR_REVISION\": \"0800\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/profile_list_plugin_validation.py",
    "content": "from regipy.plugins.software.profilelist import ProfileListPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ProfileListPluginValidationCase(ValidationCase):\n    plugin = ProfileListPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = [\n        {\n            \"last_write\": \"2009-07-14T04:41:12.493608+00:00\",\n            \"path\": \"%systemroot%\\\\system32\\\\config\\\\systemprofile\",\n            \"flags\": 12,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-18\",\n            \"load_time\": None,\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2010-11-10T18:09:16.250000+00:00\",\n            \"path\": \"C:\\\\Windows\\\\ServiceProfiles\\\\LocalService\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-19\",\n            \"load_time\": None,\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2010-11-10T18:09:16.250000+00:00\",\n            \"path\": \"C:\\\\Windows\\\\ServiceProfiles\\\\NetworkService\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-20\",\n            \"load_time\": None,\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2010-11-10T17:22:52.109376+00:00\",\n            \"path\": \"C:\\\\Users\\\\Pepper\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-100689374-1717798114-2601648136-1000\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2012-04-04T12:42:17.719834+00:00\",\n            \"path\": \"C:\\\\Users\\\\SRL-Helpdesk\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-100689374-1717798114-2601648136-1001\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2011-08-21T00:51:19.820166+00:00\",\n            \"path\": \"C:\\\\Users\\\\nfury\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1105\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2011-08-23T01:33:29.006350+00:00\",\n            \"path\": \"C:\\\\Users\\\\mhill\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1106\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2011-09-17T13:33:17.372366+00:00\",\n            \"path\": \"C:\\\\Users\\\\Tdungan\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1107\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2012-04-06T19:44:17.844274+00:00\",\n            \"path\": \"C:\\\\Users\\\\nromanoff\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 0,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1109\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2012-04-06T19:42:31.408714+00:00\",\n            \"path\": \"C:\\\\Users\\\\rsydow\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 256,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1114\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n        {\n            \"last_write\": \"2012-04-06T19:22:20.845938+00:00\",\n            \"path\": \"C:\\\\Users\\\\vibranium\",\n            \"flags\": 0,\n            \"full_profile\": None,\n            \"state\": 256,\n            \"sid\": \"S-1-5-21-2036804247-3058324640-2116585241-1673\",\n            \"load_time\": \"1601-01-01T00:00:00+00:00\",\n            \"local_load_time\": None,\n        },\n    ]\n\n    expected_entries_count = 11\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/ras_tracing_plugin_validation.py",
    "content": "from regipy.plugins.software.tracing import RASTracingPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass RASTracingPluginValidationCase(ValidationCase):\n    plugin = RASTracingPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries = [\n        {\n            \"key\": \"\\\\Microsoft\\\\Tracing\",\n            \"name\": \"AcroRd32_RASAPI32\",\n            \"timestamp\": \"2012-03-16T21:31:26.613878+00:00\",\n        },\n        {\n            \"key\": \"\\\\Microsoft\\\\Tracing\",\n            \"name\": \"wmplayer_RASMANCS\",\n            \"timestamp\": \"2012-03-12T20:58:55.476336+00:00\",\n        },\n    ]\n    expected_entries_count = 70\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/routes_validation.py",
    "content": "from regipy.plugins.system.routes import RoutesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass RoutesPluginValidationCase(ValidationCase):\n    plugin = RoutesPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Services\\\\Tcpip\\\\Parameters\\\\PersistentRoutes\": {\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n            \"values\": [\n                {\n                    \"name\": \"0.0.0.0,0.0.0.0,192.168.1.1,-1\",\n                    \"value\": 0,\n                    \"value_type\": \"REG_SZ\",\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"0.0.0.0,0.0.0.0,10.3.58.1,-1\",\n                    \"value\": 0,\n                    \"value_type\": \"REG_SZ\",\n                    \"is_corrupted\": False,\n                },\n            ],\n        },\n        \"\\\\ControlSet002\\\\Services\\\\Tcpip\\\\Parameters\\\\PersistentRoutes\": {\n            \"timestamp\": \"2011-09-17T13:43:23.770078+00:00\",\n            \"values\": [\n                {\n                    \"name\": \"0.0.0.0,0.0.0.0,192.168.1.1,-1\",\n                    \"value\": 0,\n                    \"value_type\": \"REG_SZ\",\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"0.0.0.0,0.0.0.0,10.3.58.1,-1\",\n                    \"value\": 0,\n                    \"value_type\": \"REG_SZ\",\n                    \"is_corrupted\": False,\n                },\n            ],\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/safeboot_configuration_validation.py",
    "content": "from regipy.plugins.system.safeboot_configuration import SafeBootConfigurationPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_safeboot_config_result(c: ValidationCase):\n    assert {x[\"name\"] for x in c.plugin_output[\"network\"]} == {\n        \"volmgrx.sys\",\n        \"PlugPlay\",\n        \"NetBT\",\n        \"{4D36E977-E325-11CE-BFC1-08002BE10318}\",\n        \"mfefirek.sys\",\n        \"SWPRV\",\n        \"{4D36E965-E325-11CE-BFC1-08002BE10318}\",\n        \"mfefire\",\n        \"WudfPf\",\n        \"Dhcp\",\n        \"CryptSvc\",\n        \"WudfSvc\",\n        \"TabletInputService\",\n        \"netprofm\",\n        \"ndiscap\",\n        \"mfevtp\",\n        \"System Bus Extender\",\n        \"NetDDEGroup\",\n        \"{50DD5230-BA8A-11D1-BF5D-0000F805F530}\",\n        \"MCODS\",\n        \"Wlansvc\",\n        \"mrxsmb20\",\n        \"PNP_TDI\",\n        \"nsiproxy.sys\",\n        \"{745A17A0-74D3-11D0-B6FE-00A0C90F57DA}\",\n        \"Messenger\",\n        \"TrustedInstaller\",\n        \"rdsessmgr\",\n        \"{4D36E97D-E325-11CE-BFC1-08002BE10318}\",\n        \"DnsCache\",\n        \"{4D36E980-E325-11CE-BFC1-08002BE10318}\",\n        \"dfsc\",\n        \"RpcSs\",\n        \"volmgr.sys\",\n        \"LanmanServer\",\n        \"sacsvr\",\n        \"File system\",\n        \"WinDefend\",\n        \"McMPFSvc\",\n        \"NTDS\",\n        \"{4D36E973-E325-11CE-BFC1-08002BE10318}\",\n        \"LmHosts\",\n        \"DcomLaunch\",\n        \"PNP Filter\",\n        \"NlaSvc\",\n        \"Power\",\n        \"Base\",\n        \"ProfSvc\",\n        \"{71A27CDD-812A-11D0-BEC7-08002BE2092F}\",\n        \"mfehidk.sys\",\n        \"NetworkProvider\",\n        \"mrxsmb\",\n        \"NDIS Wrapper\",\n        \"{4D36E96B-E325-11CE-BFC1-08002BE10318}\",\n        \"{4D36E97B-E325-11CE-BFC1-08002BE10318}\",\n        \"NDIS\",\n        \"Network\",\n        \"WudfRd\",\n        \"{4D36E969-E325-11CE-BFC1-08002BE10318}\",\n        \"VDS\",\n        \"Boot Bus Extender\",\n        \"{D48179BE-EC20-11D1-B6B8-00C04FA372A7}\",\n        \"PolicyAgent\",\n        \"{4D36E972-E325-11CE-BFC1-08002BE10318}\",\n        \"vga.sys\",\n        \"Primary disk\",\n        \"LanmanWorkstation\",\n        \"Ndisuio\",\n        \"PCI Configuration\",\n        \"Dot3Svc\",\n        \"Streams Drivers\",\n        \"vmms\",\n        \"{4D36E96A-E325-11CE-BFC1-08002BE10318}\",\n        \"{6BDD1FC1-810F-11D0-BEC7-08002BE2092F}\",\n        \"{4D36E967-E325-11CE-BFC1-08002BE10318}\",\n        \"TDI\",\n        \"WinMgmt\",\n        \"WudfUsbccidDriver\",\n        \"Boot file system\",\n        \"mfehidk\",\n        \"Browser\",\n        \"BFE\",\n        \"VaultSvc\",\n        \"RpcEptMapper\",\n        \"NetBIOS\",\n        \"vgasave.sys\",\n        \"SharedAccess\",\n        \"AFD\",\n        \"{4D36E96F-E325-11CE-BFC1-08002BE10318}\",\n        \"bowser\",\n        \"MPSSvc\",\n        \"rdpencdd.sys\",\n        \"mrxsmb10\",\n        \"{D94EE5D8-D189-4994-83D2-F68D7D41B0E6}\",\n        \"KeyIso\",\n        \"Netlogon\",\n        \"Eaphost\",\n        \"TBS\",\n        \"AppMgmt\",\n        \"IKEEXT\",\n        \"NativeWifiP\",\n        \"{4D36E974-E325-11CE-BFC1-08002BE10318}\",\n        \"MPSDrv\",\n        \"rdbss\",\n        \"EventLog\",\n        \"Tcpip\",\n        \"EFS\",\n        \"mfefirek\",\n        \"SCardSvr\",\n        \"sermouse.sys\",\n        \"{4D36E975-E325-11CE-BFC1-08002BE10318}\",\n        \"Filter\",\n        \"AppInfo\",\n        \"NetBIOSGroup\",\n        \"{533C5B84-EC70-11D2-9505-00C04F79DEAF}\",\n        \"ipnat.sys\",\n        \"SCSI Class\",\n        \"NetMan\",\n        \"{36FC9E60-C465-11CF-8056-444553540000}\",\n        \"HelpSvc\",\n        \"Nsi\",\n    }\n    assert {x[\"name\"] for x in c.plugin_output[\"minimal\"]} == {\n        \"vga.sys\",\n        \"{4D36E97D-E325-11CE-BFC1-08002BE10318}\",\n        \"{4D36E980-E325-11CE-BFC1-08002BE10318}\",\n        \"volmgrx.sys\",\n        \"Primary disk\",\n        \"RpcSs\",\n        \"volmgr.sys\",\n        \"PlugPlay\",\n        \"sacsvr\",\n        \"File system\",\n        \"WinDefend\",\n        \"{D94EE5D8-D189-4994-83D2-F68D7D41B0E6}\",\n        \"PCI Configuration\",\n        \"KeyIso\",\n        \"NTDS\",\n        \"Netlogon\",\n        \"vmms\",\n        \"{4D36E977-E325-11CE-BFC1-08002BE10318}\",\n        \"{4D36E96A-E325-11CE-BFC1-08002BE10318}\",\n        \"SWPRV\",\n        \"DcomLaunch\",\n        \"PNP Filter\",\n        \"Power\",\n        \"{4D36E965-E325-11CE-BFC1-08002BE10318}\",\n        \"Base\",\n        \"AppMgmt\",\n        \"TBS\",\n        \"{6BDD1FC1-810F-11D0-BEC7-08002BE2092F}\",\n        \"ProfSvc\",\n        \"{71A27CDD-812A-11D0-BEC7-08002BE2092F}\",\n        \"WudfPf\",\n        \"{4D36E967-E325-11CE-BFC1-08002BE10318}\",\n        \"CryptSvc\",\n        \"WudfSvc\",\n        \"WinMgmt\",\n        \"TabletInputService\",\n        \"{4D36E96B-E325-11CE-BFC1-08002BE10318}\",\n        \"Boot file system\",\n        \"System Bus Extender\",\n        \"EventLog\",\n        \"{4D36E97B-E325-11CE-BFC1-08002BE10318}\",\n        \"MCODS\",\n        \"EFS\",\n        \"sermouse.sys\",\n        \"WudfRd\",\n        \"Filter\",\n        \"RpcEptMapper\",\n        \"AppInfo\",\n        \"vgasave.sys\",\n        \"{4D36E969-E325-11CE-BFC1-08002BE10318}\",\n        \"{533C5B84-EC70-11D2-9505-00C04F79DEAF}\",\n        \"VDS\",\n        \"{745A17A0-74D3-11D0-B6FE-00A0C90F57DA}\",\n        \"SCSI Class\",\n        \"Boot Bus Extender\",\n        \"{D48179BE-EC20-11D1-B6B8-00C04FA372A7}\",\n        \"{36FC9E60-C465-11CF-8056-444553540000}\",\n        \"HelpSvc\",\n        \"TrustedInstaller\",\n        \"{4D36E96F-E325-11CE-BFC1-08002BE10318}\",\n    }\n\n\nclass SafeBootConfigurationPluginValidationCase(ValidationCase):\n    plugin = SafeBootConfigurationPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    custom_test = test_safeboot_config_result\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/samparse_plugin_validation.py",
    "content": "from regipy.plugins.sam.samparse import SAMParsePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass SAMParsePluginValidationCase(ValidationCase):\n    plugin = SAMParsePlugin\n    test_hive_file_name = \"SAM.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\SAM\\\\Domains\\\\Account\\\\Users\\\\000001F4\",\n            \"rid\": 500,\n            \"last_write\": \"2014-09-24T06:32:50.378042+00:00\",\n            \"last_login\": \"2010-11-20T21:48:12.569244+00:00\",\n            \"password_last_set\": \"2010-11-20T21:56:34.743687+00:00\",\n            \"account_expires\": None,\n            \"last_failed_login\": None,\n            \"account_flags\": 529,\n            \"account_flags_parsed\": [\n                \"Account Disabled\",\n                \"Normal User Account\",\n                \"Password Does Not Expire\",\n            ],\n            \"failed_login_count\": 0,\n            \"login_count\": 6,\n        },\n        {\n            \"key_path\": \"\\\\SAM\\\\Domains\\\\Account\\\\Users\\\\000003E8\",\n            \"rid\": 1000,\n            \"last_write\": \"2014-09-30T02:59:34.316692+00:00\",\n            \"last_login\": \"2014-09-30T02:59:34.316693+00:00\",\n            \"password_last_set\": \"2014-09-24T03:35:45.844801+00:00\",\n            \"account_expires\": None,\n            \"last_failed_login\": None,\n            \"account_flags\": 16,\n            \"account_flags_parsed\": [\"Normal User Account\"],\n            \"failed_login_count\": 0,\n            \"login_count\": 4,\n        },\n    ]\n    expected_entries_count = 3\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/services_plugin_validation.py",
    "content": "from regipy.plugins.system.services import ServicesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_service(c: ValidationCase):\n    assert c.plugin_output[\"\\\\ControlSet001\\\\Services\"][\"services\"][0] == {\n        \"last_modified\": \"2008-10-21T17:48:29.328124+00:00\",\n        \"name\": \"Abiosdsk\",\n        \"parameters\": [],\n        \"values\": [\n            {\n                \"is_corrupted\": False,\n                \"name\": \"ErrorControl\",\n                \"value\": 0,\n                \"value_type\": \"REG_DWORD\",\n            },\n            {\n                \"is_corrupted\": False,\n                \"name\": \"Group\",\n                \"value\": \"Primary disk\",\n                \"value_type\": \"REG_SZ\",\n            },\n            {\n                \"is_corrupted\": False,\n                \"name\": \"Start\",\n                \"value\": 4,\n                \"value_type\": \"REG_DWORD\",\n            },\n            {\n                \"is_corrupted\": False,\n                \"name\": \"Tag\",\n                \"value\": 3,\n                \"value_type\": \"REG_DWORD\",\n            },\n            {\n                \"is_corrupted\": False,\n                \"name\": \"Type\",\n                \"value\": 1,\n                \"value_type\": \"REG_DWORD\",\n            },\n        ],\n    }\n\n\nclass ServicesPluginValidationCase(ValidationCase):\n    plugin = ServicesPlugin\n    test_hive_file_name = \"corrupted_system_hive.xz\"\n\n    custom_test = test_service\n    expected_entries_count = 1\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shell_bag_ntuser_plugin_validation.py",
    "content": "import datetime as dt\n\nfrom regipy.plugins.ntuser.shellbags_ntuser import ShellBagNtuserPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ShellBagNtuserPluginValidationCase(ValidationCase):\n    plugin = ShellBagNtuserPlugin\n    test_hive_file_name = \"NTUSER_BAGMRU.DAT.xz\"\n\n    expected_entries_count = 102\n    expected_entries = [\n        {\n            \"value\": \"rekall\",\n            \"slot\": \"0\",\n            \"reg_path\": \"\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\BagMRU\\\\2\\\\0\",\n            \"value_name\": \"0\",\n            \"node_slot\": \"11\",\n            \"shell_type\": \"Directory\",\n            \"path\": \"Search Folder\\\\tmp\\\\rekall\",\n            \"creation_time\": dt.datetime(2021, 8, 16, 9, 41, 32).isoformat(),\n            \"full path\": None,\n            \"access_time\": dt.datetime(2021, 8, 16, 9, 43, 22).isoformat(),\n            \"modification_time\": dt.datetime(2021, 8, 16, 9, 41, 32).isoformat(),\n            \"last_write\": \"2021-08-16T09:44:39.333110+00:00\",\n            \"location description\": None,\n            \"mru_order\": \"0\",\n            \"mru_order_location\": 0,\n            \"first_interacted\": None,\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shell_bag_usrclass_plugin_validation.py",
    "content": "from regipy.plugins.usrclass.shellbags_usrclass import ShellBagUsrclassPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ShellBagUsrclassPluginValidationCase(ValidationCase):\n    plugin = ShellBagUsrclassPlugin\n\n    test_hive_file_name = \"UsrClass.dat.xz\"\n    expected_entries_count = 29\n    expected_entries = [\n        {\n            \"value\": \"Dropbox\",\n            \"slot\": \"9\",\n            \"reg_path\": \"\\\\Local Settings\\\\Software\\\\Microsoft\\\\Windows\\\\Shell\\\\BagMRU\",\n            \"value_name\": \"9\",\n            \"node_slot\": \"20\",\n            \"shell_type\": \"Root Folder\",\n            \"path\": \"Dropbox\",\n            \"creation_time\": None,\n            \"full path\": None,\n            \"access_time\": None,\n            \"modification_time\": None,\n            \"last_write\": \"2018-04-05T02:13:26.843024+00:00\",\n            \"location description\": None,\n            \"mru_order\": \"4-8-7-6-9-0-1-5-3-2\",\n            \"mru_order_location\": 4,\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shimcache_validation.py",
    "content": "from regipy.plugins.system.shimcache import ShimCachePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass AmCacheValidationCase(ValidationCase):\n    plugin = ShimCachePlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    expected_entries = [\n        {\n            \"last_mod_date\": \"2011-01-12T12:08:00+00:00\",\n            \"path\": \"\\\\??\\\\C:\\\\Program Files\\\\McAfee\\\\VirusScan Enterprise\\\\mfeann.exe\",\n            \"exec_flag\": \"True\",\n        }\n    ]\n    expected_entries_count = 660\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/shutdown_validation.py",
    "content": "from regipy.plugins.system.shutdown import ShutdownPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass ShutdownPluginValidationCase(ValidationCase):\n    plugin = ShutdownPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = {\n        \"\\\\ControlSet001\\\\Control\\\\Windows\": {\n            \"last_write\": \"2012-04-04T01:58:40.839250+00:00\",\n            \"date\": \"2012-04-04 01:58:40\",\n        },\n        \"\\\\ControlSet002\\\\Control\\\\Windows\": {\n            \"last_write\": \"2012-04-04T01:58:40.839250+00:00\",\n            \"date\": \"2012-04-04 01:58:40\",\n        },\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/software_classes_installer_plugin_validation.py",
    "content": "from regipy.plugins.software.classes_installer import SoftwareClassesInstallerPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_no_hidden_entries(c: ValidationCase):\n    assert not any(x[\"is_hidden\"] for x in c.plugin_output)\n\n\nclass SoftwareClassesInstallerPluginValidationCase(ValidationCase):\n    plugin = SoftwareClassesInstallerPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    expected_entries = [\n        {\n            \"identifier\": \"000041091A0090400000000000F01FEC\",\n            \"is_hidden\": False,\n            \"product_name\": \"Microsoft Office OneNote MUI (English) 2010\",\n            \"timestamp\": \"2010-11-10T10:31:06.573040+00:00\",\n        }\n    ]\n    expected_entries_count = 26\n\n    custom_test = test_no_hidden_entries\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/software_persistence_validation.py",
    "content": "from regipy.plugins.software.persistence import SoftwarePersistencePlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass SoftwarePersistenceValidationCase(ValidationCase):\n    plugin = SoftwarePersistencePlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = {\n        \"\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run\": {\n            \"timestamp\": \"2012-04-04T01:54:23.669836+00:00\",\n            \"values\": [\n                {\n                    \"name\": \"VMware Tools\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\VMware\\\\VMware Tools\\\\VMwareTray.exe\"',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"VMware User Process\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\VMware\\\\VMware Tools\\\\VMwareUser.exe\"',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"Adobe ARM\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\Common Files\\\\Adobe\\\\ARM\\\\1.0\\\\AdobeARM.exe\"',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"McAfeeUpdaterUI\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\McAfee\\\\Common Framework\\\\udaterui.exe\" /StartedFromRunKey',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"ShStatEXE\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\McAfee\\\\VirusScan Enterprise\\\\SHSTAT.EXE\" /STANDALONE',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"McAfee Host Intrusion Prevention Tray\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": '\"C:\\\\Program Files\\\\McAfee\\\\Host Intrusion Prevention\\\\FireTray.exe\"',\n                    \"is_corrupted\": False,\n                },\n                {\n                    \"name\": \"svchost\",\n                    \"value_type\": \"REG_SZ\",\n                    \"value\": \"c:\\\\windows\\\\system32\\\\dllhost\\\\svchost.exe\",\n                    \"is_corrupted\": False,\n                },\n            ],\n        }\n    }\n\n    expected_entries_count = 1\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/spp_clients_plugin_validation.py",
    "content": "from regipy.plugins.software.spp_clients import SppClientsPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass SppClientsPluginValidationCase(ValidationCase):\n    plugin = SppClientsPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n    exact_expected_result = {\n        \"\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\\\\SPP\\\\Clients\": {\n            \"last_write\": \"2012-03-15T22:32:18.089574+00:00\",\n            \"{09F7EDC5-294E-4180-AF6A-FB0E6A0E9513}\": [\"\\\\\\\\?\\\\Volume{656b1715-ecf6-11df-92e6-806e6f6e6963}\\\\:(C:)\"],\n        }\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/susclient_plugin_validation.py",
    "content": "from regipy.plugins.software.susclient import SusclientPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass SusclientPluginValidationCase(ValidationCase):\n    plugin = SusclientPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n    exact_expected_result = {\n        \"\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\WindowsUpdate\": {\n            \"last_write\": \"2012-03-14T07:05:41.719626+00:00\",\n            \"SusClientId\": \"50df98f2-964a-496d-976d-d95296e13929\",\n            \"SusClientIdValidation\": \"\",\n            \"LastRestorePointSetTime\": \"2012-03-14 07:05:41\",\n        }\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/terminal_services_history_validation.py",
    "content": "from regipy.plugins.ntuser.tsclient import TSClientPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass TSClientPluginValidationCase(ValidationCase):\n    plugin = TSClientPlugin\n    # TDODO: Find registry sample with test data\n    test_hive_file_name = \"NTUSER_modified.DAT.xz\"\n    # For now, bypass validation with this ugly trick:\n    exact_expected_result = None\n    expected_entries_count = 0\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/timezone_data2_validation.py",
    "content": "from regipy.plugins.system.timezone_data2 import TimezoneDataPlugin2\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_tz2_plugin_output(c: ValidationCase):\n    assert c.plugin_output[\"\\\\ControlSet001\\\\Control\\\\TimeZoneInformation\"] == {\n        \"last_write\": \"2012-03-11T07:00:00.000642+00:00\",\n        \"Bias\": 300,\n        \"DaylightBias\": -60,\n        \"DaylightName\": \"@tzres.dll,-111\",\n        \"DaylightStart\": \"00000300020002000000000000000000\",\n        \"StandardBias\": 0,\n        \"StandardName\": \"@tzres.dll,-112\",\n        \"StandardStart\": \"00000b00010002000000000000000000\",\n        # UTF-16-LE \"Eastern Standard Time\" followed by garbage bytes, converted to hex\n        \"TimeZoneKeyName\": \"4500610073007400650072006e0020005300740061006e0064006100720064002000540069006d00650000001963747614060136f408f10100000000000000000000000000000000940af101a80af10102000000040bf101000000007c0bf10100000000c80bf1010000000000000000d007f10110df0900d8717476d007f1010000000084e00900fb9373760200000000000000e98b73761406013610000000002000000200000000000000010000000000000000000000f4df090010a6f101000000000000000013000000304514001406013641919676a80af1012f000000d007f1010000000000000000020000000e0000001051140000000e00a0511400\",\n        \"DynamicDaylightTimeDisabled\": 0,\n        \"ActiveTimeBias\": 240,\n    }\n\n\nclass TimezoneDataPlugin2ValidationCase(ValidationCase):\n    plugin = TimezoneDataPlugin2\n    test_hive_file_name = \"SYSTEM.xz\"\n    custom_test = test_tz2_plugin_output\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/timezone_data_validation.py",
    "content": "from regipy.plugins.system.timezone_data import TimezoneDataPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\ndef test_timezone_data(c: ValidationCase):\n    assert {(x[\"name\"], x[\"value\"]) for x in c.plugin_output[\"\\\\ControlSet001\\\\Control\\\\TimeZoneInformation\"]} == {\n        (\"DaylightBias\", 4294967236),\n        (\"Bias\", 300),\n        (\"StandardBias\", 0),\n        (\n            \"TimeZoneKeyName\",\n            \"4500610073007400650072006e0020005300740061006e00640061007200640020\"\n            \"00540069006d00650000001963747614060136f408f101000000000000000000000\"\n            \"00000000000940af101a80af10102000000040bf101000000007c0bf10100000000\"\n            \"c80bf1010000000000000000d007f10110df0900d8717476d007f101\",\n        ),\n        (\"ActiveTimeBias\", 240),\n        (\"StandardStart\", \"00000b00010002000000000000000000\"),\n        (\"DaylightStart\", \"00000300020002000000000000000000\"),\n        (\"StandardName\", \"@tzres.dll,-112\"),\n        (\"DynamicDaylightTimeDisabled\", 0),\n        (\"DaylightName\", \"@tzres.dll,-111\"),\n    }\n\n\nclass TimezoneDataPluginValidationCase(ValidationCase):\n    plugin = TimezoneDataPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    custom_test = test_timezone_data\n    expected_entries_count = 2\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/typed_paths_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.typed_paths import TypedPathsPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass TypedPathsPluginValidationCase(ValidationCase):\n    plugin = TypedPathsPlugin\n\n    test_hive_file_name = \"NTUSER_BAGMRU.DAT.xz\"\n    exact_expected_result = {\n        \"last_write\": \"2022-02-06T13:46:04.945080+00:00\",\n        \"entries\": [\n            {\"url1\": \"cmd\"},\n            {\"url2\": \"C:\\\\Offline\\\\AD\"},\n            {\"url3\": \"git\"},\n            {\"url4\": \"powershell\"},\n            {\"url5\": \"C:\\\\Program Files\"},\n            {\"url6\": \"Network\"},\n            {\"url7\": \"\\\\\\\\wsl$\\\\Ubuntu\\\\projects\\\\CAD316_001\\\\partition_p1\"},\n            {\"url8\": \"\\\\\\\\wsl$\\\\Ubuntu\\\\projects\"},\n            {\"url9\": \"\\\\\\\\wsl$\\\\Ubuntu\"},\n            {\"url10\": \"C:\\\\Users\\\\tony\\\\Github\"},\n            {\"url11\": \"C:\\\\Users\\\\tony\\\\Github\\\\velocity-client-master\"},\n            {\"url12\": \"C:\\\\Users\\\\tony\\\\Github\\\\cogz\"},\n            {\"url13\": \"C:\\\\Users\\\\tony\\\\Github\\\\cogz\\\\cogz\"},\n            {\"url14\": \"Quick access\"},\n            {\"url15\": \"C:\\\\ProgramData\\\\chocolatey\\\\lib\\\\yara\\\\tools\"},\n            {\"url16\": \"C:\\\\Training\\\\MT01\\\\exercise\"},\n        ],\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/typed_urls_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.typed_urls import TypedUrlsPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass TypedUrlsPluginValidationCase(ValidationCase):\n    plugin = TypedUrlsPlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n\n    exact_expected_result = {\n        \"last_write\": \"2012-04-03T22:37:55.411500+00:00\",\n        \"entries\": [\n            {\"url1\": \"http://199.73.28.114:53/\"},\n            {\"url2\": \"http://go.microsoft.com/fwlink/?LinkId=69157\"},\n        ],\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/uac_status_plugin_validation.py",
    "content": "from regipy.plugins.software.uac import UACStatusPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass UACStatusPluginValidationCase(ValidationCase):\n    plugin = UACStatusPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = {\n        \"consent_prompt_admin\": 5,\n        \"consent_prompt_user\": 3,\n        \"enable_limited_user_accounts\": 1,\n        \"enable_virtualization\": 1,\n        \"filter_admin_token\": 0,\n        \"last_write\": \"2011-08-30T18:47:10.734144+00:00\",\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/usb_devices_plugin_validation.py",
    "content": "from regipy.plugins.system.usb_devices import USBDevicesPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass USBDevicesPluginValidationCase(ValidationCase):\n    plugin = USBDevicesPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n\n    expected_entries = [\n        {\n            \"key_path\": \"\\\\ControlSet001\\\\Enum\\\\USB\\\\ROOT_HUB\\\\5&391b2433&0\",\n            \"vid_pid\": \"ROOT_HUB\",\n            \"vid\": None,\n            \"pid\": None,\n            \"instance_id\": \"5&391b2433&0\",\n            \"last_write\": \"2012-04-07T10:31:37.625246+00:00\",\n            \"hardware_id\": [\n                \"USB\\\\ROOT_HUB&VID8086&PID7112&REV0000\",\n                \"USB\\\\ROOT_HUB&VID8086&PID7112\",\n                \"USB\\\\ROOT_HUB\",\n            ],\n            \"container_id\": \"{00000000-0000-0000-ffff-ffffffffffff}\",\n            \"service\": \"usbhub\",\n            \"class_guid\": \"{36fc9e60-c465-11cf-8056-444553540000}\",\n            \"driver\": \"{36fc9e60-c465-11cf-8056-444553540000}\\\\0002\",\n            \"class\": \"USB\",\n            \"manufacturer\": \"(Standard USB Host Controller)\",\n            \"device_desc\": \"USB Root Hub\",\n        }\n    ]\n    expected_entries_count = 14\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/usbstor_plugin_validation.py",
    "content": "from regipy.plugins.system.usbstor import USBSTORPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass USBSTORPluginValidationCase(ValidationCase):\n    plugin = USBSTORPlugin\n    test_hive_file_name = \"SYSTEM_WIN_10_1709.xz\"\n    exact_expected_result = [\n        {\n            \"device_name\": \"SanDisk Cruzer USB Device\",\n            \"disk_guid\": \"{fc416b61-6437-11ea-bd0c-a483e7c21469}\",\n            \"first_installed\": \"2020-03-17T14:02:38.955490+00:00\",\n            \"key_path\": \"\\\\ControlSet001\\\\Enum\\\\USBSTOR\\\\Disk&Ven_SanDisk&Prod_Cruzer&Rev_1.20\\\\200608767007B7C08A6A&0\",\n            \"last_connected\": \"2020-03-17T14:02:38.946628+00:00\",\n            \"last_installed\": \"2020-03-17T14:02:38.955490+00:00\",\n            \"last_removed\": \"2020-03-17T14:23:45.504690+00:00\",\n            \"last_write\": \"2020-03-17T14:02:38.965050+00:00\",\n            \"manufacturer\": \"Ven_SanDisk\",\n            \"serial_number\": \"200608767007B7C08A6A&0\",\n            \"title\": \"Prod_Cruzer\",\n            \"version\": \"Rev_1.20\",\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/wdigest_plugin_validation.py",
    "content": "from regipy.plugins.system.wdigest import WDIGESTPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WDIGESTPluginValidationCase(ValidationCase):\n    plugin = WDIGESTPlugin\n    test_hive_file_name = \"SYSTEM.xz\"\n    exact_expected_result = [\n        {\n            \"subkey\": \"\\\\ControlSet001\\\\Control\\\\SecurityProviders\\\\WDigest\",\n            \"timestamp\": \"2009-07-14T04:37:09.491968+00:00\",\n            \"use_logon_credential\": 1,\n        },\n        {\n            \"subkey\": \"\\\\ControlSet002\\\\Control\\\\SecurityProviders\\\\WDigest\",\n            \"timestamp\": \"2009-07-14T04:37:09.491968+00:00\",\n            \"use_logon_credential\": None,\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/windows_defender_plugin_validation.py",
    "content": "from regipy.plugins.software.defender import WindowsDefenderPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WindowsDefenderPluginValidationCase(ValidationCase):\n    plugin = WindowsDefenderPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n\n    exact_expected_result = [\n        {\n            \"type\": \"configuration\",\n            \"key_path\": \"\\\\Microsoft\\\\Windows Defender\",\n            \"last_write\": \"2011-09-16T20:48:31.741870+00:00\",\n            \"antispyware_disabled\": True,\n            \"product_status\": 0,\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winrar_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.winrar import WinRARPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WinRARPluginValidationCase(ValidationCase):\n    plugin = WinRARPlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n    exact_expected_result = [\n        {\n            \"last_write\": \"2021-11-18T13:59:04.888952+00:00\",\n            \"file_path\": \"C:\\\\Users\\\\tony\\\\Downloads\\\\RegistryFinder64.zip\",\n            \"operation\": \"archive_opened\",\n            \"value_name\": \"0\",\n        },\n        {\n            \"last_write\": \"2021-11-18T13:59:04.888952+00:00\",\n            \"file_path\": \"C:\\\\temp\\\\token.zip\",\n            \"operation\": \"archive_opened\",\n            \"value_name\": \"1\",\n        },\n        {\n            \"last_write\": \"2021-11-18T13:59:50.023788+00:00\",\n            \"file_name\": \"Tools.zip\",\n            \"operation\": \"archive_created\",\n            \"value_name\": \"0\",\n        },\n        {\n            \"last_write\": \"2021-11-18T13:59:50.023788+00:00\",\n            \"file_name\": \"data.zip\",\n            \"operation\": \"archive_created\",\n            \"value_name\": \"1\",\n        },\n        {\n            \"last_write\": \"2021-11-18T14:00:44.180468+00:00\",\n            \"file_path\": \"C:\\\\Users\\\\tony\\\\Downloads\",\n            \"operation\": \"archive_extracted\",\n            \"value_name\": \"0\",\n        },\n        {\n            \"last_write\": \"2021-11-18T14:00:44.180468+00:00\",\n            \"file_path\": \"C:\\\\temp\",\n            \"operation\": \"archive_extracted\",\n            \"value_name\": \"1\",\n        },\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winscp_saved_sessions_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.winscp_saved_sessions import WinSCPSavedSessionsPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WinSCPSavedSessionsPluginValidationCase(ValidationCase):\n    plugin = WinSCPSavedSessionsPlugin\n    test_hive_file_name = \"NTUSER_with_winscp.DAT.xz\"\n    expected_entries_count = 2\n    expected_entries = [\n        {\n            \"timestamp\": \"2022-04-25T09:53:58.125852+00:00\",\n            \"hive_name\": \"HKEY_CURRENT_USER\",\n            \"key_path\": \"HKEY_CURRENT_USER\\\\Software\\\\Martin Prikryl\\\\WinSCP 2\\\\Sessions\\\\Default%20Settings\",\n        }\n    ]\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/winver_plugin_validation.py",
    "content": "from regipy.plugins.software.winver import WinVersionPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WinVersionPluginValidationCase(ValidationCase):\n    plugin = WinVersionPlugin\n    test_hive_file_name = \"SOFTWARE.xz\"\n    exact_expected_result = {\n        \"\\\\Microsoft\\\\Windows NT\\\\CurrentVersion\": {\n            \"last_write\": \"2012-03-14T07:09:21.562500+00:00\",\n            \"CurrentVersion\": \"6.1\",\n            \"CurrentBuild\": \"7601\",\n            \"InstallDate\": \"2010-11-10 16:28:55\",\n            \"RegisteredOrganization\": 0,\n            \"RegisteredOwner\": \"Windows User\",\n            \"InstallationType\": \"Client\",\n            \"EditionID\": \"Ultimate\",\n            \"ProductName\": \"Windows 7 Ultimate\",\n            \"ProductId\": \"00426-067-1817155-86250\",\n            \"CurrentBuildNumber\": \"7601\",\n            \"BuildLab\": \"7601.win7sp1_gdr.111118-2330\",\n            \"BuildLabEx\": \"7601.17727.x86fre.win7sp1_gdr.111118-2330\",\n            \"CSDVersion\": \"Service Pack 1\",\n        }\n    }\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/word_wheel_query_ntuser_validation.py",
    "content": "from regipy.plugins.ntuser.word_wheel_query import WordWheelQueryPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WordWheelQueryPluginValidationCase(ValidationCase):\n    plugin = WordWheelQueryPlugin\n    test_hive_file_name = \"NTUSER.DAT.xz\"\n\n    expected_entries = [\n        {\n            \"last_write\": \"2012-04-04T15:45:18.551340+00:00\",\n            \"mru_id\": 1,\n            \"name\": \"alloy\",\n            \"order\": 0,\n        }\n    ]\n    expected_entries_count = 6\n"
  },
  {
    "path": "regipy_tests/validation/validation_tests/wsl_plugin_validation.py",
    "content": "from regipy.plugins.ntuser.wsl import WSLPlugin\nfrom regipy_tests.validation.validation import ValidationCase\n\n\nclass WSLPluginValidationCase(ValidationCase):\n    plugin = WSLPlugin\n    test_hive_file_name = \"NTUSER-WSL.DAT.xz\"\n    exact_expected_result = {\n        \"\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Lxss\": {\n            \"last_modified\": \"2024-11-26T23:13:44.535966+00:00\",\n            \"number_of_distrib\": 4,\n            \"default_distrib_GUID\": \"{e3986a51-3357-4c37-8a3e-1a83d995a3da}\",\n            \"wsl_version\": \"WSL2\",\n            \"nat_ip_address\": None,\n            \"distributions\": [\n                {\n                    \"GUID\": \"AppxInstallerCache\",\n                    \"last_modified\": \"2024-11-26T22:25:52.048876+00:00\",\n                    \"wsl_distribution_source_location\": None,\n                    \"default_uid\": None,\n                    \"distribution_name\": None,\n                    \"default_environment\": None,\n                    \"flags\": None,\n                    \"kernel_command_line\": None,\n                    \"package_family_name\": None,\n                    \"state\": None,\n                    \"filesystem\": \"Unknown\",\n                },\n                {\n                    \"GUID\": \"{17a1ba00-e5d7-44ba-af5f-194a2677fbb6}\",\n                    \"last_modified\": \"2024-11-26T23:13:35.817050+00:00\",\n                    \"wsl_distribution_source_location\": \"C:\\\\Users\\\\admin\\\\AppData\\\\Local\\\\Packages\\\\TheDebianProject.DebianGNULinux_76v4gfsz19hv4\\\\LocalState\",\n                    \"default_uid\": 1000,\n                    \"distribution_name\": \"Debian\",\n                    \"default_environment\": [\n                        \"HOSTTYPE=x86_64\",\n                        \"LANG=en_US.UTF-8\",\n                        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games\",\n                        \"TERM=xterm-256color\",\n                    ],\n                    \"flags\": 7,\n                    \"kernel_command_line\": \"BOOT_IMAGE=/kernel init=/init\",\n                    \"package_family_name\": \"TheDebianProject.DebianGNULinux_76v4gfsz19hv4\",\n                    \"state\": \"Normal\",\n                    \"filesystem\": \"wslfs\",\n                    \"enable_interop\": True,\n                    \"append_nt_path\": True,\n                    \"enable_drive_mounting\": True,\n                },\n                {\n                    \"GUID\": \"{3f702114-dc8d-44dc-9903-642eb650ec4b}\",\n                    \"last_modified\": \"2024-11-26T23:12:56.145376+00:00\",\n                    \"wsl_distribution_source_location\": \"C:\\\\Users\\\\admin\\\\AppData\\\\Local\\\\Packages\\\\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\\\\LocalState\",\n                    \"default_uid\": 1000,\n                    \"distribution_name\": \"Ubuntu\",\n                    \"default_environment\": [\n                        \"HOSTTYPE=x86_64\",\n                        \"LANG=en_US.UTF-8\",\n                        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games\",\n                        \"TERM=xterm-256color\",\n                    ],\n                    \"flags\": 7,\n                    \"kernel_command_line\": \"BOOT_IMAGE=/kernel init=/init\",\n                    \"package_family_name\": \"CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\",\n                    \"state\": \"Normal\",\n                    \"filesystem\": \"wslfs\",\n                    \"enable_interop\": True,\n                    \"append_nt_path\": True,\n                    \"enable_drive_mounting\": True,\n                },\n                {\n                    \"GUID\": \"{e3986a51-3357-4c37-8a3e-1a83d995a3da}\",\n                    \"last_modified\": \"2024-11-26T23:06:39.553058+00:00\",\n                    \"wsl_distribution_source_location\": \"C:\\\\Users\\\\admin\\\\AppData\\\\Local\\\\Packages\\\\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\\\\LocalState\",\n                    \"default_uid\": 0,\n                    \"distribution_name\": \"Ubuntu-20.04\",\n                    \"default_environment\": None,\n                    \"flags\": 7,\n                    \"kernel_command_line\": None,\n                    \"package_family_name\": \"CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\",\n                    \"state\": \"Normal\",\n                    \"filesystem\": \"wslfs\",\n                    \"enable_interop\": True,\n                    \"append_nt_path\": True,\n                    \"enable_drive_mounting\": True,\n                },\n            ],\n        }\n    }\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"extends\": [\n    \"config:base\"\n  ]\n}\n"
  },
  {
    "path": "requirements.txt",
    "content": "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==20240423\nlibfwps-python==20240417\n"
  }
]