Architecture & Design
Overview
Section titled “Overview”A$PECT UTILS is a Windows setup toolkit with two front ends: a PowerShell app with a WPF (Windows Presentation Foundation) GUI, and a native .NET desktop app. Both read the same JSON configuration files. This document explains the PowerShell architecture, code structure, and how the components work together; the desktop app is covered in Desktop app.
Desktop app
Section titled “Desktop app”The desktop app in desktop/ is a .NET 8 WPF application with no NuGet dependencies. It targets net8.0-windows, requests administrator rights through app.manifest, and copies config/*.json plus its helper scripts into the build output.
Services/ConfigService.csloads the shared JSON catalogs and tolerates control characters inside upstream script strings.Services/TweakEngine.csapplies tweaks and records registry snapshots in%LocalAppData%\AspectUtils\state\applied-tweaks.jsonso changes can be undone.Services/PackageManagerService.csdrives WinGet and Chocolatey for install, upgrade, and uninstall.Services/FeatureService.csand the scripts underdesktop/Scripts/cover Windows features, fixes, and update profiles.Views/contains the seven pages: Dashboard, Install apps, Tweaks, Fixes, Features, Updates, and Activity log.- AppX package management and Win11 Creator remain PowerShell-only in the current version.
High-Level Architecture
Section titled “High-Level Architecture”┌─────────────────────────────────────────────────────┐│ A$PECT UTILS GUI ││ (WPF XAML Interface) │└──────────────────┬──────────────────────────────────┘ │ ┌─────────┴─────────┐ │ │┌────────▼──────┐ ┌───────▼────────┐│ Public APIs │ │ Private APIs ││ (User-facing)│ │ (Internal) │└───────┬───────┘ └───────┬────────┘ │ │ └────────┬──────────┘ │ ┌────────────▼────────────┐ │ Configuration Files │ │ (JSON definitions) │ └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ External Tools │ │ (WinGet, Chocolatey) │ └─────────────────────────┘Project Structure
Section titled “Project Structure”Directory Layout
Section titled “Directory Layout”AspectUtils/├── Compile.ps1 # Build script that combines all files├── AspectUtils.ps1 # Compiled output (generated)├── scripts/│ ├── main.ps1 # Entry point and GUI initialization│ └── start.ps1 # Startup logic├── functions/│ ├── private/ # Internal helper functions│ │ ├── Get-WinUtilVariables.ps1│ │ ├── Install-WinUtilWinget.ps1│ │ └── ...│ ├── public/ # User-facing functions│ │ ├── Initialize-WPFUI.ps1│ │ └── ...├── config/ # Shared JSON configuration files│ ├── applications.json # Application definitions│ ├── tweaks.json # Tweak definitions│ ├── feature.json # Windows feature definitions│ └── preset.json # Preset configurations├── xaml/│ └── inputXML.xaml # GUI layout definition├── desktop/ # Native WPF desktop app (.NET 8)│ ├── AspectUtils.Desktop.csproj│ ├── Services/ # Config, tweaks, packages, undo│ ├── Views/ # Install, Tweaks, Fixes, Features, Updates, Log│ └── Scripts/ # Fix and update helper scripts└── docs/ # DocumentationKey Components
Section titled “Key Components”1. Compile.ps1
Section titled “1. Compile.ps1”Purpose: Combines all separate script files into a single AspectUtils.ps1 for distribution.
Process:
- Reads all function files from
/functions/ - Includes configuration JSON files
- Embeds XAML GUI definition
- Combines into a single script
- Outputs
AspectUtils.ps1
Why: Makes distribution easier (single file) and improves load time.
2. scripts/main.ps1
Section titled “2. scripts/main.ps1”Purpose: Entry point that manages the run.
Responsibilities:
- Load configurations
- Run the headless
-Presetand-Configpaths - Start the interface on a dedicated STA runspace and wait for it
- Report anything the interface thread failed with, then clean up
The interface itself lives in Start-WinUtilUserInterface, not here. See Threading Model.
3. functions/public/
Section titled “3. functions/public/”Purpose: User-facing functions that implement main features.
Key Functions:
Initialize-WPFUI.ps1: Sets up the GUIInvoke-WPFTweak*: Applies system tweaksInvoke-WPFFeature*: Enables Windows featuresInstall-WinUtilProgram*: Installs applications
Naming Convention: Functions start with WPF or Winutil to be loaded into the runspace.
4. functions/private/
Section titled “4. functions/private/”Purpose: Internal helper functions not directly called by users.
Key Functions:
Get-WinUtilVariables.ps1: Retrieves UI element referencesInstall-WinUtilWinget.ps1: Ensures WinGet is installedGet-WinUtilCheckBoxes.ps1: Gets checkbox statesInvoke-WinUtilCurrentSystem.ps1: Gets system information
5. config/*.json
Section titled “5. config/*.json”Purpose: Define available applications, tweaks, and features declaratively.
Files:
applications.json: Application definitions with WinGet/Choco IDstweaks.json: Registry tweaks and their undo actionsfeature.json: Windows features that can be enabled/disabledpreset.json: Predefined tweak combinationsdns.json: DNS provider configurations
6. xaml/inputXML.xaml
Section titled “6. xaml/inputXML.xaml”Purpose: WPF GUI layout and design.
Structure:
- Buttons with event handlers
- TextBoxes for input
- CheckBoxes for options
- ListBoxes for selections
Win11 Creator Architecture
Section titled “Win11 Creator Architecture”The Win11 Creator is a specialized subsystem within Winutil that creates customized Windows 11 ISOs. It operates independently of the main package installation and tweak system.
Win11 Creator Components
Section titled “Win11 Creator Components”Core Functions (functions/private/):
-
Invoke-WinUtilISO.ps1: Main orchestrator containing ISO workflow and helper functionsInvoke-WinUtilISOBrowse: ISO file selection dialogInvoke-WinUtilISOMountAndVerify: Validates and mounts ISO, verifies it is an official Windows 11 ISOInvoke-WinUtilISOModify: Launches modification in background runspaceInvoke-WinUtilISOExport: Builds bootable ISO viaoscdimg.exeInvoke-WinUtilISOCheckExistingWork: Recovers incomplete work sessionsInvoke-WinUtilISOCleanAndReset: Cleans up temp directories and resets UIWrite-WinUtilISOLog: Posts log messages to UI status box and session logSet-WinUtilISOStep: Controls wizard navigation stateGet-WinUtilEditionIdFromName: Maps edition display names to setup edition IDsInvoke-WinUtilRobocopy: Copies files with exit code verificationFind-WinUtilOscdimg/Get-WinUtilOscdimgPath: Locates or installsoscdimg.exe
-
Invoke-WinUtilISOUSB.ps1: USB drive detection and formattingInvoke-WinUtilISORefreshUSBDrives: Enumerates USB drivesGet-WinUtilFreeDriveLetter: Finds available drive lettersInvoke-WinUtilISOWriteUSB: Formats USB drive as GPT/FAT32, splits WIM if needed, and copies files
-
Invoke-WinUtilISOScript.ps1: Prepares setup media and customizations- Stages AppX removal (19 bloatware packages) into
autounattend.xmlfor first logon - Applies 50+ registry tweaks during Windows Setup and first logon; removes scheduled tasks at first logon
- Triggers OneDrive uninstall during first logon
- Stages setup script fallbacks to
sources\$OEM$\$$\Setup\Scripts\ - Pins selected edition in
autounattend.xmland writessources\ei.cfg - Injects eligible current system drivers into WIM images if enabled (WIM mount is only used for driver servicing)
- Stages AppX removal (19 bloatware packages) into
Win11 Creator Data Flow
Section titled “Win11 Creator Data Flow”User selects official Windows 11 ISO ↓Invoke-WinUtilISOBrowse → OpenFileDialog, validates file size ↓Invoke-WinUtilISOMountAndVerify ├─ Mount ISO via Mount-DiskImage ├─ Verify install.wim or install.esd exists ├─ Check for "Windows 11" in image metadata ├─ Extract available editions (Home, Pro, Enterprise, etc.) └─ Store ISO path, drive letter, WIM path, image info in $sync ↓User optionally enables the Driver Injection checkbox ↓Invoke-WinUtilISOModify (runs in background runspace) ├─ Create work directory: ~WinUtil_Win11ISO_[timestamp] ├─ Copy ISO contents to disk via robocopy (~5-6 GB) ├─ Invoke-WinUtilISOScript: │ ├─ Generate autounattend.xml with Windows PE and specialize safeguards │ ├─ Add first-logon script (19 AppX removals, registry tweaks, OneDrive uninstall) │ ├─ Pin selected edition index in autounattend.xml (/IMAGE/INDEX) │ ├─ Stage setup script fallbacks to sources\$OEM$\$$\Setup\Scripts\ │ ├─ Write sources\ei.cfg and remove stale sources\PID.txt │ └─ If driver injection enabled: │ ├─ Require install.wim; install.esd cannot accept driver injection │ ├─ Export current system drivers via DISM │ ├─ Exclude stale duplicate packages │ ├─ Inject storage drivers into boot.wim index 2 and eligible drivers into install.wim │ └─ Validate WIM metadata before and after injection ├─ Dismount source ISO └─ Report completion, enable export options ↓Invoke-WinUtilISOExport (user chooses output) ├─ Option 1: Save as ISO │ ├─ Build bootable ISO via oscdimg.exe (BIOS/UEFI dual-boot) │ └─ Output: Win11_Modified_[date].iso (close to the source ISO size) │ └─ Option 2: Write to USB ├─ Format USB as GPT ├─ Create single FAT32 partition (capped at 32 GB) ├─ Split install.wim into .swm files if > 3.8 GB ├─ Reject install.esd files of 4 GB or more ├─ Copy files via robocopy └─ Output: Bootable USB (minimum 8 GB) ↓Invoke-WinUtilISOCleanAndReset (optional) ├─ Dismount any open WIM mounts with discard ├─ Delete temp working directory (~10-15 GB) └─ Reset UI to initial stateWin11 Creator Validation & Safety
Section titled “Win11 Creator Validation & Safety”ISO Validation:
- Only accepts official Microsoft Windows 11 ISOs
- Validates presence of install.wim or install.esd
- Checks image metadata for “Windows 11” string
- Rejects custom, modified, or non-Windows 11 ISOs
WIM Metadata Validation:
- During driver injection,
Assert-WinUtilISOWimMetadatavalidates critical fields (Languages,Installation,Edition,ProductSuite,ProductType) before and after WIM servicing - Driver injection stops before export if the required metadata is missing or changes
- Without driver injection, WinUtil preserves the original installation image and skips WIM metadata validation
Edition Pinning & Setup Fallback:
- Writes
sources\ei.cfgand removessources\PID.txtso setup does not use mismatched OEM product keys - Pins the selected edition index in
autounattend.xml(/IMAGE/INDEX) - Stages fallback setup scripts under
sources\$OEM$\$$\Setup\Scripts\withUseConfigurationSetenabled
Work Session Recovery:
- Auto-detects incomplete work from previous sessions
- Allows resuming the export step without re-running selection and modification
- Prevents redundant modifications
Modification Safety:
- Windows PE and specialize set hardware bypass and setup safeguards;
WinUtil-PostInstall.ps1applies additional registry changes at first logon - Original ISO never modified; only working copy
- Logged to the WinUtil session log and the live UI status panel
- DISM handles image dismount with automatic cleanup and discard on error
Win11 Creator Registry Tweaks
Section titled “Win11 Creator Registry Tweaks”The Invoke-WinUtilISOScript function applies 50+ registry tweaks during setup and first logon:
Hardware Bypass:
- TPM 2.0 check bypass
- Secure Boot requirement bypass
- CPU compatibility bypass
- RAM requirement bypass
- Storage check bypass
Privacy & Telemetry:
- Disable advertising ID
- Disable tailored experiences
- Disable input personalization
- Disable speech online privacy
- Disable cloud content suggestions
- Disable app suggestion subscriptions
- Remove CEIP, Appraiser, WaaSMedic, etc.
OOBE & Setup:
- Enable local account setup
- Skip Microsoft account requirement
- Dark mode by default
- Empty taskbar and Start Menu
Post-Setup Installations:
- Prevent DevHome auto-installation
- Prevent new Outlook Mail app installation
- Prevent Teams auto-installation
System Features:
- Disable BitLocker and device encryption
- Disable Chat icon from the Taskbar
- Disable OneDrive folder backup
- Disable Copilot
- Disable Windows Update during OOBE (re-enabled at first login)
Driver Injection Feature
Section titled “Driver Injection Feature”Optional Enhancement: When enabled, exports all drivers from the running system and injects them into:
install.wim(main OS image), excluding stale duplicatesboot.wimindex 2 (Windows Setup PE environment), storage controllers only (SCSIAdapter/HDC)
Use Case: Enables offline installation on systems with missing drivers.
Disk Space Requirements
Section titled “Disk Space Requirements”- Temporary working directory: ~10-15 GB
- Original ISO: 4-6 GB
- Modified ISO: close to the source ISO size
- Total needed: ~25 GB for safe operation
Data Flow
Section titled “Data Flow”Application Installation Flow
Section titled “Application Installation Flow”User clicks "Install" ↓Get-WinUtilCheckBoxes → Retrieves selected apps ↓For each selected app: ↓Check if WinGet/Choco is installed ↓Install-WinUtilWinget/Choco (if needed) ↓Install-WinUtilProgramWinget/Choco → Install app ↓Update UI with progress ↓Display completion messageTweak Application Flow
Section titled “Tweak Application Flow”User selects tweaks and clicks "Run Tweaks" ↓Get-WinUtilCheckBoxes → Get selected tweaks ↓For each selected tweak: ↓Load tweak definition from tweaks.json ↓Invoke-WPFTweak → Apply registry/service changes ↓Log changes ↓Store original values (for undo) ↓Update UI ↓Display completionUndo Tweak Flow
Section titled “Undo Tweak Flow”User selects tweaks and clicks "Undo" ↓Get-WinUtilCheckBoxes → Get selected tweaks ↓For each tweak: ↓Retrieve "OriginalState" from tweak definition ↓Invoke-WPFUndoTweak → Restore original values ↓Remove from the applied tweaks log ↓Update UIConfiguration File Format
Section titled “Configuration File Format”applications.json Structure
Section titled “applications.json Structure”{ "WPFInstall<AppName>": { "category": "Browsers", "choco": "googlechrome", "content": "Google Chrome", "description": "Google Chrome browser", "link": "https://chrome.google.com", "winget": "Google.Chrome" }}Fields:
category: Which section in the Install tabcontent: Display name in GUIdescription: Tooltip/description textwinget: WinGet package IDchoco: Chocolatey package namelink: Official website
tweaks.json Structure
Section titled “tweaks.json Structure”{ "WPFTweaksTelemetry": { "Content": "Disable Telemetry", "Description": "Disables Microsoft Telemetry", "category": "Essential Tweaks", "panel": "1", "registry": [ { "Path": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\DataCollection", "Name": "AllowTelemetry", "Type": "DWord", "Value": "0", "OriginalValue": "1" } ] }}Fields:
Content: Display nameDescription: What it doescategory: Essential/Advanced/Customizeregistry: Registry changes to makeregistry[].Values: Per-state values for a registry-backed comboboxregistry[].DefaultValue: Effective value when the registry entry is absentservice: Services to changeOriginalValue/State: For undo functionality
Threading Model
Section titled “Threading Model”WinUtil runs on three kinds of thread, and each has one job:
| Thread | Runspace | Responsibility |
|---|---|---|
| Main | The one the script started in | Start the interface, wait for it, surface its errors, clean up |
| Interface | $sync.UIRunspace, a dedicated STA runspace |
Own the window. Paint and dispatch, nothing else |
| Workers | $sync.runspace, a shared pool |
Run everything long: installs, tweaks, features, AppX, Win11 Creator |
All three are created from the same starting point, New-WinUtilSessionState, which carries
$sync, the compiled script’s globals, and every WinUtil function. That is what lets any
thread call any helper without the caller injecting function definitions.
# main.ps1 - the interface gets its own thread$sync.UIRunspace = [runspacefactory]::CreateRunspace($Host, (New-WinUtilSessionState))$sync.UIRunspace.ApartmentState = "STA"$sync.UIRunspace.Open()
$uiShell = [powershell]::Create()$uiShell.Runspace = $sync.UIRunspace[void]$uiShell.AddScript({ Start-WinUtilUserInterface })$uiHandle = $uiShell.BeginInvoke()$uiHandle.AsyncWaitHandle.WaitOne()Why: the window never blocks on work, and a failure on the interface thread is reported instead of disappearing.
Long-Running Work
Section titled “Long-Running Work”Every long action goes through Start-WinUtilJob, which owns everything a running operation
needs: refusing to start while another job runs, the busy flag ($sync.ActiveJob), the progress
bar and taskbar item, the boxed console banner, a start/finish/failure line in the log, and
restoring the interface in a finally whatever happens.
Invoke-WPFButton decides what counts as a long action. Anything that changes the system gets a
job; anything that only changes what the interface is showing runs on the interface thread.
That single classification is why no workflow arranges its own progress, banner or busy state.
Start-WinUtilJob -Name "Features" -Description "Installing Windows Features" -Parameters @{ Features = @($sync.selectedFeatures)} -ScriptBlock { param($Features)
$total = @($Features).Count $completed = 0 foreach ($feature in $Features) { $completed++ Step-WinUtilJob -Status "Installing $feature ($completed/$total)" -Percent ([int](($completed / $total) * 100)) Invoke-WinUtilFeatureInstall $feature }}The body only has to do the work and call Step-WinUtilJob. Anything it throws is
caught, logged, and shown on the taskbar as a failure.
Values, not closures: the body is rebuilt inside the worker from its text, so it receives
what it needs through -Parameters rather than capturing the caller’s variables.
WPF Event Handling
Section titled “WPF Event Handling”Events are wired up via XAML element names:
# Get all named elements$sync.keys | ForEach-Object { if($sync.$_.GetType().Name -eq "Button") { $sync.$_.Add_Click({ $button = $sync.$($args[0].Name) & "Invoke-$($args[0].Name)" }) }}Convention: Button named WPFInstallButton calls function Invoke-WPFInstallButton.
Package Manager Integration
Section titled “Package Manager Integration”WinGet Integration
Section titled “WinGet Integration”# Check if installedif (!(Get-Command winget -ErrorAction SilentlyContinue)) { Install-WinUtilWinget}
# Install packagewinget install --id $app.winget --silent --accept-source-agreementsChocolatey Integration
Section titled “Chocolatey Integration”# Check if installedif (!(Get-Command choco -ErrorAction SilentlyContinue)) { Install-WinUtilChoco}
# Install packagechoco install $app.choco -yError Handling And Logging
Section titled “Error Handling And Logging”A job body does not need its own error handling. Start-WinUtilJob catches whatever the body
throws, logs it, and marks the run as failed on the taskbar, so a failure can never leave the
interface stuck busy. Only catch inside a body when you have something specific to do first,
such as cleaning up a mounted image, and then rethrow.
Write-WinUtilLog -Level "ERROR" -Component "Install" -Message "winget install failed: $($_.Exception.Message)"Entries and console diagnostics go to the same
%LocalAppData%\AspectUtils\logs\aspectutils_<timestamp>.log session file. While Start-Transcript
owns that file, Write-WinUtilLog writes through the host so the transcript captures the entry
without a competing direct file write.
Configuration Loading
Section titled “Configuration Loading”At startup, Winutil loads all configurations:
# Load JSON configs$sync.configs = @{}$sync.configs.applications = Get-Content "config/applications.json" | ConvertFrom-Json$sync.configs.tweaks = Get-Content "config/tweaks.json" | ConvertFrom-Json$sync.configs.features = Get-Content "config/feature.json" | ConvertFrom-JsonSync Hash: $sync hashtable shares state across runspaces.
UI Update Pattern
Section titled “UI Update Pattern”Controls may only be touched from the thread that owns the window, so background work reaches
them through Invoke-WPFUIThread:
Invoke-WPFUIThread -Parameters @{ Count = $installed.Count } -ScriptBlock { param($Count) $sync.WPFselectedAppsButton.Content = "Selected Apps: $Count"}Add -Async to post the update instead of waiting for it. Step-WinUtilJob and the
Win11 Creator status log use that so a per-item update never stalls the worker.
Values, not closures: the body is rebuilt inside the interface runspace, so it takes what it
needs through -Parameters. Handing over a scriptblock from a worker instead would keep that
worker’s session state, which loses the caller’s variables on an async post and costs roughly
twenty times as much per command - enough to turn a checkbox refresh into a visible freeze.
Adding New Features
Section titled “Adding New Features”Adding a New Application
Section titled “Adding a New Application”- Edit
config/applications.json:
{ "WPFInstallNewApp": { "category": "Utilities", "content": "New App", "description": "Description of new app", "winget": "Publisher.AppName", "choco": "appname" }}- Recompile:
.\Compile.ps1 - The app appears automatically in the Install tab
Adding a New Tweak
Section titled “Adding a New Tweak”- Edit
config/tweaks.json:
{ "WPFTweaksNewTweak": { "Content": "New Tweak", "Description": "What it does", "category": "Essential Tweaks", "registry": [ { "Path": "HKLM:\\Path\\To\\Key", "Name": "ValueName", "Type": "DWord", "Value": "1", "OriginalValue": "0" } ] }}- Recompile:
.\Compile.ps1 - Tweak appears in the Tweaks tab
Adding a New Function
Section titled “Adding a New Function”- Create file in
functions/public/orfunctions/private/:
function Invoke-WPFNewFeature { <# .SYNOPSIS Does something new #> # Implementation}- File naming must include “WPF” or “Winutil” to load
- Recompile:
.\Compile.ps1
Testing
Section titled “Testing”Manual Testing
Section titled “Manual Testing”# Compile and run with -run flag.\Compile.ps1 -runAutomated Tests
Section titled “Automated Tests”Tests are in /pester/:
configs.Tests.ps1: Validates JSON configurationsfunctions.Tests.ps1: Tests PowerShell functions
Run tests:
Install-Module -Name Pester -RequiredVersion 5.8.0 -Scope CurrentUser -Force -SkipPublisherCheckImport-Module Pester -RequiredVersion 5.8.0 -ForceInvoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CIBuild Process
Section titled “Build Process”Development Build
Section titled “Development Build”.\Compile.ps1Outputs AspectUtils.ps1 in the root directory.
Production Release
Section titled “Production Release”- Tag release in Git
- GitHub Actions builds and uploads
AspectUtils.ps1 - Release appears on GitHub Releases
- Users download the release asset or run it from the repository
Dependencies
Section titled “Dependencies”Required:
- PowerShell 5.1+
- .NET Framework 4.5+
- Windows 11
Optional (auto-installed):
- WinGet (Windows Package Manager)
- Chocolatey
Performance Considerations
Section titled “Performance Considerations”Optimization Strategies:
- Lazy-load configurations (only when needed)
- Use runspaces for long operations
- Cache expensive lookups
- Minimize registry reads/writes
- Batch operations when possible
Security Considerations
Section titled “Security Considerations”Safety Measures:
- All operations logged
- Registry backups for undo
- No credential storage
- Open source (auditable)
- Digitally signed (future)
Contributing Guidelines
Section titled “Contributing Guidelines”Code Standards:
- Use proper PowerShell cmdlet naming (Verb-Noun)
- Include comment-based help
- Follow existing code style
- Test thoroughly before PR
- Document significant changes
File Naming:
- Public functions:
Invoke-WPF*.ps1orInvoke-Winutil*.ps1 - Private functions:
Get-WinUtil*.ps1or verb-WinUtil*.ps1` - Must include “WPF” or “Winutil” to load
Future Architecture Plans
Section titled “Future Architecture Plans”Roadmap Considerations:
- Plugin system for community extensions
- Config import/export
- Cloud sync for configurations
- Enhanced logging dashboard
- Modular compilation (choose features)
Related Documentation
Section titled “Related Documentation”- Contributing Guide - How to contribute code
- User Guide - End-user documentation
- Win11 Creator Guide - Building customized Windows 11 ISOs
- FAQ - Common questions
Additional Resources
Section titled “Additional Resources”- GitHub Repository: flickfpz/AspectUtils
- PowerShell Docs: Microsoft Docs
- WPF Guide: WPF Documentation
Last Updated: January 2026 Maintainers: A$PECT UTILS contributors
