mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-10 01:51:18 +10:00
Compare commits
12
Commits
43b6721df7
...
26.07.02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93dc23dd66 | ||
|
|
916dc761ba | ||
|
|
2cb20893fc | ||
|
|
c3b4f85fce | ||
|
|
104c763132 | ||
|
|
8062815655 | ||
|
|
58be808823 | ||
|
|
77dc64cbea | ||
|
|
72d47ba020 | ||
|
|
b6b479685a | ||
|
|
c7dfbeb7fe | ||
|
|
7c71a39f7a |
@@ -27,13 +27,16 @@ jobs:
|
||||
- name: Install Pester
|
||||
run: |
|
||||
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
|
||||
Install-Module -Name Pester -Force -SkipPublisherCheck -AllowClobber
|
||||
Install-Module -Name Pester -RequiredVersion 5.8.0 -Force -SkipPublisherCheck -AllowClobber
|
||||
Import-Module Pester -RequiredVersion 5.8.0 -Force
|
||||
Get-Module Pester | Select-Object Name, Version, Path
|
||||
shell: pwsh
|
||||
|
||||
- name: Run Pester tests
|
||||
run: |
|
||||
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
|
||||
Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed
|
||||
Import-Module Pester -RequiredVersion 5.8.0 -Force
|
||||
Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CI
|
||||
|
||||
shell: pwsh
|
||||
env:
|
||||
|
||||
@@ -5,6 +5,7 @@ Microsoft.PowerShell.ConsoleHost.dll
|
||||
winutil.exe.config
|
||||
winutil.ps1
|
||||
binary/
|
||||
testResults.xml
|
||||
|
||||
# general software/os specific
|
||||
desktop.ini
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# AGENTS.md
|
||||
|
||||
Drop-in operating instructions for coding agents. Read this file before every task.
|
||||
|
||||
**Working code only. Finish the job. Plausibility is not correctness.**
|
||||
|
||||
This repository follows the AGENTS.md convention: these instructions are for Codex, Claude Code, Cursor, Windsurf, Copilot, Aider, Devin, Amp, and other coding agents that read `AGENTS.md`.
|
||||
|
||||
## 0. Non-Negotiables
|
||||
|
||||
These rules override everything else in this file when in conflict:
|
||||
|
||||
1. **Do not edit `winutil.ps1` directly.** It is generated build output. Change source files and compile.
|
||||
2. **Do not commit `winutil.ps1`.** It is ignored locally and generated by GitHub Actions for releases.
|
||||
3. **Never fabricate.** Do not invent file paths, function names, command output, test results, commit hashes, or API behavior. Read the file or run the command.
|
||||
4. **Disagree when the premise is wrong.** Say what is wrong before acting on it.
|
||||
5. **Stop when genuinely ambiguous.** If two interpretations would produce materially different diffs, ask before editing.
|
||||
6. **Touch only what the task requires.** No drive-by refactors, formatting sweeps, or unrelated cleanup.
|
||||
7. **Verify before saying done.** A plausible-looking diff is not proof.
|
||||
|
||||
## 1. Project Context
|
||||
|
||||
WinUtil is a Windows PowerShell utility with a WPF interface. The repository is maintained as modular source, but the distributed artifact is one compiled PowerShell script.
|
||||
|
||||
### Stack
|
||||
|
||||
- Language: Windows PowerShell / PowerShell.
|
||||
- UI: WPF via `xaml/inputXML.xaml`.
|
||||
- Configuration: JSON files under `config/`.
|
||||
- Tests: Pester tests under `pester/`.
|
||||
- Lint: PowerShell Script Analyzer with settings in `lint/PSScriptAnalyser.ps1`.
|
||||
- Docs: Hugo site under `docs/`.
|
||||
- Release artifact: generated root `winutil.ps1`.
|
||||
|
||||
### Key Commands
|
||||
|
||||
- Compile:
|
||||
```powershell
|
||||
.\Compile.ps1
|
||||
```
|
||||
- Compile and run GUI:
|
||||
```powershell
|
||||
.\Compile.ps1 -Run
|
||||
```
|
||||
- Run tests:
|
||||
```powershell
|
||||
Import-Module Pester -RequiredVersion 5.8.0 -Force
|
||||
Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed
|
||||
```
|
||||
- Run Script Analyzer with project settings when available:
|
||||
```powershell
|
||||
Invoke-ScriptAnalyzer -Path . -Settings .\lint\PSScriptAnalyser.ps1 -Recurse
|
||||
```
|
||||
|
||||
Prefer the narrowest useful verification while iterating. Use the full relevant check before finishing.
|
||||
|
||||
## 2. Source Of Truth
|
||||
|
||||
Make durable changes only in files consumed by `Compile.ps1` or in documentation/test files:
|
||||
|
||||
- `scripts/start.ps1` for startup/bootstrap code.
|
||||
- `functions/public/*.ps1` for UI-facing and user-facing workflows.
|
||||
- `functions/private/*.ps1` for internal helpers.
|
||||
- `config/*.json` for applications, tweaks, features, DNS, presets, navigation, themes, and related declarative data.
|
||||
- `xaml/inputXML.xaml` for the WPF UI layout.
|
||||
- `tools/autounattend.xml` for the embedded unattended Windows setup template.
|
||||
- `scripts/main.ps1` for the final entrypoint and GUI initialization logic appended during compile.
|
||||
- `pester/*.Tests.ps1` for automated checks.
|
||||
- `docs/` for Hugo documentation.
|
||||
|
||||
If behavior changes require the compiled script to change, update these source files and run `.\Compile.ps1` only to verify generation.
|
||||
|
||||
## 3. Build Model
|
||||
|
||||
`Compile.ps1` combines the repository sources into `winutil.ps1` in this order:
|
||||
|
||||
1. Read `scripts/start.ps1` and replace `#{replaceme}` with the current `yy.MM.dd` build date.
|
||||
2. Append every file under `functions/` recursively.
|
||||
3. Convert each `config/*.json` file into embedded `$sync.configs` objects.
|
||||
4. Special-case `config/applications.json` so keys receive the `WPFInstall` prefix in compiled config.
|
||||
5. Embed `xaml/inputXML.xaml` into `$inputXML`.
|
||||
6. Embed `tools/autounattend.xml` into `$WinUtilAutounattendXml`.
|
||||
7. Append `scripts/main.ps1`.
|
||||
8. Write the result to root `winutil.ps1`.
|
||||
|
||||
Because the final script is concatenated, do not rely on runtime module imports or source-relative dot-sourcing unless the compiled script will also contain the required code/data.
|
||||
|
||||
## 4. Before Editing
|
||||
|
||||
- State the plan in one or two sentences before editing. For non-trivial work, include the verification you intend to run.
|
||||
- Read the files you will touch and the files that call them.
|
||||
- Match existing patterns even when a different greenfield design would be cleaner.
|
||||
- Surface assumptions when they affect behavior, compatibility, or user data.
|
||||
- If two approaches have meaningful tradeoffs, name them before choosing. Trivial tasks can proceed directly.
|
||||
|
||||
## 5. Coding Guidelines
|
||||
|
||||
- Prefer the minimum code that solves the stated problem.
|
||||
- Keep PowerShell functions in one function file when practical, with the file name matching the primary function name.
|
||||
- Use approved PowerShell verb-noun names and follow the existing `WPF` / `WinUtil` naming conventions.
|
||||
- Keep UI event handler names aligned with XAML element names. A button named `WPFExampleButton` is typically handled by `Invoke-WPFExampleButton`.
|
||||
- Use `$sync` for shared state and UI references, consistent with the existing runspace model.
|
||||
- Update WPF controls through the UI dispatcher when running work in a background runspace.
|
||||
- Keep config-driven features in JSON when they fit the existing schema instead of hard-coding lists in PowerShell.
|
||||
- Preserve undo/original-state data for tweaks so users can reverse changes.
|
||||
- Do not add abstractions, configurability, hooks, or "future extensibility" unless the task needs them now.
|
||||
- Clean up orphans created by your own changes, such as unused variables or functions made obsolete by the edit.
|
||||
- Avoid broad formatting-only edits, especially in JSON config files, XAML, docs, and generated output.
|
||||
|
||||
## 6. Runtime And Safety Rules
|
||||
|
||||
- WinUtil performs system-level Windows changes. Treat registry, services, AppX removal, package manager, Windows Update, ISO, and unattended setup changes as high-risk.
|
||||
- Prefer existing helper functions for WinGet, Chocolatey, registry, services, progress, and UI updates.
|
||||
- Keep tweaks reversible where the schema supports it by including original values or original states.
|
||||
- Never modify a user's original ISO in-place; follow existing copy/mount/export patterns.
|
||||
- Avoid storing credentials, secrets, or machine-specific paths in repo files.
|
||||
- Preserve logging and user feedback patterns for long-running or destructive operations.
|
||||
|
||||
## 7. Surgical Changes
|
||||
|
||||
- Do not improve adjacent code, comments, formatting, imports, or docs unless required.
|
||||
- Do not refactor working code because you are already in the file.
|
||||
- Do not delete pre-existing dead code unless asked; mention it in the summary if relevant.
|
||||
- Keep diffs reviewable. Every changed line should trace to the user's request.
|
||||
- If a change starts spreading across unrelated areas, pause and reassess the plan.
|
||||
|
||||
## 8. Verification
|
||||
|
||||
Define success in terms that can be checked, then check it.
|
||||
|
||||
- For compile/build changes, run `.\Compile.ps1`.
|
||||
- For GUI behavior changes, run `.\Compile.ps1 -Run` when practical and verify the affected path manually.
|
||||
- For config changes, run the compile check and relevant Pester tests.
|
||||
- For function changes, run the relevant Pester tests or add/update focused tests when practical.
|
||||
- For docs-only changes, proofread the changed files and skip runtime tests unless docs generation is affected.
|
||||
- Read command output. Do not report tests as passing unless they actually passed.
|
||||
- If verification fails, fix the cause rather than weakening the test.
|
||||
|
||||
If a check cannot be run, say exactly why and what residual risk remains.
|
||||
|
||||
## 9. Generated Files And Git Hygiene
|
||||
|
||||
- Treat local `winutil.ps1` changes as disposable compile output.
|
||||
- Never stage or commit `winutil.ps1`, `docs/public/`, `docs/resources/`, `binary/`, editor folders, or other ignored build artifacts.
|
||||
- Do not remove `.gitignore` rules that keep generated artifacts out of Git.
|
||||
- Before finishing, check `git status --short` and separate your changes from pre-existing user changes.
|
||||
- Do not revert user changes unless explicitly asked.
|
||||
- Commit messages, when requested, should be descriptive: short subject under 72 characters, body explaining why when needed.
|
||||
|
||||
## 10. Documentation Expectations
|
||||
|
||||
- Update `docs/content/` when user-facing behavior changes.
|
||||
- Update developer docs when architecture, build flow, config schema, or contribution workflow changes.
|
||||
- Keep README changes brief and high-level.
|
||||
- Put detailed user and developer documentation under `docs/`.
|
||||
- Keep `SPEC.md` aligned with build/runtime contract changes.
|
||||
|
||||
## 11. Communication Style
|
||||
|
||||
- Be direct and concise. Start with the answer or action.
|
||||
- No flattery, filler, ceremonial closings, or fake certainty.
|
||||
- Use bullets only when they improve scanning.
|
||||
- Report what changed, how it was verified, and anything not done.
|
||||
- If the user asks for a review, lead with findings and file/line references.
|
||||
|
||||
## 12. When To Ask
|
||||
|
||||
Ask before proceeding when:
|
||||
|
||||
- The request has two plausible interpretations and the choice materially changes behavior or files touched.
|
||||
- The change affects release generation, generated artifacts, migrations, or high-risk Windows behavior in a way the user did not specify.
|
||||
- You need credentials, secrets, production resources, or access you do not have.
|
||||
- The user's stated goal conflicts with the literal request.
|
||||
|
||||
Proceed without asking when:
|
||||
|
||||
- The task is trivial and reversible.
|
||||
- Ambiguity can be resolved by reading the code or running a local command.
|
||||
- The user already answered the question in this session.
|
||||
|
||||
## 13. Project Learnings
|
||||
|
||||
When the user corrects an agent approach, add or tighten one concrete rule here before ending the session. Keep this section short and prune rules that no longer matter.
|
||||
|
||||
- Keep `winutil.ps1` generated-only: change source files, compile to verify, and never stage the generated script.
|
||||
- Keep WinUtil runtime logging in the existing timestamped `%LocalAppData%\winutil\logs\winutil_*.log` session file; do not create a separate root `winutil.log`.
|
||||
- Import Pester 5.8.0 before running tests so `Invoke-Pester -Output Detailed -CI` does not resolve to Windows' inbox Pester 3.4.0.
|
||||
- Keep package install/uninstall process launches simple unless explicitly requested; do not add a separate stdout/stderr process logging helper for winget or Chocolatey.
|
||||
- When the active log file is owned by `Start-Transcript`, do not call `Add-Content` against that file; write to host output so the transcript captures the line in the same log file without recording a terminating-error diagnostic.
|
||||
- Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity.
|
||||
- Keep performance tracing helpers under `tools/perf`; include them in generated output only through `Compile.ps1 -Trace`.
|
||||
+40
-3
@@ -1,16 +1,46 @@
|
||||
param (
|
||||
[switch]$Run
|
||||
[switch]$Run,
|
||||
[switch]$Trace
|
||||
)
|
||||
|
||||
$OFS = "`r`n"
|
||||
|
||||
function Get-WinUtilTraceStrippedContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Content
|
||||
)
|
||||
|
||||
if ($Trace) {
|
||||
return $Content
|
||||
}
|
||||
|
||||
$filteredLines = $Content -split "`r?`n" | Where-Object {
|
||||
$_ -notmatch '^\s*(Start-WinUtilPerformanceTrace|Stop-WinUtilPerformanceTrace|Write-WinUtilPerformanceCheckpoint)\b'
|
||||
}
|
||||
|
||||
$filteredLines -join "`r`n"
|
||||
}
|
||||
|
||||
# Variable to sync between runspaces
|
||||
$sync = [Hashtable]::Synchronized(@{})
|
||||
$sync.configs = @{}
|
||||
|
||||
$script = (Get-Content -Path scripts\start.ps1) -replace '#{replaceme}', (Get-Date -Format 'yy.MM.dd')
|
||||
|
||||
$script += Get-ChildItem -Path functions -Recurse -File | Get-Content -Raw
|
||||
$script += Get-ChildItem -Path functions -Recurse -File | ForEach-Object {
|
||||
Get-WinUtilTraceStrippedContent -Content (Get-Content -Path $_.FullName -Raw)
|
||||
}
|
||||
|
||||
if ($Trace) {
|
||||
$script += Get-ChildItem -Path tools\perf -Filter *.ps1 -File | Get-Content -Raw
|
||||
|
||||
$script += @'
|
||||
$sync.PerformanceTraceEnabled = $true
|
||||
Start-WinUtilPerformanceTrace
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Config load start"
|
||||
'@
|
||||
}
|
||||
|
||||
Get-ChildItem config | ForEach-Object {
|
||||
$obj = Get-Content -Path $_.FullName -Raw | ConvertFrom-Json
|
||||
@@ -27,6 +57,13 @@ Get-ChildItem config | ForEach-Object {
|
||||
|
||||
$sync.configs[$_.BaseName] = $obj
|
||||
$script += "`$sync.configs.$($_.BaseName) = @'`r`n$json`r`n'@ | ConvertFrom-Json"
|
||||
if ($Trace) {
|
||||
$script += "`r`nWrite-WinUtilPerformanceCheckpoint -Name `"Config $($_.BaseName) loaded`""
|
||||
}
|
||||
}
|
||||
|
||||
if ($Trace) {
|
||||
$script += "`r`nWrite-WinUtilPerformanceCheckpoint -Name `"Config load complete`""
|
||||
}
|
||||
|
||||
$xaml = Get-Content -Path xaml\inputXML.xaml -Raw
|
||||
@@ -35,7 +72,7 @@ $script += "`$inputXML = @'`r`n$xaml`r`n'@"
|
||||
$autounattendXml = Get-Content -Path tools\autounattend.xml -Raw
|
||||
$script += "`$WinUtilAutounattendXml = @'`r`n$autounattendXml`r`n'@"
|
||||
|
||||
$script += Get-Content -Path scripts\main.ps1 -Raw
|
||||
$script += Get-WinUtilTraceStrippedContent -Content (Get-Content -Path scripts\main.ps1 -Raw)
|
||||
|
||||
Set-Content -Path winutil.ps1 -Value $script
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# SPEC.md
|
||||
|
||||
## Project Contract
|
||||
|
||||
WinUtil is a Windows PowerShell utility with a WPF interface. The repository is maintained as modular source files, but the released artifact is a single generated `winutil.ps1` script.
|
||||
|
||||
The compiled `winutil.ps1` is not source code for editing or review. It is generated by `Compile.ps1` and produced during release automation. All durable changes must be made to the source files that feed the compiler.
|
||||
|
||||
## Goals
|
||||
|
||||
- Provide a single-script Windows utility that can be launched from PowerShell.
|
||||
- Keep development modular enough for contributors to work on functions, config, UI, docs, and tooling independently.
|
||||
- Make install, tweak, feature, repair, update, and ISO workflows discoverable from the WPF UI.
|
||||
- Keep common lists and options declarative in JSON config where possible.
|
||||
- Preserve a repeatable compile process so local builds and GitHub Actions builds produce the distributable script from the same inputs.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- `winutil.ps1` is not hand-maintained.
|
||||
- The project is not structured as a PowerShell module at runtime.
|
||||
- The GUI is not a separate packaged desktop application in this repository's normal release path.
|
||||
- Generated files should not be reviewed as source changes.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `Compile.ps1`: build script that creates `winutil.ps1`.
|
||||
- `scripts/start.ps1`: startup/bootstrap segment used at the beginning of the compiled script.
|
||||
- `scripts/main.ps1`: main entrypoint appended at the end of the compiled script.
|
||||
- `functions/public/`: public/UI-facing PowerShell functions.
|
||||
- `functions/private/`: internal helper PowerShell functions.
|
||||
- `config/`: JSON configuration consumed at compile time and embedded into `$sync.configs`.
|
||||
- `xaml/inputXML.xaml`: WPF UI markup embedded into the compiled script.
|
||||
- `tools/autounattend.xml`: unattended setup XML embedded for Windows ISO workflows.
|
||||
- `pester/`: Pester tests for config and function checks.
|
||||
- `lint/PSScriptAnalyser.ps1`: PowerShell Script Analyzer settings.
|
||||
- `docs/`: Hugo documentation site.
|
||||
- `winutil.ps1`: ignored generated build artifact.
|
||||
|
||||
## Compile Specification
|
||||
|
||||
`Compile.ps1` must produce a standalone root `winutil.ps1` by combining all required project files.
|
||||
|
||||
The compile flow is:
|
||||
|
||||
1. Initialize shared state with `$sync = [Hashtable]::Synchronized(@{})` and `$sync.configs = @{}`.
|
||||
2. Read `scripts/start.ps1`.
|
||||
3. Replace `#{replaceme}` in startup code with the current `yy.MM.dd` build date.
|
||||
4. Append raw content from all files under `functions/` recursively.
|
||||
5. For every file in `config/`, parse JSON and embed it into `$sync.configs.<basename>`.
|
||||
6. Special-case `config/applications.json` so application keys are emitted with the `WPFInstall` prefix.
|
||||
7. Embed `xaml/inputXML.xaml` as `$inputXML`.
|
||||
8. Embed `tools/autounattend.xml` as `$WinUtilAutounattendXml`.
|
||||
9. Append `scripts/main.ps1`.
|
||||
10. Write the combined script to `winutil.ps1`.
|
||||
11. If `-Run` is supplied, execute the generated script.
|
||||
|
||||
The generated script must have everything it needs from repository sources embedded or appended by this process.
|
||||
|
||||
## Runtime Model
|
||||
|
||||
- WinUtil runs in PowerShell on Windows and uses WPF for the UI.
|
||||
- Shared mutable state is stored in `$sync`, including configs, UI element references, runspace state, selections, and progress.
|
||||
- Long-running operations should use runspaces or existing async patterns so the UI remains responsive.
|
||||
- UI updates from background work must be dispatched back to the WPF UI thread.
|
||||
- Declarative features such as apps, tweaks, presets, DNS providers, and navigation should stay in `config/*.json` unless code is required.
|
||||
|
||||
## UI And Event Contract
|
||||
|
||||
- UI layout lives in `xaml/inputXML.xaml`.
|
||||
- Named WPF controls are discovered and stored in `$sync`.
|
||||
- Button/action wiring follows existing naming conventions, where an element named like `WPFThingButton` maps to a function named like `Invoke-WPFThingButton`.
|
||||
- When adding controls, ensure the XAML name, config key, and PowerShell function names line up with the existing event system.
|
||||
|
||||
## Configuration Contract
|
||||
|
||||
Config files must remain valid JSON and compile cleanly through `ConvertFrom-Json`.
|
||||
|
||||
`config/applications.json` defines installable applications. Each application entry should include the fields expected by tests and UI code, such as package manager IDs, category, display content, description, and link.
|
||||
|
||||
`config/tweaks.json` defines Windows tweaks. Registry and service changes should include original values or original states when applicable so undo workflows can restore user systems.
|
||||
|
||||
Preset and navigation files should reference valid config keys. Avoid renaming config keys unless all presets, UI references, docs, and code paths are updated together.
|
||||
|
||||
## Safety Requirements
|
||||
|
||||
- Registry, service, package manager, Windows Update, AppX removal, and ISO operations can affect the host system. Changes must be explicit, reversible where practical, and consistent with existing logging and confirmation patterns.
|
||||
- Tweak changes should include undo metadata when the schema supports it.
|
||||
- Package installation should prefer existing WinGet and Chocolatey helper functions.
|
||||
- ISO workflows must not modify the user's original ISO file; they should work on copied/mounted content following existing patterns.
|
||||
|
||||
## Testing And CI
|
||||
|
||||
Expected validation for source changes:
|
||||
|
||||
- `.\Compile.ps1` verifies the compiler can generate `winutil.ps1`.
|
||||
- `.\Compile.ps1 -Run` compiles and launches the generated utility for manual GUI verification.
|
||||
- `Install-Module -Name Pester -RequiredVersion 5.8.0 -Scope CurrentUser -Force -SkipPublisherCheck` installs the supported Pester version.
|
||||
- `Import-Module Pester -RequiredVersion 5.8.0 -Force; Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CI` runs the Pester suite.
|
||||
- GitHub Actions also runs a compile check and PowerShell Script Analyzer with `lint/PSScriptAnalyser.ps1`.
|
||||
|
||||
The generated `winutil.ps1` may appear locally after compile. It remains ignored build output and must not be committed.
|
||||
|
||||
## Release Artifact
|
||||
|
||||
GitHub Actions is responsible for producing the release `winutil.ps1` from repository sources. A release should be considered valid only if the generated script came from the compile process, not from direct manual edits to `winutil.ps1`.
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
# Test TODO
|
||||
|
||||
Current suite status: broad parser, import, config JSON, compile, and runspace smoke coverage exists, but runtime behavior coverage is still very thin. Work from the top down; the first items are the highest-value gaps.
|
||||
|
||||
## P0 - Highest Priority
|
||||
|
||||
- [x] Add config reference integrity tests.
|
||||
- Verify every preset entry points to an existing tweak, feature, app, appx item, or supported action.
|
||||
- Verify `appnavigation.json` entries point to real tabs/panels and valid config groups.
|
||||
- Verify tweak, feature, DNS, appx, and theme entries include all fields used by the UI/rendering code.
|
||||
- Verify all embedded script strings in `config/*.json` parse as PowerShell when wrapped as scriptblocks.
|
||||
|
||||
- [x] Add XAML/control wiring tests.
|
||||
- Load `xaml/inputXML.xaml` as XML and verify expected named controls exist.
|
||||
- Verify controls referenced through `$sync["Name"]`, `$sync.Name`, or event handlers exist in XAML or are intentionally created dynamically.
|
||||
- Verify `Invoke-WPF*` handler names used by buttons/tabs correspond to real functions.
|
||||
- Verify core tabs, search controls, install panels, tweak panels, and Win11 Creator controls are present.
|
||||
|
||||
- [x] Add mock-based tests for registry and service helpers.
|
||||
- Cover `Set-WinUtilRegistry` set/remove paths with `Mock New-Item`, `Mock Set-ItemProperty`, and `Mock Remove-ItemProperty`.
|
||||
- Cover `Set-WinUtilService` for missing services, startup type changes, and no-op behavior.
|
||||
- Assert command parameters instead of touching the real registry or services.
|
||||
|
||||
- [x] Add package selection and package manager tests.
|
||||
- Cover `Get-WinUtilSelectedPackages` for winget-only, choco-only, mixed, `na`, empty, duplicate, and missing package entries.
|
||||
- Cover `Test-WinUtilPackageManager` with mocked `Get-Command`.
|
||||
- Cover `Install-WinUtilProgramWinget` and `Install-WinUtilProgramChoco` with mocked `Start-Process`, including install/uninstall arguments.
|
||||
|
||||
- [x] Add runspace behavior tests beyond the smoke test.
|
||||
- Cover no argument list, one named parameter, multiple named parameters, and scriptblock failures.
|
||||
- Assert callers receive a single `IAsyncResult`.
|
||||
- Verify failure output/errors can be retrieved by the owning PowerShell instance.
|
||||
- Add focused tests for public functions that call `Invoke-WPFRunspace` without executing their destructive inner work.
|
||||
|
||||
## P1 - Important Workflow Coverage
|
||||
|
||||
- [x] Add tweak execution orchestration tests.
|
||||
- Cover `Invoke-WinUtilTweaks` with mocked `Invoke-WinUtilScript`, `Set-WinUtilRegistry`, `Set-WinUtilService`, and `Set-WinUtilDNS`.
|
||||
- Verify undo mode uses `OriginalValue` and `OriginalType`.
|
||||
- Verify DNS provider and progress counters are passed through correctly.
|
||||
|
||||
- [x] Add install/uninstall workflow tests.
|
||||
- Cover `Invoke-WPFInstall` and `Invoke-WPFUnInstall` with mocked package sorting and mocked installers.
|
||||
- Verify empty selection prompts and exits.
|
||||
- Verify process-running guard prevents a second install/uninstall.
|
||||
- Verify taskbar/progress cleanup happens on success and error.
|
||||
|
||||
- [x] Add update profile tests.
|
||||
- Cover `Invoke-WPFUpdatesdisable`, `Invoke-WPFUpdatesdefault`, and `Invoke-WPFUpdatessecurity` with mocks for registry, services, scheduled tasks, and process calls.
|
||||
- Assert intended registry paths, values, service startup types, and scheduled task paths.
|
||||
- Keep all tests non-mutating.
|
||||
|
||||
- [x] Add AppX removal tests.
|
||||
- Cover `Remove-WinUtilAPPX` with mocked `Get-AppxPackage` and `Remove-AppxPackage`.
|
||||
- Cover `Invoke-WPFAppxRemoval` selection behavior and runspace parameter passing.
|
||||
- Verify empty selection is handled without removal calls.
|
||||
|
||||
- [x] Add UI selection/state helper tests.
|
||||
- Cover `Update-WinUtilSelections`, `Reset-WPFCheckBoxes`, `Invoke-WPFSelectedCheckboxesUpdate`, and `Invoke-WPFToggleAllCategories`.
|
||||
- Use small fake checkbox/control objects instead of WPF windows where possible.
|
||||
- Verify selected apps, tweaks, toggles, appx, and features stay in sync with checkbox state.
|
||||
|
||||
## P2 - Domain-Specific Coverage
|
||||
|
||||
- [x] Expand Win11 Creator tests.
|
||||
- Test edition name to edition ID mapping.
|
||||
- Test `ei.cfg` and `PID.txt` generation/removal behavior in a temp directory.
|
||||
- Test autounattend conversion preserves required nodes and removes unsupported product key paths.
|
||||
- Test driver injection branching with mocked DISM commands.
|
||||
- Test ISO export fallback when `oscdimg.exe` is missing and winget install fails.
|
||||
|
||||
- [x] Add preferences/theme tests.
|
||||
- Cover `Set-Preferences` loading, saving, old preference cleanup, and defaults.
|
||||
- Cover theme resource updates without opening the full GUI.
|
||||
- Verify theme config contains required brushes/colors used by the theme code.
|
||||
|
||||
- [x] Add search/filter tests.
|
||||
- Cover `Find-AppsByNameOrDescription` and `Find-TweaksByNameOrDescription` with fake UI item trees.
|
||||
- Verify empty search restores visibility.
|
||||
- Verify category labels and panels hide/show correctly.
|
||||
- Verify description and display-name matches both work.
|
||||
|
||||
- [x] Add compile contract tests.
|
||||
- Verify compiled config key transformation for `applications.json` adds `WPFInstall` prefixes.
|
||||
- Verify source ordering in `winutil.ps1`: start script, functions, configs, XAML, autounattend XML, main script.
|
||||
- Verify generated build date placeholder is replaced.
|
||||
- Verify generated script does not contain unresolved `#{replaceme}`.
|
||||
|
||||
## P3 - Nice To Have
|
||||
|
||||
- [ ] Add Script Analyzer as a failing CI gate once existing findings are triaged.
|
||||
- [ ] Add docs generation/link checks for `docs/content`.
|
||||
- [ ] Add coverage reporting in CI as informational output.
|
||||
- [ ] Add a lightweight test helper module for fake `$sync`, fake WPF controls, and common mocks.
|
||||
- [ ] Add regression tests whenever a production bug is fixed.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Tests must not mutate the real registry, services, scheduled tasks, package managers, AppX packages, USB disks, ISOs, or user profile state.
|
||||
- Prefer Pester mocks and temp directories for system-facing code.
|
||||
- Keep generated `winutil.ps1` ignored and never commit it.
|
||||
- Run the full gate before marking test work complete:
|
||||
|
||||
```powershell
|
||||
Import-Module Pester -RequiredVersion 5.8.0 -Force
|
||||
Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CI
|
||||
```
|
||||
@@ -161,6 +161,15 @@
|
||||
"winget": "Cemu.Cemu",
|
||||
"foss": true
|
||||
},
|
||||
"chatgpt": {
|
||||
"category": "Development",
|
||||
"choco": "na",
|
||||
"content": "ChatGPT Desktop",
|
||||
"description": "The official ChatGPT desktop app for Windows, distributed through the Microsoft Store.",
|
||||
"link": "https://apps.microsoft.com/detail/9nt1r1c2hh7j",
|
||||
"winget": "msstore:9NT1R1C2HH7J",
|
||||
"foss": false
|
||||
},
|
||||
"chatterino": {
|
||||
"category": "Communications",
|
||||
"choco": "chatterino",
|
||||
@@ -188,6 +197,33 @@
|
||||
"winget": "Hibbiki.Chromium",
|
||||
"foss": true
|
||||
},
|
||||
"cinebenchr23": {
|
||||
"category": "Pro Tools",
|
||||
"choco": "na",
|
||||
"content": "Cinebench R23",
|
||||
"description": "Cinebench R23 is a benchmark tool for comparing CPU rendering performance across systems.",
|
||||
"link": "https://www.maxon.net/en/cinebench",
|
||||
"winget": "Maxon.CinebenchR23",
|
||||
"foss": false
|
||||
},
|
||||
"claude": {
|
||||
"category": "Development",
|
||||
"choco": "claude",
|
||||
"content": "Claude Desktop",
|
||||
"description": "Anthropic's Claude desktop application for focused AI-assisted work and chat.",
|
||||
"link": "https://claude.ai/download",
|
||||
"winget": "Anthropic.Claude",
|
||||
"foss": false
|
||||
},
|
||||
"claude-code": {
|
||||
"category": "Development",
|
||||
"choco": "claude-code",
|
||||
"content": "Claude Code",
|
||||
"description": "Anthropic's agentic coding tool for terminal and IDE development workflows.",
|
||||
"link": "https://code.claude.com/",
|
||||
"winget": "Anthropic.ClaudeCode",
|
||||
"foss": false
|
||||
},
|
||||
"cmake": {
|
||||
"category": "Development",
|
||||
"choco": "cmake",
|
||||
@@ -197,6 +233,15 @@
|
||||
"winget": "Kitware.CMake",
|
||||
"foss": true
|
||||
},
|
||||
"codex": {
|
||||
"category": "Development",
|
||||
"choco": "codex",
|
||||
"content": "Codex",
|
||||
"description": "Codex CLI is an OpenAI coding agent that runs locally in your terminal.",
|
||||
"link": "https://developers.openai.com/codex/cli",
|
||||
"winget": "OpenAI.Codex",
|
||||
"foss": true
|
||||
},
|
||||
"cpuz": {
|
||||
"category": "Pro Tools",
|
||||
"choco": "cpu-z",
|
||||
@@ -314,6 +359,15 @@
|
||||
"winget": "Microsoft.DotNet.DesktopRuntime.10",
|
||||
"foss": true
|
||||
},
|
||||
"dropbox": {
|
||||
"category": "Utilities",
|
||||
"choco": "dropbox",
|
||||
"content": "Dropbox",
|
||||
"description": "Dropbox is a cloud storage client for syncing files, sharing content, and keeping documents available across devices.",
|
||||
"link": "https://www.dropbox.com/desktop",
|
||||
"winget": "Dropbox.Dropbox",
|
||||
"foss": false
|
||||
},
|
||||
"eaapp": {
|
||||
"category": "Games",
|
||||
"choco": "ea-app",
|
||||
@@ -539,6 +593,15 @@
|
||||
"winget": "DuongDieuPhap.ImageGlass",
|
||||
"foss": true
|
||||
},
|
||||
"internetdownloadmanager": {
|
||||
"category": "Utilities",
|
||||
"choco": "internet-download-manager",
|
||||
"content": "Internet Download Manager",
|
||||
"description": "Internet Download Manager is a download manager for accelerating, resuming, and scheduling file downloads.",
|
||||
"link": "https://www.internetdownloadmanager.com/",
|
||||
"winget": "Tonec.InternetDownloadManager",
|
||||
"foss": false
|
||||
},
|
||||
"irfanview": {
|
||||
"category": "Multimedia Tools",
|
||||
"choco": "irfanview",
|
||||
@@ -1285,6 +1348,15 @@
|
||||
"winget": "SlackTechnologies.Slack",
|
||||
"foss": false
|
||||
},
|
||||
"startallback": {
|
||||
"category": "Utilities",
|
||||
"choco": "StartAllBack",
|
||||
"content": "StartAllBack",
|
||||
"description": "StartAllBack restores and improves Windows taskbar, Start menu, File Explorer, and shell UI behavior.",
|
||||
"link": "https://www.startallback.com/",
|
||||
"winget": "StartIsBack.StartAllBack",
|
||||
"foss": false
|
||||
},
|
||||
"steam": {
|
||||
"category": "Games",
|
||||
"choco": "steam-client",
|
||||
@@ -1573,6 +1645,15 @@
|
||||
"winget": "Waterfox.Waterfox",
|
||||
"foss": true
|
||||
},
|
||||
"whatsapp": {
|
||||
"category": "Communications",
|
||||
"choco": "na",
|
||||
"content": "WhatsApp Desktop",
|
||||
"description": "WhatsApp Desktop is the official Windows desktop messaging app from Meta, distributed through the Microsoft Store.",
|
||||
"link": "https://apps.microsoft.com/detail/9nksqgp7f2nh",
|
||||
"winget": "msstore:9NKSQGP7F2NH",
|
||||
"foss": false
|
||||
},
|
||||
"wingetui": {
|
||||
"category": "Utilities",
|
||||
"choco": "wingetui",
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"WPFAppxMicrosoft_WindowsFeedbackHub": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Feedback Hub",
|
||||
"Description": "Allows users to submit bug reports, feature suggestions, and diagnostic data directly to Microsoft.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsFeedbackHub"
|
||||
},
|
||||
"WPFAppxMicrosoft_GetHelp": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Get Help",
|
||||
"Description": "Provides access to automated troubleshooting guides, support documentation, and direct Microsoft customer assistance.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.GetHelp"
|
||||
},
|
||||
"WPFAppxMicrosoft_OutlookForWindows": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Outlook for Windows",
|
||||
"Description": "Provides modern email management, calendar scheduling, and contact organization features.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.OutlookForWindows"
|
||||
},
|
||||
"WPFAppxMSTeams": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Microsoft Teams",
|
||||
"Description": "Facilitates instant messaging, video conferencing, file sharing, and workspace collaboration.",
|
||||
"Panel": "0",
|
||||
"PackageId": "MSTeams"
|
||||
},
|
||||
"WPFAppxClipchamp_Clipchamp": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Clipchamp",
|
||||
"Description": "Provides a user-friendly video editor with built-in templates, effects, and timeline editing tools.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Clipchamp.Clipchamp"
|
||||
},
|
||||
"WPFAppxMicrosoft_MicrosoftOfficeHub": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Microsoft 365",
|
||||
"Description": "Serves as a centralized launcher and dashboard for accessing cloud-based Microsoft 365 apps and recent documents.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.MicrosoftOfficeHub"
|
||||
},
|
||||
"WPFAppxMicrosoft_ZuneMusic": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Media Player",
|
||||
"Description": "Plays local audio and video files with modern playlist management and casting capabilities.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.ZuneMusic"
|
||||
},
|
||||
"WPFAppxMicrosoft_BingSearch": {
|
||||
"Category": "Bing & Web Services",
|
||||
"Content": "Bing Search",
|
||||
"Description": "Integrates Microsoft Bing search capabilities and web services directly into the operating system.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.BingSearch"
|
||||
},
|
||||
"WPFAppxMicrosoftCorporationII_QuickAssist": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Quick Assist",
|
||||
"Description": "Enables secure remote technical support and screen sharing over an internet connection.",
|
||||
"Panel": "0",
|
||||
"PackageId": "MicrosoftCorporationII.QuickAssist"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsDevHome": {
|
||||
"Category": "Developer Tools",
|
||||
"Content": "Dev Home",
|
||||
"Description": "Provides a specialized dashboard for software developer environment setups, repository syncing, and hardware widgets.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.Windows.DevHome"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsCrossDevice": {
|
||||
"Category": "Microsoft Ecosystem",
|
||||
"Content": "Mobile Devices",
|
||||
"Description": "Manages system-level background connectivity with paired mobile devices. Removing this may disable cross-device features such as phone screen mirroring, file transfer, and mobile hotspot handoff integrated into Windows Settings.",
|
||||
"Panel": "0",
|
||||
"PackageId": "MicrosoftWindows.CrossDevice"
|
||||
},
|
||||
"WPFAppxMicrosoft_Todos": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "To Do",
|
||||
"Description": "Creates, tracks, and synchronizes personal tasks, smart lists, and daily reminders.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.Todos"
|
||||
},
|
||||
"WPFAppxMicrosoft_PowerAutomateDesktop": {
|
||||
"Category": "Developer Tools",
|
||||
"Content": "Power Automate",
|
||||
"Description": "Automates repetitive workflows and desktop tasks using low-code visual scripting.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.PowerAutomateDesktop"
|
||||
},
|
||||
"WPFAppxMicrosoft_YourPhone": {
|
||||
"Category": "Microsoft Ecosystem",
|
||||
"Content": "Phone Link",
|
||||
"Description": "Synchronizes text messages, phone notifications, photos, and calls from a mobile device to the desktop.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.YourPhone"
|
||||
},
|
||||
"WPFAppxMicrosoft_MicrosoftStickyNotes": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Sticky Notes",
|
||||
"Description": "Creates quick, floating text notes on the desktop that automatically sync across devices.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.MicrosoftStickyNotes"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsSoundRecorder": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Sound Recorder",
|
||||
"Description": "Records and trims live audio inputs with simple microphone adjustment controls.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsSoundRecorder"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsAlarms": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Clock",
|
||||
"Description": "Features world clocks, alarms, countdown timers, stopwatches, and dedicated focus session tracking.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsAlarms"
|
||||
},
|
||||
"WPFAppxMicrosoft_Paint": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Paint",
|
||||
"Description": "Provides built-in digital sketching, basic image editing, and pixel-level graphic manipulation tools.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.Paint"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsNotepad": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Notepad",
|
||||
"Description": "Provides a lightweight text editor with multi-tab support for plain text files and code snippets.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsNotepad"
|
||||
},
|
||||
"WPFAppxMicrosoft_ScreenSketch": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Snipping Tool",
|
||||
"Description": "Captures screenshots or screen recordings with built-in markup, image cropping, and optical character recognition (OCR).",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.ScreenSketch"
|
||||
},
|
||||
"WPFAppxMicrosoft_Copilot": {
|
||||
"Category": "Bing & Web Services",
|
||||
"Content": "Copilot",
|
||||
"Description": "Launches the Microsoft AI companion for contextual answers, creative writing assistance, and intelligent web search.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.Copilot"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsCalculator": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Calculator",
|
||||
"Description": "Performs standard arithmetic, scientific operations, programming calculations, and unit conversions.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsCalculator"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsCamera": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Camera",
|
||||
"Description": "Captures photographs and records video files via connected webcams or imaging hardware.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsCamera"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsPhotos": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Photos",
|
||||
"Description": "Organizes, views, and crops local images with basic color adjustment and album creation tools.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.Windows.Photos"
|
||||
},
|
||||
"WPFAppxMicrosoft_BingNews": {
|
||||
"Category": "Bing & Web Services",
|
||||
"Content": "News",
|
||||
"Description": "Aggregates breaking news headlines, personalized article feeds, and world current events.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.BingNews"
|
||||
},
|
||||
"WPFAppxMicrosoft_BingWeather": {
|
||||
"Category": "Bing & Web Services",
|
||||
"Content": "Weather",
|
||||
"Description": "Displays local real-time weather tracking, radar maps, and historical meteorological forecasts.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.BingWeather"
|
||||
},
|
||||
"WPFAppxMicrosoft_GamingApp": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Xbox App",
|
||||
"Description": "Serves as the primary gaming library manager, social community interface, and PC Game Pass dashboard.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.GamingApp"
|
||||
},
|
||||
"WPFAppxMicrosoft_XboxGamingOverlay": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Xbox Game Bar",
|
||||
"Description": "Provides customizable in-game status widgets, audio balancing sliders, system monitoring tools, and gameplay recording.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.XboxGamingOverlay"
|
||||
},
|
||||
"WPFAppxMicrosoft_XboxIdentityProvider": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Xbox Identity Provider",
|
||||
"Description": "Manages Xbox network user authentication and background account validation for connected titles. Warning: removing this may break Microsoft account sign-in for non-Xbox games and apps that rely on this authentication pipeline.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.XboxIdentityProvider"
|
||||
},
|
||||
"WPFAppxMicrosoft_XboxSpeechToTextOverlay": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Xbox Speech To Text Overlay",
|
||||
"Description": "Provides system-level live accessibility captions and voice-to-text translation for gaming chat networks.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.XboxSpeechToTextOverlay"
|
||||
},
|
||||
"WPFAppxMicrosoft_Xbox_TCUI": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Xbox TCUI",
|
||||
"Description": "Provides core account connection UI modules for single sign-on flows within game titles. Warning: removing this may break Microsoft account authentication in games and apps that do not otherwise require the Xbox app.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.Xbox.TCUI"
|
||||
},
|
||||
"WPFAppxMicrosoft_StartExperiencesApp": {
|
||||
"Category": "Bing & Web Services",
|
||||
"Content": "Start Experiences App",
|
||||
"Description": "Powers the Windows Widgets board, delivering a personalized feed of news, weather, sports, and finance content.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.StartExperiencesApp"
|
||||
},
|
||||
"WPFAppxMicrosoft_MicrosoftSolitaireCollection": {
|
||||
"Category": "Xbox & Gaming",
|
||||
"Content": "Solitaire Collection",
|
||||
"Description": "Bundles built-in card game modes including Klondike, Spider, FreeCell, Pyramid, and TriPeaks alongside daily challenges.",
|
||||
"Panel": "1",
|
||||
"PackageId": "Microsoft.MicrosoftSolitaireCollection"
|
||||
}
|
||||
}
|
||||
+21
-3
@@ -4,7 +4,6 @@
|
||||
"WPFTweaksConsumerFeatures",
|
||||
"WPFTweaksDisableExplorerAutoDiscovery",
|
||||
"WPFTweaksWPBT",
|
||||
"WPFTweaksDeBloat",
|
||||
"WPFTweaksLocation",
|
||||
"WPFTweaksServices",
|
||||
"WPFTweaksTelemetry",
|
||||
@@ -16,17 +15,16 @@
|
||||
],
|
||||
"Minimal": [
|
||||
"WPFTweaksConsumerFeatures",
|
||||
"WPFTweaksDeBloat",
|
||||
"WPFTweaksWPBT",
|
||||
"WPFTweaksServices",
|
||||
"WPFTweaksTelemetry"
|
||||
],
|
||||
"Advanced": [
|
||||
"WPFTweaksRestorePoint",
|
||||
"WPFTweaksActivity",
|
||||
"WPFTweaksConsumerFeatures",
|
||||
"WPFTweaksDisableExplorerAutoDiscovery",
|
||||
"WPFTweaksWPBT",
|
||||
"WPFTweaksDeBloat",
|
||||
"WPFTweaksLocation",
|
||||
"WPFTweaksServices",
|
||||
"WPFTweaksTelemetry",
|
||||
@@ -39,5 +37,25 @@
|
||||
"WPFTweaksRemoveOneDrive",
|
||||
"WPFTweaksWindowsAI",
|
||||
"WPFTweaksRightClickMenu"
|
||||
],
|
||||
"AppxDefault": [
|
||||
"WPFAppxMicrosoft_WindowsFeedbackHub",
|
||||
"WPFAppxMicrosoft_GetHelp",
|
||||
"WPFAppxMicrosoft_MicrosoftOfficeHub",
|
||||
"WPFAppxMicrosoft_WindowsCalculator",
|
||||
"WPFAppxClipchamp_Clipchamp",
|
||||
"WPFAppxMicrosoft_WindowsAlarms",
|
||||
"WPFAppxMicrosoftCorporationII_QuickAssist",
|
||||
"WPFAppxMicrosoft_WindowsSoundRecorder",
|
||||
"WPFAppxMicrosoft_MicrosoftStickyNotes",
|
||||
"WPFAppxMicrosoft_Todos",
|
||||
"WPFAppxMicrosoft_MicrosoftSolitaireCollection",
|
||||
"WPFAppxMicrosoft_PowerAutomateDesktop",
|
||||
"WPFAppxMicrosoft_WindowsDevHome",
|
||||
"WPFAppxMicrosoft_BingWeather",
|
||||
"WPFAppxMicrosoft_StartExperiencesApp",
|
||||
"WPFAppxMicrosoft_BingNews",
|
||||
"WPFAppxMicrosoft_Copilot",
|
||||
"WPFAppxMicrosoft_BingSearch"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -65,11 +65,13 @@
|
||||
"ButtonConfigBackgroundColor": "#F7F7F7",
|
||||
"ButtonUpdatesBackgroundColor": "#F7F7F7",
|
||||
"ButtonWin11ISOBackgroundColor": "#F7F7F7",
|
||||
"ButtonAppxBackgroundColor": "#F7F7F7",
|
||||
"ButtonInstallForegroundColor": "#232629",
|
||||
"ButtonTweaksForegroundColor": "#232629",
|
||||
"ButtonConfigForegroundColor": "#232629",
|
||||
"ButtonUpdatesForegroundColor": "#232629",
|
||||
"ButtonWin11ISOForegroundColor": "#232629",
|
||||
"ButtonAppxForegroundColor": "#232629",
|
||||
"ButtonBackgroundColor": "#F5F5F5",
|
||||
"ButtonBackgroundPressedColor": "#1A1A1A",
|
||||
"ButtonBackgroundMouseoverColor": "#C2C2C2",
|
||||
@@ -105,11 +107,13 @@
|
||||
"ButtonConfigBackgroundColor": "#444444",
|
||||
"ButtonUpdatesBackgroundColor": "#555555",
|
||||
"ButtonWin11ISOBackgroundColor": "#666666",
|
||||
"ButtonAppxBackgroundColor": "#777777",
|
||||
"ButtonInstallForegroundColor": "#F7F7F7",
|
||||
"ButtonTweaksForegroundColor": "#F7F7F7",
|
||||
"ButtonConfigForegroundColor": "#F7F7F7",
|
||||
"ButtonUpdatesForegroundColor": "#F7F7F7",
|
||||
"ButtonWin11ISOForegroundColor": "#F7F7F7",
|
||||
"ButtonAppxForegroundColor": "#F7F7F7",
|
||||
"ButtonBackgroundColor": "#1E3747",
|
||||
"ButtonBackgroundPressedColor": "#F7F7F7",
|
||||
"ButtonBackgroundMouseoverColor": "#3B4252",
|
||||
|
||||
+4
-68
@@ -650,7 +650,7 @@
|
||||
icacls $Env:OneDrive /deny \"Administrators:(D,DC)\"
|
||||
|
||||
Write-Host \"Uninstalling OneDrive...\"
|
||||
Start-Process 'C:\\Windows\\System32\\OneDriveSetup.exe' -ArgumentList '/uninstall' -Wait
|
||||
Start-Process '$Env:SystemRoot\\System32\\OneDriveSetup.exe' -ArgumentList '/uninstall' -Wait
|
||||
|
||||
# Some of OneDrive files use explorer, and OneDrive uses FileCoAuth
|
||||
Write-Host \"Removing leftover OneDrive Files...\"
|
||||
@@ -658,7 +658,7 @@
|
||||
Stop-Process -Name FileCoAuth,Explorer
|
||||
|
||||
Remove-Item \"$Env:LocalAppData\\Microsoft\\OneDrive\" -Recurse -Force
|
||||
Remove-Item \"C:\\ProgramData\\Microsoft OneDrive\" -Recurse -Force
|
||||
Remove-Item \"$Env:ProgramData\\Microsoft OneDrive\" -Recurse -Force
|
||||
|
||||
# Grant back permission to access OneDrive folder
|
||||
icacls $Env:OneDrive /grant \"Administrators:(D,DC)\"
|
||||
@@ -812,70 +812,6 @@
|
||||
],
|
||||
"link": "https://winutil.christitus.com/dev/tweaks/z--advanced-tweaks---caution/display"
|
||||
},
|
||||
"WPFTweaksXboxRemoval": {
|
||||
"Content": "Xbox & Gaming Components - Remove",
|
||||
"Description": "Removes Xbox services, the Xbox app, Game Bar, and related authentication components.",
|
||||
"category": "z__Advanced Tweaks - CAUTION",
|
||||
"panel": "1",
|
||||
"registry": [
|
||||
{
|
||||
"Path": "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\GameDVR",
|
||||
"Name": "AppCaptureEnabled",
|
||||
"Value": "0",
|
||||
"Type": "DWord",
|
||||
"OriginalValue": "1"
|
||||
}
|
||||
],
|
||||
"appx": [
|
||||
"Microsoft.XboxIdentityProvider",
|
||||
"Microsoft.XboxSpeechToTextOverlay",
|
||||
"Microsoft.GamingApp",
|
||||
"Microsoft.Xbox.TCUI",
|
||||
"Microsoft.XboxGamingOverlay"
|
||||
],
|
||||
"link": "https://winutil.christitus.com/dev/tweaks/z--advanced-tweaks---caution/xboxremoval"
|
||||
},
|
||||
"WPFTweaksDeBloat": {
|
||||
"Content": "Unwanted Pre-Installed Apps - Remove",
|
||||
"Description": "This will remove a bunch of Windows pre-installed applications which most people dont want on their system.",
|
||||
"category": "Essential Tweaks",
|
||||
"panel": "1",
|
||||
"appx": [
|
||||
"Microsoft.WindowsFeedbackHub",
|
||||
"Microsoft.BingNews",
|
||||
"Microsoft.BingSearch",
|
||||
"Microsoft.BingWeather",
|
||||
"Clipchamp.Clipchamp",
|
||||
"Microsoft.Todos",
|
||||
"Microsoft.PowerAutomateDesktop",
|
||||
"Microsoft.MicrosoftSolitaireCollection",
|
||||
"Microsoft.WindowsSoundRecorder",
|
||||
"Microsoft.MicrosoftStickyNotes",
|
||||
"Microsoft.Windows.DevHome",
|
||||
"Microsoft.Paint",
|
||||
"Microsoft.OutlookForWindows",
|
||||
"Microsoft.WindowsAlarms",
|
||||
"Microsoft.StartExperiencesApp",
|
||||
"Microsoft.GetHelp",
|
||||
"Microsoft.ZuneMusic",
|
||||
"MicrosoftCorporationII.QuickAssist",
|
||||
"MSTeams"
|
||||
],
|
||||
"InvokeScript": [
|
||||
"
|
||||
$TeamsPath = \"$Env:LocalAppData\\Microsoft\\Teams\\Update.exe\"
|
||||
|
||||
if (Test-Path $TeamsPath) {
|
||||
Write-Host \"Uninstalling Teams\"
|
||||
Start-Process $TeamsPath -ArgumentList -uninstall -wait
|
||||
|
||||
Write-Host \"Deleting Teams directory\"
|
||||
Remove-Item $TeamsPath -Recurse -Force
|
||||
}
|
||||
"
|
||||
],
|
||||
"link": "https://winutil.christitus.com/dev/tweaks/essential-tweaks/debloat"
|
||||
},
|
||||
"WPFTweaksReservedStorage": {
|
||||
"Content": "Disable Reserved Storage",
|
||||
"Description": "Disables Windows Reserved Storage (7-10 GB held for updates/temp files). Recommended only on small drives. Re-enable before major Windows feature updates to avoid installation failures.",
|
||||
@@ -1030,7 +966,7 @@
|
||||
],
|
||||
"InvokeScript": [
|
||||
"
|
||||
$RazerPath = \"C:\\Windows\\Installer\\Razer\"
|
||||
$RazerPath = \"$Env:SystemRoot\\Installer\\Razer\"
|
||||
|
||||
if (Test-Path $RazerPath) {
|
||||
Remove-Item $RazerPath\\* -Recurse -Force
|
||||
@@ -1043,7 +979,7 @@
|
||||
],
|
||||
"UndoScript": [
|
||||
"
|
||||
icacls \"C:\\Windows\\Installer\\Razer\" /remove:d Everyone
|
||||
icacls \"$Env:SystemRoot\\Installer\\Razer\" /remove:d Everyone
|
||||
"
|
||||
],
|
||||
"link": "https://winutil.christitus.com/dev/tweaks/z--advanced-tweaks---caution/razerblock"
|
||||
|
||||
@@ -578,7 +578,9 @@ Tests are in `/pester/`:
|
||||
|
||||
Run tests:
|
||||
```powershell
|
||||
Invoke-Pester
|
||||
Install-Module -Name Pester -RequiredVersion 5.8.0 -Scope CurrentUser -Force -SkipPublisherCheck
|
||||
Import-Module Pester -RequiredVersion 5.8.0 -Force
|
||||
Invoke-Pester -Path 'pester/*.Tests.ps1' -Output Detailed -CI
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "System Tray Battery Percentage"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1315}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1251}
|
||||
"WPFToggleBatteryPercentage": {
|
||||
"Content": "System Tray Battery Percentage",
|
||||
"Description": "Shows numeric battery percentage next to the battery icon in the system tray.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Start Menu Bing Search"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1650}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1586}
|
||||
"WPFToggleBingSearch": {
|
||||
"Content": "Start Menu Bing Search",
|
||||
"Description": "Toggles Bing web search results in Windows Search.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Dark Theme for Windows"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1333}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1269}
|
||||
"WPFToggleDarkMode": {
|
||||
"Content": "Dark Theme for Windows",
|
||||
"Description": "Dark Mode for the system and applications.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "BSoD Verbose Mode"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1289}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1225}
|
||||
"WPFToggleDetailedBSoD": {
|
||||
"Content": "BSoD Verbose Mode",
|
||||
"Description": "Gives more information when you blue screen.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Lock Screen - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1686}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1622}
|
||||
"WPFTweaksDisableLockscreen": {
|
||||
"Content": "Lock Screen - Disable",
|
||||
"Description": "Skips the lock screen entirely and goes directly to the sign-in screen on boot and wake.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Game Mode"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1829}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1765}
|
||||
"WPFToggleGameMode": {
|
||||
"Content": "Game Mode",
|
||||
"Description": "Toggles Windows prioritizes gaming performance by allocating system resources to games.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "File Explorer Hidden Files"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1403}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1339}
|
||||
"WPFToggleHiddenFiles": {
|
||||
"Content": "File Explorer Hidden Files",
|
||||
"Description": "Reveals hidden files in Explorer.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Settings Home Page"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1632}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1568}
|
||||
"WPFToggleHideSettingsHome": {
|
||||
"Content": "Settings Home Page",
|
||||
"Description": "Toggles the Home Page in the Windows Settings app.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Logon Screen Acrylic Blur"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1668}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1604}
|
||||
"WPFToggleLoginBlur": {
|
||||
"Content": "Logon Screen Acrylic Blur",
|
||||
"Description": "Toggles the acrylic blur effect on login screen background.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Enable Long Paths"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1855}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1791}
|
||||
"WPFToggleLongPaths": {
|
||||
"Content": "Enable Long Paths",
|
||||
"Description": "Toggles support for file paths longer than 260 characters in Explorer.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Mouse Acceleration"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1536}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1472}
|
||||
"WPFToggleMouseAcceleration": {
|
||||
"Content": "Mouse Acceleration",
|
||||
"Description": "Makes it so Cursor movement is affected by the speed of your physical mouse movements.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Multiplane Overlay"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1510}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1446}
|
||||
"WPFToggleMultiplaneOverlay": {
|
||||
"Content": "Multiplane Overlay",
|
||||
"Description": "Multiplane Overlay compose multiple image layers, which can sometimes cause issues with graphics cards.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Microsoft Outlook New Version"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1449}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1385}
|
||||
"WPFToggleNewOutlook": {
|
||||
"Content": "Microsoft Outlook New Version",
|
||||
"Description": "This will ensures the classic Outlook application is used.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Num Lock on Startup"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1570}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1506}
|
||||
"WPFToggleNumLock": {
|
||||
"Content": "Num Lock on Startup",
|
||||
"Description": "Toggle the Num Lock key state when your computer starts.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "S3 Sleep"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1614}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1550}
|
||||
"WPFToggleS3Sleep": {
|
||||
"Content": "S3 Sleep",
|
||||
"Description": "Toggles between Modern Standby and S3 Sleep, which cuts off power to the CPU while continuing to refresh the memory.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Scrollbars Always Visible"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1491}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1427}
|
||||
"WPFToggleScrollbars": {
|
||||
"Content": "Scrollbars Always Visible",
|
||||
"Description": "If enabled, scrollbars will always be visible. If disabled, Windows will automatically hide scrollbars when not in use.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "File Explorer File Extensions"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1375}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1311}
|
||||
"WPFToggleShowExt": {
|
||||
"Content": "File Explorer File Extensions",
|
||||
"Description": "Shows .file extensions in Explorer (.exe, .png, etc.)",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "S0 Sleep Network Connectivity"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1596}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1532}
|
||||
"WPFToggleStandbyFix": {
|
||||
"Content": "S0 Sleep Network Connectivity",
|
||||
"Description": "Toggles network connectivity during S0 Sleep which is low power idle in modern laptops.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Start Menu Recommendations"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1703}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1639}
|
||||
"WPFToggleStartMenuRecommendations": {
|
||||
"Content": "Start Menu Recommendations",
|
||||
"Description": "Toggles the recommendations section in the Start Menu. WARNING: This will also disable Windows Spotlight on your Lock Screen as a side effect.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Sticky Keys"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1747}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1683}
|
||||
"WPFToggleStickyKeys": {
|
||||
"Content": "Sticky Keys",
|
||||
"Description": "Toggles the Sticky Keys, which activate when clicking shift rapidly.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Taskbar Task View Icon"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1811}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1747}
|
||||
"WPFToggleTaskView": {
|
||||
"Content": "Taskbar Task View Icon",
|
||||
"Description": "Toggles the Task View Button in the Taskbar.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Taskbar Centered Icons"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1765}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1701}
|
||||
"WPFToggleTaskbarAlignment": {
|
||||
"Content": "Taskbar Centered Icons",
|
||||
"Description": "Toggles the Taskbar alignment either to the left or center.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Taskbar Search Icon"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1793}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1729}
|
||||
"WPFToggleTaskbarSearch": {
|
||||
"Content": "Taskbar Search Icon",
|
||||
"Description": "Toggles the Search Button on the Taskbar.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Logon Verbose Mode"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1431}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1367}
|
||||
"WPFToggleVerboseLogon": {
|
||||
"Content": "Logon Verbose Mode",
|
||||
"Description": "Show detailed messages during startup/shutdown.",
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
title: "Unwanted Pre-Installed Apps - Remove"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=838}
|
||||
"WPFTweaksDeBloat": {
|
||||
"Content": "Unwanted Pre-Installed Apps - Remove",
|
||||
"Description": "This will remove a bunch of Windows pre-installed applications which most people dont want on their system.",
|
||||
"category": "Essential Tweaks",
|
||||
"panel": "1",
|
||||
"appx": [
|
||||
"Microsoft.WindowsFeedbackHub",
|
||||
"Microsoft.BingNews",
|
||||
"Microsoft.BingSearch",
|
||||
"Microsoft.BingWeather",
|
||||
"Clipchamp.Clipchamp",
|
||||
"Microsoft.Todos",
|
||||
"Microsoft.PowerAutomateDesktop",
|
||||
"Microsoft.MicrosoftSolitaireCollection",
|
||||
"Microsoft.WindowsSoundRecorder",
|
||||
"Microsoft.MicrosoftStickyNotes",
|
||||
"Microsoft.Windows.DevHome",
|
||||
"Microsoft.Paint",
|
||||
"Microsoft.OutlookForWindows",
|
||||
"Microsoft.WindowsAlarms",
|
||||
"Microsoft.StartExperiencesApp",
|
||||
"Microsoft.GetHelp",
|
||||
"Microsoft.ZuneMusic",
|
||||
"MicrosoftCorporationII.QuickAssist",
|
||||
"MSTeams"
|
||||
],
|
||||
"InvokeScript": [
|
||||
"
|
||||
$TeamsPath = \"$Env:LocalAppData\\Microsoft\\Teams\\Update.exe\"
|
||||
|
||||
if (Test-Path $TeamsPath) {
|
||||
Write-Host \"Uninstalling Teams\"
|
||||
Start-Process $TeamsPath -ArgumentList -uninstall -wait
|
||||
|
||||
Write-Host \"Deleting Teams directory\"
|
||||
Remove-Item $TeamsPath -Recurse -Force
|
||||
}
|
||||
"
|
||||
],
|
||||
```
|
||||
@@ -3,7 +3,7 @@ title: "Temporary Files - Remove"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1129}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1065}
|
||||
"WPFTweaksDeleteTempFiles": {
|
||||
"Content": "Temporary Files - Remove",
|
||||
"Description": "Erases TEMP Folders.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "File Explorer Automatic Folder Discovery - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1234}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1170}
|
||||
"WPFTweaksDisableExplorerAutoDiscovery": {
|
||||
"Content": "File Explorer Automatic Folder Discovery - Disable",
|
||||
"Description": "Windows Explorer automatically tries to guess the type of the folder based on its contents, slowing down the browsing experience. WARNING! Will disable File Explorer grouping.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Disk Cleanup - Run"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1116}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1052}
|
||||
"WPFTweaksDiskCleanup": {
|
||||
"Content": "Disk Cleanup - Run",
|
||||
"Description": "Runs Disk Cleanup on Drive C: and removes old Windows Updates.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "End Task With Right Click - Enable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=919}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=855}
|
||||
"WPFTweaksEndTaskOnTaskbar": {
|
||||
"Content": "End Task With Right Click - Enable",
|
||||
"Description": "Enables option to end task when right clicking a program in the taskbar.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Restore Point - Create"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=892}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=828}
|
||||
"WPFTweaksRestorePoint": {
|
||||
"Content": "Restore Point - Create",
|
||||
"Description": "Creates a restore point at runtime in case a revert is needed from WinUtil modifications.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Windows Platform Binary Table (WPBT) - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=994}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=930}
|
||||
"WPFTweaksWPBT": {
|
||||
"Content": "Windows Platform Binary Table (WPBT) - Disable",
|
||||
"Description": "If enabled, WPBT allows your computer vendor to execute programs at boot time, such as anti-theft software, software drivers, as well as force install software without user consent. Poses potential security risk.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Adobe URL Block List - Enable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1074}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1010}
|
||||
"WPFTweaksBlockAdobeNet": {
|
||||
"Content": "Adobe URL Block List - Enable",
|
||||
"Description": "Reduces user interruptions by selectively blocking connections to Adobe's activation and telemetry servers. Credit: Ruddernation-Designs",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Background Apps - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1202}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1138}
|
||||
"WPFTweaksDisableBGapps": {
|
||||
"Content": "Background Apps - Disable",
|
||||
"Description": "Disables all Microsoft Store apps from running in the background, which has to be done individually since Windows 11.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Fullscreen Optimizations - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1218}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1154}
|
||||
"WPFTweaksDisableFSO": {
|
||||
"Content": "Fullscreen Optimizations - Disable",
|
||||
"Description": "Disables FSO in all applications. NOTE: This will disable Color Management in Exclusive Fullscreen.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "IPv6 - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1180}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1116}
|
||||
"WPFTweaksDisableIPv6": {
|
||||
"Content": "IPv6 - Disable",
|
||||
"Description": "Disables IPv6.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "System Tray Notifications & Calendar - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1051}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=987}
|
||||
"WPFTweaksDisableNotifications": {
|
||||
"Content": "System Tray Notifications & Calendar - Disable",
|
||||
"Description": "Disables all Notifications INCLUDING Calendar.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "IPv6 - Set IPv4 as Preferred"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1142}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1078}
|
||||
"WPFTweaksIPv46": {
|
||||
"Content": "IPv6 - Set IPv4 as Preferred",
|
||||
"Description": "Setting the IPv4 preference can have latency and security benefits on private networks where IPv6 is not configured.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Razer Software Auto-Install - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1010}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=946}
|
||||
"WPFTweaksRazerBlock": {
|
||||
"Content": "Razer Software Auto-Install - Disable",
|
||||
"Description": "Blocks ALL Razer Software installations. The hardware works fine without any software.",
|
||||
@@ -27,7 +27,7 @@ description: ""
|
||||
],
|
||||
"InvokeScript": [
|
||||
"
|
||||
$RazerPath = \"C:\\Windows\\Installer\\Razer\"
|
||||
$RazerPath = \"$Env:SystemRoot\\Installer\\Razer\"
|
||||
|
||||
if (Test-Path $RazerPath) {
|
||||
Remove-Item $RazerPath\\* -Recurse -Force
|
||||
@@ -40,7 +40,7 @@ description: ""
|
||||
],
|
||||
"UndoScript": [
|
||||
"
|
||||
icacls \"C:\\Windows\\Installer\\Razer\" /remove:d Everyone
|
||||
icacls \"$Env:SystemRoot\\Installer\\Razer\" /remove:d Everyone
|
||||
"
|
||||
],
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ description: ""
|
||||
icacls $Env:OneDrive /deny \"Administrators:(D,DC)\"
|
||||
|
||||
Write-Host \"Uninstalling OneDrive...\"
|
||||
Start-Process 'C:\\Windows\\System32\\OneDriveSetup.exe' -ArgumentList '/uninstall' -Wait
|
||||
Start-Process '$Env:SystemRoot\\System32\\OneDriveSetup.exe' -ArgumentList '/uninstall' -Wait
|
||||
|
||||
# Some of OneDrive files use explorer, and OneDrive uses FileCoAuth
|
||||
Write-Host \"Removing leftover OneDrive Files...\"
|
||||
@@ -23,7 +23,7 @@ description: ""
|
||||
Stop-Process -Name FileCoAuth,Explorer
|
||||
|
||||
Remove-Item \"$Env:LocalAppData\\Microsoft\\OneDrive\" -Recurse -Force
|
||||
Remove-Item \"C:\\ProgramData\\Microsoft OneDrive\" -Recurse -Force
|
||||
Remove-Item \"$Env:ProgramData\\Microsoft OneDrive\" -Recurse -Force
|
||||
|
||||
# Grant back permission to access OneDrive folder
|
||||
icacls $Env:OneDrive /grant \"Administrators:(D,DC)\"
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Disable Reserved Storage"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=879}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=815}
|
||||
"WPFTweaksReservedStorage": {
|
||||
"Content": "Disable Reserved Storage",
|
||||
"Description": "Disables Windows Reserved Storage (7-10 GB held for updates/temp files). Recommended only on small drives. Re-enable before major Windows feature updates to avoid installation failures.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Right-Click Menu Previous Layout - Enable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1100}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1036}
|
||||
"WPFTweaksRightClickMenu": {
|
||||
"Content": "Right-Click Menu Previous Layout - Enable",
|
||||
"Description": "Restores the classic context menu when right-clicking in File Explorer, replacing the simplified Windows 11 version.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Storage Sense - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=935}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=871}
|
||||
"WPFTweaksStorage": {
|
||||
"Content": "Storage Sense - Disable",
|
||||
"Description": "Storage Sense deletes temp files automatically.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Teredo - Disable"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1158}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1094}
|
||||
"WPFTweaksTeredo": {
|
||||
"Content": "Teredo - Disable",
|
||||
"Description": "Teredo network tunneling is an IPv6 feature that can cause additional latency, but may cause problems with some games.",
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Windows AI - Disable And Remove"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=951}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=887}
|
||||
"WPFTweaksWindowsAI": {
|
||||
"Content": "Windows AI - Disable And Remove",
|
||||
"Description": "Removes and disables all AI features/packages",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: "Xbox & Gaming Components - Remove"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=815}
|
||||
"WPFTweaksXboxRemoval": {
|
||||
"Content": "Xbox & Gaming Components - Remove",
|
||||
"Description": "Removes Xbox services, the Xbox app, Game Bar, and related authentication components.",
|
||||
"category": "z__Advanced Tweaks - CAUTION",
|
||||
"panel": "1",
|
||||
"registry": [
|
||||
{
|
||||
"Path": "HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\GameDVR",
|
||||
"Name": "AppCaptureEnabled",
|
||||
"Value": "0",
|
||||
"Type": "DWord",
|
||||
"OriginalValue": "1"
|
||||
}
|
||||
],
|
||||
"appx": [
|
||||
"Microsoft.XboxIdentityProvider",
|
||||
"Microsoft.XboxSpeechToTextOverlay",
|
||||
"Microsoft.GamingApp",
|
||||
"Microsoft.Xbox.TCUI",
|
||||
"Microsoft.XboxGamingOverlay"
|
||||
],
|
||||
```
|
||||
|
||||
## Registry Changes
|
||||
|
||||
Applications and System Components store and retrieve configuration data to modify Windows settings, so we can use the registry to change many settings in one place.
|
||||
|
||||
You can find information about the registry on [Wikipedia](https://en.wikipedia.org/wiki/Windows_Registry) and [Microsoft's Website](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry).
|
||||
@@ -3,7 +3,7 @@ title: "DNS - Set to:"
|
||||
description: ""
|
||||
---
|
||||
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1880}
|
||||
```json {filename="config/tweaks.json",linenos=inline,linenostart=1816}
|
||||
"WPFchangedns": {
|
||||
"Content": "DNS - Set to:",
|
||||
"category": "z__Advanced Tweaks - CAUTION",
|
||||
|
||||
@@ -51,6 +51,7 @@ Click **Run Windows ISO Modification and Creator** to start the customization pr
|
||||
- **Disable BitLocker and device encryption** — removes startup overhead
|
||||
- **Disable Chat icon** — removes chat taskbar button
|
||||
- **Strip unused editions** — keeps only your selected edition, saving 1–2 GB per removed edition
|
||||
- **Pin the selected edition during setup** — writes setup metadata so OEM firmware keys for a different edition do not force the installer down the wrong product-key path
|
||||
- **Clean the component store** — runs DISM cleanup to reclaim another 300–800 MB
|
||||
|
||||
**Privacy & Telemetry Tweaks:**
|
||||
@@ -132,6 +133,7 @@ When you install Windows 11 from your modified ISO:
|
||||
| USB drive not showing up | Plug it in, wait a few seconds, then click **Refresh** |
|
||||
| Modification seems stuck | The WIM dismount step is slow — wait at least 10 minutes before assuming it's frozen |
|
||||
| "Access Denied" error | Make sure WinUtil is running as Administrator |
|
||||
| "Setup has failed to validate the product key" | Recreate the ISO with the latest WinUtil. The creator now removes stale `PID.txt`, writes `sources\ei.cfg`, and pins the selected image in `autounattend.xml` so setup does not use an embedded OEM key for a different edition |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
function Close-WinUtilRunspacePool {
|
||||
if ($null -eq $sync -or -not $sync.ContainsKey("runspace") -or $null -eq $sync.runspace) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if ($sync.runspace.RunspacePoolStateInfo.State -notin @(
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Closed,
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Closing,
|
||||
[System.Management.Automation.Runspaces.RunspacePoolState]::Broken
|
||||
)) {
|
||||
$sync.runspace.Close()
|
||||
}
|
||||
} finally {
|
||||
$sync.runspace.Dispose()
|
||||
$sync.Remove("runspace")
|
||||
}
|
||||
}
|
||||
@@ -46,12 +46,17 @@ function Find-TweaksByNameOrDescription {
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# 2. GET REFERENCE TO TWEAKS PANEL
|
||||
# 2. GET REFERENCE TO TWEAKS OR APPX PANEL
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
$panelName = "tweakspanel"
|
||||
if ($null -ne $Sync.currentTab -and $Sync.currentTab -eq "AppX") {
|
||||
$panelName = "appxpanel"
|
||||
}
|
||||
|
||||
$tweaksPanel = $null
|
||||
try {
|
||||
$tweaksPanel = $Sync.Form.FindName("tweakspanel")
|
||||
$tweaksPanel = $Sync.Form.FindName($panelName)
|
||||
}
|
||||
catch {
|
||||
# Silent return - panel not found or disposed
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
function Get-WinUtilPackageLogSummary {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object[]]$Packages,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[PackageManagers]$Preference
|
||||
)
|
||||
|
||||
@($Packages | ForEach-Object {
|
||||
$package = $_
|
||||
$packageName = @($package.Name, $package.Description, $package.winget, $package.choco) |
|
||||
Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) -and $_ -ne "na" } |
|
||||
Select-Object -First 1
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace([string]$packageName)) {
|
||||
$packageName = "Unknown package"
|
||||
}
|
||||
|
||||
if ($Preference -eq [PackageManagers]::Choco -and -not [string]::IsNullOrWhiteSpace([string]$package.choco) -and $package.choco -ne "na") {
|
||||
"$packageName (choco: $($package.choco))"
|
||||
} elseif (-not [string]::IsNullOrWhiteSpace([string]$package.winget) -and $package.winget -ne "na") {
|
||||
"$packageName (winget: $($package.winget))"
|
||||
} else {
|
||||
"$packageName (no package id)"
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -21,17 +21,32 @@ function Get-WinUtilSelectedPackages {
|
||||
$packages[[PackageManagers]::Winget] = $packagesWinget
|
||||
$packages[[PackageManagers]::Choco] = $packagesChoco
|
||||
|
||||
function Add-PackageId {
|
||||
param(
|
||||
[System.Collections.ArrayList]$Target,
|
||||
$PackageId
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace([string]$PackageId) -or $PackageId -eq "na") {
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $Target.Contains($PackageId)) {
|
||||
$null = $Target.Add($PackageId)
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($package in $PackageList) {
|
||||
switch ($Preference) {
|
||||
"Choco" {
|
||||
if ($package.choco -eq "na") {
|
||||
$null = $packagesWinget.add($package.winget)
|
||||
if ([string]::IsNullOrWhiteSpace([string]$package.choco) -or $package.choco -eq "na") {
|
||||
Add-PackageId -Target $packagesWinget -PackageId $package.winget
|
||||
} else {
|
||||
$null = $packagesChoco.add($package.choco)
|
||||
Add-PackageId -Target $packagesChoco -PackageId $package.choco
|
||||
}
|
||||
}
|
||||
"Winget" {
|
||||
$null = $packagesWinget.add($package.winget)
|
||||
Add-PackageId -Target $packagesWinget -PackageId $package.winget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,39 @@ Function Get-WinUtilToggleStatus ($ToggleSwitch) {
|
||||
|
||||
$ToggleSwitchReg = $sync.configs.tweaks.$ToggleSwitch.registry
|
||||
|
||||
if ($null -eq $sync.ToggleStatusCache) {
|
||||
$sync.ToggleStatusCache = @{}
|
||||
}
|
||||
|
||||
if ($sync.ToggleStatusCache.ContainsKey($ToggleSwitch)) {
|
||||
return [bool]$sync.ToggleStatusCache[$ToggleSwitch]
|
||||
}
|
||||
|
||||
if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
|
||||
New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS | Out-Null
|
||||
}
|
||||
|
||||
foreach ($regentry in $ToggleSwitchReg) {
|
||||
|
||||
if (-not (Test-Path $regentry.Path)) {
|
||||
New-Item -Path $regentry.Path -Force | Out-Null
|
||||
if (Test-Path $regentry.Path) {
|
||||
$regstate = (Get-ItemProperty -Path $regentry.Path).$($regentry.Name)
|
||||
} else {
|
||||
$regstate = $null
|
||||
}
|
||||
|
||||
$regstate = (Get-ItemProperty -Path $regentry.Path).$($regentry.Name)
|
||||
|
||||
if ($null -eq $regstate) {
|
||||
switch ($regentry.DefaultState) {
|
||||
switch ([string]$regentry.DefaultState) {
|
||||
"true" { $regstate = $regentry.Value }
|
||||
"false" { $regstate = $regentry.OriginalValue }
|
||||
}
|
||||
}
|
||||
|
||||
if ($regstate -ne $regentry.Value) {
|
||||
$sync.ToggleStatusCache[$ToggleSwitch] = $false
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
$sync.ToggleStatusCache[$ToggleSwitch] = $true
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ function Initialize-InstallAppEntry {
|
||||
$appKey
|
||||
)
|
||||
|
||||
$app = $sync.configs.applicationsHashtable.$appKey
|
||||
|
||||
# Create the outer Border for the application type
|
||||
$border = New-Object Windows.Controls.Border
|
||||
$border.Style = $sync.Form.Resources.AppEntryBorderStyle
|
||||
$border.Tag = $appKey
|
||||
$border.ToolTip = $Apps.$appKey.description
|
||||
$border.ToolTip = $app.description
|
||||
$border.Add_MouseLeftButtonUp({
|
||||
$childCheckbox = ($this.Child | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0]
|
||||
$childCheckBox.isChecked = -not $childCheckbox.IsChecked
|
||||
@@ -61,10 +63,10 @@ function Initialize-InstallAppEntry {
|
||||
# Create the TextBlock for the application name
|
||||
$appName = New-Object Windows.Controls.TextBlock
|
||||
$appName.Style = $sync.Form.Resources.AppEntryNameStyle
|
||||
$appName.Text = $Apps.$appKey.content
|
||||
$appName.Text = $app.content
|
||||
|
||||
# Add FOSS label after the name if FOSS
|
||||
if ($Apps.$appKey.foss -eq $true) {
|
||||
if ($app.foss -eq $true) {
|
||||
$fossRun = [System.Windows.Documents.Run]::new(" $([char]0x25CF)")
|
||||
$fossRun.Foreground = [Windows.Media.SolidColorBrush]::new([Windows.Media.Color]::FromRgb(110, 255, 114))
|
||||
$fossRun.FontSize = 11.5
|
||||
@@ -74,10 +76,13 @@ function Initialize-InstallAppEntry {
|
||||
$checkBox.Content = $appName
|
||||
|
||||
# Add accessibility properties to make the elements screen reader friendly
|
||||
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
|
||||
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $Apps.$appKey.content)
|
||||
$checkBox.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
|
||||
$border.SetValue([Windows.Automation.AutomationProperties]::NameProperty, $app.content)
|
||||
|
||||
$border.Child = $checkBox
|
||||
if ($sync.selectedApps -contains $appKey) {
|
||||
$checkBox.IsChecked = $true
|
||||
}
|
||||
# Add the border to the corresponding Category
|
||||
$TargetElement.Children.Add($border) | Out-Null
|
||||
return $checkbox
|
||||
|
||||
@@ -16,7 +16,7 @@ function Initialize-InstallCategoryAppList {
|
||||
$Apps
|
||||
)
|
||||
|
||||
# Pre-group apps by category
|
||||
# Pre-group apps by category before creating WPF controls.
|
||||
$appsByCategory = @{}
|
||||
foreach ($appKey in $Apps.Keys) {
|
||||
$category = $Apps.$appKey.Category
|
||||
@@ -25,6 +25,8 @@ function Initialize-InstallCategoryAppList {
|
||||
}
|
||||
$appsByCategory[$category] += $appKey
|
||||
}
|
||||
$sync.InstallAppRenderQueue = [System.Collections.Queue]::new()
|
||||
|
||||
foreach ($category in $($appsByCategory.Keys | Sort-Object)) {
|
||||
# Create a container for category label + apps
|
||||
$categoryContainer = New-Object Windows.Controls.StackPanel
|
||||
@@ -52,10 +54,10 @@ function Initialize-InstallCategoryAppList {
|
||||
|
||||
# Add click handler to toggle category visibility
|
||||
$toggleButton.Add_MouseLeftButtonUp({
|
||||
param($sender, $e)
|
||||
param($categoryToggle)
|
||||
|
||||
# Find the parent StackPanel (categoryContainer)
|
||||
$categoryContainer = $sender.Parent
|
||||
$categoryContainer = $categoryToggle.Parent
|
||||
if ($categoryContainer -and $categoryContainer.Children.Count -ge 2) {
|
||||
# The WrapPanel is the second child
|
||||
$wrapPanel = $categoryContainer.Children[1]
|
||||
@@ -64,11 +66,11 @@ function Initialize-InstallCategoryAppList {
|
||||
if ($wrapPanel.Visibility -eq [Windows.Visibility]::Visible) {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
|
||||
# Change - to +
|
||||
$sender.Content = $sender.Content -replace "^- ", "+ "
|
||||
$categoryToggle.Content = $categoryToggle.Content -replace "^- ", "+ "
|
||||
} else {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Visible
|
||||
# Change + to -
|
||||
$sender.Content = $sender.Content -replace "^\+ ", "- "
|
||||
$categoryToggle.Content = $categoryToggle.Content -replace "^\+ ", "- "
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -89,9 +91,12 @@ function Initialize-InstallCategoryAppList {
|
||||
# Add the entire category container to the target element
|
||||
$null = $TargetElement.Items.Add($categoryContainer)
|
||||
|
||||
# Add apps to the wrap panel
|
||||
$appsByCategory[$category] | Sort-Object | ForEach-Object {
|
||||
$sync.$_ = $(Initialize-InstallAppEntry -TargetElement $wrapPanel -AppKey $_)
|
||||
}
|
||||
$sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{
|
||||
Category = $category
|
||||
TargetElement = $wrapPanel
|
||||
AppKeys = @($appsByCategory[$category] | Sort-Object)
|
||||
})
|
||||
}
|
||||
|
||||
Start-WinUtilInstallAppRendering
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
function Initialize-WinUtilRunspacePool {
|
||||
if ($sync.runspace -and $sync.runspace.RunspacePoolStateInfo.State -eq [System.Management.Automation.Runspaces.RunspacePoolState]::Opened) {
|
||||
return $sync.runspace
|
||||
}
|
||||
|
||||
if ($sync.runspace) {
|
||||
Close-WinUtilRunspacePool
|
||||
}
|
||||
|
||||
# Set the maximum number of threads for the RunspacePool to the number of threads on the machine.
|
||||
$maxthreads = [Math]::Max([int]$env:NUMBER_OF_PROCESSORS, 1)
|
||||
|
||||
# Create a new session state for parsing variables into our runspace.
|
||||
$hashVars = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync', $sync, $null
|
||||
$offlineVar = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'PARAM_OFFLINE', $PARAM_OFFLINE, $null
|
||||
$initialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
|
||||
|
||||
$initialSessionState.Variables.Add($hashVars)
|
||||
$initialSessionState.Variables.Add($offlineVar)
|
||||
|
||||
# Get every WinUtil/WPF function and add it to the session state.
|
||||
$functions = Get-ChildItem function:\ | Where-Object { $_.Name -imatch 'winutil|WPF' }
|
||||
foreach ($function in $functions) {
|
||||
$functionDefinition = Get-Content function:\$($function.Name)
|
||||
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $function.Name, $functionDefinition
|
||||
$initialSessionState.Commands.Add($functionEntry)
|
||||
}
|
||||
|
||||
$sync.runspace = [runspacefactory]::CreateRunspacePool(
|
||||
1, # Minimum thread count
|
||||
$maxthreads, # Maximum thread count
|
||||
$initialSessionState, # Initial session state
|
||||
$Host # Machine to create runspaces on
|
||||
)
|
||||
|
||||
$sync.runspace.Open()
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Runspace pool initialized"
|
||||
return $sync.runspace
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
function Initialize-WinUtilTabContent {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TabName
|
||||
)
|
||||
|
||||
if ($null -eq $sync.InitializedTabs) {
|
||||
$sync.InitializedTabs = @{}
|
||||
}
|
||||
|
||||
if ($sync.InitializedTabs[$TabName]) {
|
||||
return
|
||||
}
|
||||
|
||||
switch ($TabName) {
|
||||
"Install" {
|
||||
Invoke-WPFUIElements -configVariable $sync.configs.appnavigation -targetGridName "appscategory" -columncount 1
|
||||
Initialize-WPFUI -targetGridName "appscategory"
|
||||
Write-WinUtilPerformanceCheckpoint -Name "App navigation UI created"
|
||||
|
||||
Initialize-WPFUI -targetGridName "appspanel"
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Install UI created"
|
||||
}
|
||||
"Tweaks" {
|
||||
Invoke-WPFUIElements -configVariable $sync.configs.tweaks -targetGridName "tweakspanel" -columncount 2
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Tweaks UI created"
|
||||
}
|
||||
"Config" {
|
||||
Invoke-WPFUIElements -configVariable $sync.configs.feature -targetGridName "featurespanel" -columncount 2
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Features UI created"
|
||||
}
|
||||
"AppX" {
|
||||
Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2
|
||||
Write-WinUtilPerformanceCheckpoint -Name "AppX UI created"
|
||||
}
|
||||
"Win11 Creator" {
|
||||
if ($sync.Form -and $sync.Form.Dispatcher) {
|
||||
$sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null
|
||||
}
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Win11 ISO tab initialized"
|
||||
}
|
||||
}
|
||||
|
||||
$sync.InitializedTabs[$TabName] = $true
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
function Initialize-WinUtilTaskbarOverlayAssets {
|
||||
param(
|
||||
[bool]$IncludeLogo = $true,
|
||||
[bool]$IncludeStatusAssets = $true
|
||||
)
|
||||
|
||||
if ($IncludeLogo -and -not $sync["logorender"]) {
|
||||
$sync["logorender"] = (Invoke-WinUtilAssets -Type "Logo" -Size 90 -Render)
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Taskbar logo asset rendered"
|
||||
}
|
||||
|
||||
if ($IncludeStatusAssets -and -not $sync["checkmarkrender"]) {
|
||||
$sync["checkmarkrender"] = (Invoke-WinUtilAssets -Type "checkmark" -Size 512 -Render)
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Taskbar checkmark asset rendered"
|
||||
}
|
||||
|
||||
if ($IncludeStatusAssets -and -not $sync["warningrender"]) {
|
||||
$sync["warningrender"] = (Invoke-WinUtilAssets -Type "warning" -Size 512 -Render)
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Taskbar warning asset rendered"
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,12 @@ function Install-WinUtilProgramChoco {
|
||||
)
|
||||
|
||||
if ($Action -eq 'Install') {
|
||||
Start-Process -FilePath choco -ArgumentList "install $Programs -y" -NoNewWindow -Wait
|
||||
$arguments = "install $Programs -y"
|
||||
} else {
|
||||
Start-Process -FilePath choco -ArgumentList "uninstall $Programs -y" -NoNewWindow -Wait
|
||||
$arguments = "uninstall $Programs -y"
|
||||
}
|
||||
|
||||
Write-WinUtilLog -Component "Package" -Message "$Action choco package(s): $($Programs -join ', ')"
|
||||
$process = Start-Process -FilePath choco -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||
Write-WinUtilLog -Component "Package" -Message "$Action choco package(s) completed: $($Programs -join ', ') (exit code: $($process.ExitCode))"
|
||||
}
|
||||
|
||||
@@ -8,9 +8,25 @@ Function Install-WinUtilProgramWinget {
|
||||
[string[]]$Programs
|
||||
)
|
||||
|
||||
if ($Action -eq 'Install') {
|
||||
Start-Process -FilePath winget -ArgumentList "install $Programs --accept-package-agreements --source winget --silent" -NoNewWindow -Wait
|
||||
} else {
|
||||
Start-Process -FilePath winget -ArgumentList "uninstall $Programs --source winget --silent" -NoNewWindow -Wait
|
||||
foreach ($program in $Programs) {
|
||||
if ([string]::IsNullOrWhiteSpace($program) -or $program -eq "na") {
|
||||
continue
|
||||
}
|
||||
|
||||
$source = "winget"
|
||||
if ($program.StartsWith("msstore:", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
$source = "msstore"
|
||||
$program = $program.Substring("msstore:".Length)
|
||||
}
|
||||
|
||||
if ($Action -eq 'Install') {
|
||||
$arguments = @("install", "--id", $program, "--accept-package-agreements", "--accept-source-agreements", "--source", $source, "--silent")
|
||||
} else {
|
||||
$arguments = @("uninstall", "--id", $program, "--source", $source, "--silent")
|
||||
}
|
||||
|
||||
Write-WinUtilLog -Component "Package" -Message "$Action winget package: $program (source: $source)"
|
||||
$process = Start-Process -FilePath winget -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||
Write-WinUtilLog -Component "Package" -Message "$Action winget package completed: $program (exit code: $($process.ExitCode))"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,17 @@ function Invoke-WinUtilAssets {
|
||||
[switch]$render
|
||||
)
|
||||
|
||||
if ($render -and $null -ne $sync) {
|
||||
if ($null -eq $sync.RenderedAssetCache) {
|
||||
$sync.RenderedAssetCache = @{}
|
||||
}
|
||||
|
||||
$cacheKey = "$(([string]$type).ToLowerInvariant())|$Size"
|
||||
if ($sync.RenderedAssetCache.ContainsKey($cacheKey)) {
|
||||
return $sync.RenderedAssetCache[$cacheKey]
|
||||
}
|
||||
}
|
||||
|
||||
# Create the Viewbox and set its size
|
||||
$LogoViewbox = New-Object Windows.Controls.Viewbox
|
||||
$LogoViewbox.Width = $Size
|
||||
@@ -191,6 +202,13 @@ C 21.36,47.14 28.67,50.71 30.01,52.63
|
||||
$bitmapImage.StreamSource = $imageStream
|
||||
$bitmapImage.CacheOption = [Windows.Media.Imaging.BitmapCacheOption]::OnLoad
|
||||
$bitmapImage.EndInit()
|
||||
if ($bitmapImage.CanFreeze) {
|
||||
$bitmapImage.Freeze()
|
||||
}
|
||||
|
||||
if ($null -ne $sync -and $sync.ContainsKey("RenderedAssetCache")) {
|
||||
$sync.RenderedAssetCache[$cacheKey] = $bitmapImage
|
||||
}
|
||||
|
||||
return $bitmapImage
|
||||
} else {
|
||||
|
||||
@@ -28,12 +28,16 @@ Function Invoke-WinUtilCurrentSystem {
|
||||
|
||||
$originalEncoding = [Console]::OutputEncoding
|
||||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
|
||||
$Sync.InstalledPrograms = winget list -s winget | Select-Object -skip 3 | ConvertFrom-String -PropertyNames "Name", "Id", "Version", "Available" -Delimiter '\s{2,}'
|
||||
$Sync.InstalledPrograms = @("winget", "msstore") | ForEach-Object {
|
||||
winget list -s $psitem | Select-Object -skip 3 | ConvertFrom-String -PropertyNames "Name", "Id", "Version", "Available" -Delimiter '\s{2,}'
|
||||
}
|
||||
[Console]::OutputEncoding = $originalEncoding
|
||||
|
||||
$filter = Get-WinUtilVariables -Type Checkbox | Where-Object {$psitem -like "WPFInstall*"}
|
||||
$sync.GetEnumerator() | Where-Object {$psitem.Key -in $filter} | ForEach-Object {
|
||||
$dependencies = @($sync.configs.applications.$($psitem.Key).winget -split ";")
|
||||
$dependencies = @($sync.configs.applications.$($psitem.Key).winget -split ";") | ForEach-Object {
|
||||
$psitem -replace "^msstore:", ""
|
||||
}
|
||||
|
||||
if ($dependencies[-1] -in $sync.InstalledPrograms.Id) {
|
||||
Write-Output $psitem.name
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
function Invoke-WinUtilFeatureInstall ($CheckBox) {
|
||||
Write-WinUtilLog -Component "Feature" -Message "Applying feature action: $CheckBox"
|
||||
|
||||
if ($sync.configs.feature.$CheckBox.feature) {
|
||||
foreach ($feature in $sync.configs.feature.$CheckBox.feature) {
|
||||
Write-Host "Installing $feature"
|
||||
Write-WinUtilLog -Component "Feature" -Message "Enabling Windows optional feature: $feature"
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName $feature -All -NoRestart -ErrorAction Stop
|
||||
Write-WinUtilLog -Component "Feature" -Message "Enabled Windows optional feature: $feature"
|
||||
}
|
||||
}
|
||||
|
||||
if ($sync.configs.feature.$CheckBox.InvokeScript) {
|
||||
foreach ($script in $sync.configs.feature.$CheckBox.InvokeScript) {
|
||||
Write-Host "Running Script for $CheckBox"
|
||||
Write-WinUtilLog -Component "Feature" -Message "Running feature script for: $CheckBox"
|
||||
Invoke-Command -ScriptBlock ([scriptblock]::Create($script)) -ErrorAction Stop
|
||||
Write-WinUtilLog -Component "Feature" -Message "Completed feature script for: $CheckBox"
|
||||
}
|
||||
}
|
||||
Write-WinUtilLog -Component "Feature" -Message "Feature action completed: $CheckBox"
|
||||
}
|
||||
|
||||
@@ -213,6 +213,59 @@ function Invoke-WinUtilISOModify {
|
||||
})
|
||||
}
|
||||
|
||||
function Get-WinUtilEditionIdFromName {
|
||||
param([string]$EditionName)
|
||||
|
||||
$normalizedName = ($EditionName -replace '^Windows\s+11\s+', '').Trim()
|
||||
switch -Regex ($normalizedName) {
|
||||
'^Home Single Language$' { return 'CoreSingleLanguage' }
|
||||
'^Home N$' { return 'CoreN' }
|
||||
'^Home$' { return 'Core' }
|
||||
'^Pro for Workstations N$' { return 'ProfessionalWorkstationN' }
|
||||
'^Pro for Workstations$' { return 'ProfessionalWorkstation' }
|
||||
'^Pro Education N$' { return 'ProfessionalEducationN' }
|
||||
'^Pro Education$' { return 'ProfessionalEducation' }
|
||||
'^Pro N$' { return 'ProfessionalN' }
|
||||
'^Pro$' { return 'Professional' }
|
||||
'^Education N$' { return 'EducationN' }
|
||||
'^Education$' { return 'Education' }
|
||||
'^Enterprise LTSC N$' { return 'EnterpriseSN' }
|
||||
'^Enterprise LTSC$' { return 'EnterpriseS' }
|
||||
'^Enterprise N$' { return 'EnterpriseN' }
|
||||
'^Enterprise$' { return 'Enterprise' }
|
||||
default { return '' }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WinUtilMountedImageEditionId {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$MountDir,
|
||||
[string]$EditionName,
|
||||
[scriptblock]$Logger
|
||||
)
|
||||
|
||||
try {
|
||||
$dismOutput = & dism /English "/Image:$MountDir" /Get-CurrentEdition 2>&1
|
||||
foreach ($line in $dismOutput) {
|
||||
if ($line -match '^\s*Current Edition\s*:\s*(.+?)\s*$') {
|
||||
$editionId = $Matches[1].Trim()
|
||||
if ($editionId) {
|
||||
if ($Logger) { $null = $Logger.Invoke("Detected mounted image EditionID: $editionId") }
|
||||
return $editionId
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if ($Logger) { $null = $Logger.Invoke("Warning: could not detect mounted image EditionID with DISM: $_") }
|
||||
}
|
||||
|
||||
$fallbackEditionId = Get-WinUtilEditionIdFromName -EditionName $EditionName
|
||||
if ($fallbackEditionId -and $Logger) {
|
||||
$null = $Logger.Invoke("Using fallback EditionID '$fallbackEditionId' from selected edition name.")
|
||||
}
|
||||
return $fallbackEditionId
|
||||
}
|
||||
|
||||
function Get-DismImageInfoMap {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ImagePath,
|
||||
@@ -322,9 +375,10 @@ function Invoke-WinUtilISOModify {
|
||||
Log "Mounting install.wim (Index ${selectedWimIndex}: $selectedEditionName) at $mountDir..."
|
||||
Mount-WindowsImage -ImagePath $localWim -Index $selectedWimIndex -Path $mountDir
|
||||
SetProgress "Modifying install.wim..." 45
|
||||
$selectedEditionId = Get-WinUtilMountedImageEditionId -MountDir $mountDir -EditionName $selectedEditionName -Logger ${function:Log}
|
||||
|
||||
Log "Applying WinUtil modifications to install.wim..."
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -InjectCurrentSystemDrivers $injectDrivers -Log { param($m) Log $m }
|
||||
Invoke-WinUtilISOScript -ScratchDir $mountDir -ISOContentsDir $isoContents -AutoUnattendXml $autounattendContent -InjectCurrentSystemDrivers $injectDrivers -InstallEditionId $selectedEditionId -InstallImageIndex 1 -Log { param($m) Log $m }
|
||||
|
||||
SetProgress "Cleaning up component store (WinSxS)..." 56
|
||||
Log "Running DISM component store cleanup (/ResetBase)..."
|
||||
|
||||
@@ -34,6 +34,16 @@ function Invoke-WinUtilISOScript {
|
||||
them into install.wim and boot.wim index 2 (Windows Setup PE).
|
||||
Defaults to $false.
|
||||
|
||||
.PARAMETER InstallEditionId
|
||||
Optional. Windows edition ID for the selected image, for example Professional
|
||||
or Core. Used to write sources\ei.cfg so setup does not fall back to an
|
||||
embedded firmware product key for a different edition.
|
||||
|
||||
.PARAMETER InstallImageIndex
|
||||
Optional. Image index that setup should install from the final install.wim.
|
||||
Win11 Creator exports the selected edition to a single-image WIM, so this
|
||||
defaults to 1.
|
||||
|
||||
.PARAMETER Log
|
||||
Optional ScriptBlock for progress/status logging. Receives a single [string] argument.
|
||||
|
||||
@@ -56,6 +66,8 @@ function Invoke-WinUtilISOScript {
|
||||
[string]$ISOContentsDir = "",
|
||||
[string]$AutoUnattendXml = "",
|
||||
[bool]$InjectCurrentSystemDrivers = $false,
|
||||
[string]$InstallEditionId = "",
|
||||
[int]$InstallImageIndex = 1,
|
||||
[scriptblock]$Log = { param($m) Write-Output $m }
|
||||
)
|
||||
|
||||
@@ -108,6 +120,146 @@ function Invoke-WinUtilISOScript {
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WinUtilISOScriptChildElement {
|
||||
param (
|
||||
[Parameter(Mandatory)][System.Xml.XmlElement]$Parent,
|
||||
[Parameter(Mandatory)][string]$Name,
|
||||
[Parameter(Mandatory)][string]$NamespaceUri
|
||||
)
|
||||
|
||||
foreach ($childNode in $Parent.ChildNodes) {
|
||||
if ($childNode.NodeType -eq [System.Xml.XmlNodeType]::Element -and
|
||||
$childNode.LocalName -eq $Name -and
|
||||
$childNode.NamespaceURI -eq $NamespaceUri) {
|
||||
return [System.Xml.XmlElement]$childNode
|
||||
}
|
||||
}
|
||||
|
||||
$childElement = $Parent.OwnerDocument.CreateElement($Name, $NamespaceUri)
|
||||
[void]$Parent.AppendChild($childElement)
|
||||
return $childElement
|
||||
}
|
||||
|
||||
function ConvertTo-WinUtilISOAnswerFile {
|
||||
param (
|
||||
[Parameter(Mandatory)][string]$XmlContent,
|
||||
[int]$ImageIndex = 1
|
||||
)
|
||||
|
||||
if ($ImageIndex -lt 1) { $ImageIndex = 1 }
|
||||
|
||||
$unattendNs = "urn:schemas-microsoft-com:unattend"
|
||||
$wcmNs = "http://schemas.microsoft.com/WMIConfig/2002/State"
|
||||
|
||||
$xmlDoc = [xml]::new()
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.LoadXml($XmlContent)
|
||||
|
||||
if ($xmlDoc.DocumentElement.NamespaceURI -ne $unattendNs) {
|
||||
throw "Unexpected autounattend.xml namespace: $($xmlDoc.DocumentElement.NamespaceURI)"
|
||||
}
|
||||
|
||||
if (-not $xmlDoc.DocumentElement.HasAttribute("xmlns:wcm")) {
|
||||
$xmlDoc.DocumentElement.SetAttribute("wcm", "http://www.w3.org/2000/xmlns/", $wcmNs)
|
||||
}
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("u", $unattendNs)
|
||||
|
||||
$windowsPESettings = $xmlDoc.SelectSingleNode('/u:unattend/u:settings[@pass="windowsPE"]', $nsMgr)
|
||||
if (-not $windowsPESettings) {
|
||||
$windowsPESettings = $xmlDoc.CreateElement("settings", $unattendNs)
|
||||
$windowsPESettings.SetAttribute("pass", "windowsPE")
|
||||
[void]$xmlDoc.DocumentElement.PrependChild($windowsPESettings)
|
||||
}
|
||||
|
||||
$setupComponent = $windowsPESettings.SelectSingleNode('u:component[@name="Microsoft-Windows-Setup"]', $nsMgr)
|
||||
if (-not $setupComponent) {
|
||||
$setupComponent = $xmlDoc.CreateElement("component", $unattendNs)
|
||||
$setupComponent.SetAttribute("name", "Microsoft-Windows-Setup")
|
||||
$setupComponent.SetAttribute("processorArchitecture", "amd64")
|
||||
$setupComponent.SetAttribute("publicKeyToken", "31bf3856ad364e35")
|
||||
$setupComponent.SetAttribute("language", "neutral")
|
||||
$setupComponent.SetAttribute("versionScope", "nonSxS")
|
||||
[void]$windowsPESettings.AppendChild($setupComponent)
|
||||
}
|
||||
|
||||
$productKeyNodes = @($setupComponent.SelectNodes("u:UserData/u:ProductKey", $nsMgr))
|
||||
foreach ($productKeyNode in $productKeyNodes) {
|
||||
$keyNode = $productKeyNode.SelectSingleNode("u:Key", $nsMgr)
|
||||
$keyValue = if ($keyNode) { $keyNode.InnerText.Trim() } else { "" }
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($keyValue) -or $keyValue -eq "00000-00000-00000-00000-00000") {
|
||||
[void]$productKeyNode.ParentNode.RemoveChild($productKeyNode)
|
||||
}
|
||||
}
|
||||
|
||||
$imageInstall = Get-WinUtilISOScriptChildElement -Parent $setupComponent -Name "ImageInstall" -NamespaceUri $unattendNs
|
||||
$osImage = Get-WinUtilISOScriptChildElement -Parent $imageInstall -Name "OSImage" -NamespaceUri $unattendNs
|
||||
$installFrom = Get-WinUtilISOScriptChildElement -Parent $osImage -Name "InstallFrom" -NamespaceUri $unattendNs
|
||||
|
||||
$existingMetadataNodes = @($installFrom.SelectNodes("u:MetaData", $nsMgr))
|
||||
foreach ($metadataNode in $existingMetadataNodes) {
|
||||
[void]$installFrom.RemoveChild($metadataNode)
|
||||
}
|
||||
|
||||
$metadata = $xmlDoc.CreateElement("MetaData", $unattendNs)
|
||||
$actionAttribute = $xmlDoc.CreateAttribute("wcm", "action", $wcmNs)
|
||||
$actionAttribute.Value = "add"
|
||||
[void]$metadata.Attributes.Append($actionAttribute)
|
||||
|
||||
$keyElement = $xmlDoc.CreateElement("Key", $unattendNs)
|
||||
$keyElement.InnerText = "/IMAGE/INDEX"
|
||||
[void]$metadata.AppendChild($keyElement)
|
||||
|
||||
$valueElement = $xmlDoc.CreateElement("Value", $unattendNs)
|
||||
$valueElement.InnerText = [string]$ImageIndex
|
||||
[void]$metadata.AppendChild($valueElement)
|
||||
|
||||
[void]$installFrom.AppendChild($metadata)
|
||||
|
||||
return $xmlDoc.OuterXml
|
||||
}
|
||||
|
||||
function Write-WinUtilISOEditionConfig {
|
||||
param (
|
||||
[Parameter(Mandatory)][string]$ContentRoot,
|
||||
[string]$EditionId,
|
||||
[scriptblock]$Logger
|
||||
)
|
||||
|
||||
if (-not (Test-Path $ContentRoot)) {
|
||||
return
|
||||
}
|
||||
|
||||
$sourcesDir = Join-Path $ContentRoot "sources"
|
||||
New-Item -Path $sourcesDir -ItemType Directory -Force | Out-Null
|
||||
|
||||
$pidPath = Join-Path $sourcesDir "PID.txt"
|
||||
if (Test-Path $pidPath) {
|
||||
Remove-Item -Path $pidPath -Force
|
||||
& $Logger "Removed sources\PID.txt so setup will not force a stale or mismatched product key."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($EditionId)) {
|
||||
& $Logger "Warning: selected edition ID is unknown - skipping sources\ei.cfg fallback."
|
||||
return
|
||||
}
|
||||
|
||||
$eiCfgPath = Join-Path $sourcesDir "ei.cfg"
|
||||
$eiCfg = @"
|
||||
[EditionID]
|
||||
$EditionId
|
||||
[Channel]
|
||||
Retail
|
||||
[VL]
|
||||
0
|
||||
"@.Trim()
|
||||
|
||||
Set-Content -Path $eiCfgPath -Value $eiCfg -Encoding ASCII -Force
|
||||
& $Logger "Written sources\ei.cfg for EditionID '$EditionId'."
|
||||
}
|
||||
|
||||
# -- 1. Remove provisioned AppX packages ----------------------------------
|
||||
& $Log "Removing provisioned AppX packages..."
|
||||
|
||||
@@ -218,9 +370,17 @@ function Invoke-WinUtilISOScript {
|
||||
Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1'
|
||||
|
||||
if ($AutoUnattendXml) {
|
||||
$preparedAutoUnattendXml = $AutoUnattendXml
|
||||
try {
|
||||
$preparedAutoUnattendXml = ConvertTo-WinUtilISOAnswerFile -XmlContent $AutoUnattendXml -ImageIndex $InstallImageIndex
|
||||
& $Log "Prepared autounattend.xml to install image index $InstallImageIndex without forcing a product key."
|
||||
} catch {
|
||||
& $Log "Warning: could not prepare autounattend.xml image selection: $_"
|
||||
}
|
||||
|
||||
try {
|
||||
$xmlDoc = [xml]::new()
|
||||
$xmlDoc.LoadXml($AutoUnattendXml)
|
||||
$xmlDoc.LoadXml($preparedAutoUnattendXml)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("sg", "https://schneegans.de/windows/unattend-generator/")
|
||||
@@ -251,13 +411,17 @@ function Invoke-WinUtilISOScript {
|
||||
|
||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||
$isoDest = Join-Path $ISOContentsDir "autounattend.xml"
|
||||
Set-Content -Path $isoDest -Value $AutoUnattendXml -Encoding UTF8 -Force
|
||||
Set-Content -Path $isoDest -Value $preparedAutoUnattendXml -Encoding UTF8 -Force
|
||||
& $Log "Written autounattend.xml to ISO root ($isoDest)."
|
||||
}
|
||||
} else {
|
||||
& $Log "Warning: autounattend.xml content is empty - skipping OOBE bypass file."
|
||||
}
|
||||
|
||||
if ($ISOContentsDir -and (Test-Path $ISOContentsDir)) {
|
||||
Write-WinUtilISOEditionConfig -ContentRoot $ISOContentsDir -EditionId $InstallEditionId -Logger $Log
|
||||
}
|
||||
|
||||
& $Log "Disabling reserved storage..."
|
||||
Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0'
|
||||
|
||||
|
||||
@@ -22,23 +22,30 @@ function Invoke-WinUtilScript {
|
||||
|
||||
try {
|
||||
Write-Host "Running Script for $Name"
|
||||
Write-WinUtilLog -Component "Script" -Message "Running script for $Name"
|
||||
Invoke-Command $scriptblock -ErrorAction Stop
|
||||
Write-WinUtilLog -Component "Script" -Message "Completed script for $Name"
|
||||
} catch [System.Management.Automation.CommandNotFoundException] {
|
||||
Write-Warning "The specified command was not found."
|
||||
Write-Warning $PSItem.Exception.message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Script" -Message "Command not found while running script for $Name`: $($PSItem.Exception.Message)"
|
||||
} catch [System.Management.Automation.RuntimeException] {
|
||||
Write-Warning "A runtime exception occurred."
|
||||
Write-Warning $PSItem.Exception.message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Script" -Message "Runtime exception while running script for $Name`: $($PSItem.Exception.Message)"
|
||||
} catch [System.Security.SecurityException] {
|
||||
Write-Warning "A security exception occurred."
|
||||
Write-Warning $PSItem.Exception.message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Script" -Message "Security exception while running script for $Name`: $($PSItem.Exception.Message)"
|
||||
} catch [System.UnauthorizedAccessException] {
|
||||
Write-Warning "Access denied. You do not have permission to perform this operation."
|
||||
Write-Warning $PSItem.Exception.message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Script" -Message "Access denied while running script for $Name`: $($PSItem.Exception.Message)"
|
||||
} catch {
|
||||
# Generic catch block to handle any other type of exception
|
||||
Write-Warning "Unable to run script for $Name due to unhandled exception."
|
||||
Write-Warning $psitem.Exception.StackTrace
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Script" -Message "Unhandled exception while running script for $Name`: $($psitem.Exception.Message)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ function Invoke-WinUtilTweaks {
|
||||
$KeepServiceStartup = $true
|
||||
)
|
||||
|
||||
$action = if ($undo) { "Undo" } else { "Apply" }
|
||||
Write-WinUtilLog -Component "Tweaks" -Message "$action tweak: $CheckBox"
|
||||
|
||||
if ($undo) {
|
||||
$Values = @{
|
||||
Registry = "OriginalValue"
|
||||
@@ -77,4 +80,5 @@ function Invoke-WinUtilTweaks {
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-WinUtilLog -Component "Tweaks" -Message "$action tweak completed: $CheckBox"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ function Remove-WinUtilAPPX {
|
||||
)
|
||||
|
||||
Write-Host "Removing $Name"
|
||||
Write-WinUtilLog -Component "AppX" -Message "Removing AppX package pattern: $Name"
|
||||
Get-AppxPackage $Name -AllUsers | Remove-AppxPackage -AllUsers
|
||||
Get-AppxProvisionedPackage -Online | Where-Object DisplayName -like $Name | Remove-AppxProvisionedPackage -Online
|
||||
Write-WinUtilLog -Component "AppX" -Message "AppX removal completed for package pattern: $Name"
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ function Reset-WPFCheckBoxes {
|
||||
[string]$checkboxfilterpattern = "**"
|
||||
)
|
||||
|
||||
$CheckBoxesToCheck = $sync.selectedApps + $sync.selectedTweaks + $sync.selectedFeatures
|
||||
$CheckBoxesToCheck = $sync.selectedApps + $sync.selectedTweaks + $sync.selectedFeatures + $sync.selectedAppx
|
||||
$CheckBoxes = ($sync.GetEnumerator()).where{ $_.Value -is [System.Windows.Controls.CheckBox] -and $_.Name -notlike "WPFToggle*" -and $_.Name -like "$checkboxfilterpattern"}
|
||||
|
||||
foreach ($CheckBox in $CheckBoxes) {
|
||||
|
||||
@@ -12,22 +12,31 @@ function Set-WinUtilDNS {
|
||||
|
||||
#>
|
||||
param($DNSProvider)
|
||||
if($DNSProvider -eq "Default") {return}
|
||||
if($DNSProvider -eq "Default") {
|
||||
Write-WinUtilLog -Component "DNS" -Message "DNS provider is Default; no DNS changes applied."
|
||||
return
|
||||
}
|
||||
try {
|
||||
$Adapters = Get-NetAdapter | Where-Object {$_.Status -eq "Up"}
|
||||
Write-Host "Ensuring DNS is set to $DNSProvider on the following interfaces:"
|
||||
Write-Host $($Adapters | Out-String)
|
||||
Write-WinUtilLog -Component "DNS" -Message "Setting DNS provider to $DNSProvider for $(@($Adapters).Count) active adapter(s)."
|
||||
|
||||
Foreach ($Adapter in $Adapters) {
|
||||
if($DNSProvider -eq "DHCP") {
|
||||
Write-WinUtilLog -Component "DNS" -Message "Resetting DNS to DHCP on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex))."
|
||||
Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ResetServerAddresses
|
||||
} else {
|
||||
Write-WinUtilLog -Component "DNS" -Message "Setting IPv4 DNS on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex)) to $($sync.configs.dns.$DNSProvider.Primary), $($sync.configs.dns.$DNSProvider.Secondary)."
|
||||
Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ServerAddresses ("$($sync.configs.dns.$DNSProvider.Primary)", "$($sync.configs.dns.$DNSProvider.Secondary)")
|
||||
Write-WinUtilLog -Component "DNS" -Message "Setting IPv6 DNS on adapter $($Adapter.Name) (ifIndex: $($Adapter.ifIndex)) to $($sync.configs.dns.$DNSProvider.Primary6), $($sync.configs.dns.$DNSProvider.Secondary6)."
|
||||
Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ServerAddresses ("$($sync.configs.dns.$DNSProvider.Primary6)", "$($sync.configs.dns.$DNSProvider.Secondary6)")
|
||||
}
|
||||
}
|
||||
Write-WinUtilLog -Component "DNS" -Message "DNS provider change completed: $DNSProvider"
|
||||
} catch {
|
||||
Write-Warning "Unable to set DNS Provider due to an unhandled exception."
|
||||
Write-Warning $psitem.Exception.StackTrace
|
||||
Write-WinUtilLog -Level "ERROR" -Component "DNS" -Message "Unable to set DNS provider $DNSProvider`: $($psitem.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,25 +32,32 @@ function Set-WinUtilRegistry {
|
||||
|
||||
If (!(Test-Path $Path)) {
|
||||
Write-Host "$Path was not found. Creating..."
|
||||
Write-WinUtilLog -Component "Registry" -Message "Creating registry path: $Path"
|
||||
New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
|
||||
if ($Value -ne "<RemoveEntry>") {
|
||||
Write-Host "Set $Path\$Name to $Value"
|
||||
Write-WinUtilLog -Component "Registry" -Message "Setting $Path\$Name ($Type) to $Value"
|
||||
Set-ItemProperty -Path $Path -Name $Name -Type $Type -Value $Value -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
else{
|
||||
Write-Host "Remove $Path\$Name"
|
||||
Write-WinUtilLog -Component "Registry" -Message "Removing $Path\$Name"
|
||||
Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
} catch [System.Security.SecurityException] {
|
||||
Write-Warning "Unable to set $Path\$Name to $Value due to a Security Exception."
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Registry" -Message "Security exception while changing $Path\$Name to $Value`: $($psitem.Exception.Message)"
|
||||
} catch [System.Management.Automation.ItemNotFoundException] {
|
||||
Write-Warning $psitem.Exception.ErrorRecord
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Registry" -Message "Registry item not found while changing $Path\$Name`: $($psitem.Exception.Message)"
|
||||
} catch [System.UnauthorizedAccessException] {
|
||||
Write-Warning $psitem.Exception.Message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Registry" -Message "Unauthorized while changing $Path\$Name`: $($psitem.Exception.Message)"
|
||||
} catch {
|
||||
Write-Warning "Unable to set $Name due to unhandled exception."
|
||||
Write-Warning $psitem.Exception.StackTrace
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Registry" -Message "Unhandled exception while changing $Path\$Name`: $($psitem.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,21 +20,33 @@ Function Set-WinUtilService {
|
||||
)
|
||||
try {
|
||||
Write-Host "Setting Service $Name to $StartupType"
|
||||
Write-WinUtilLog -Component "Service" -Message "Setting service $Name startup type to $StartupType"
|
||||
|
||||
# Check if the service exists
|
||||
$service = Get-Service -Name $Name -ErrorAction Stop
|
||||
|
||||
if (($service.PSObject.Properties.Name -contains "StartType") -and ([string]$service.StartType -eq [string]$StartupType) ) {
|
||||
Write-Host "Service $Name is already set to $StartupType"
|
||||
Write-WinUtilLog -Component "Service" -Message "Service $Name startup type is already $StartupType; no change needed."
|
||||
return
|
||||
}
|
||||
|
||||
# Service exists, proceed with changing properties -- while handling auto delayed start for PWSH 5
|
||||
if (($PSVersionTable.PSVersion.Major -lt 7) -and ($StartupType -eq "AutomaticDelayedStart")) {
|
||||
sc.exe config $Name start=delayed-auto
|
||||
} else {
|
||||
$service | Set-Service -StartupType $StartupType -ErrorAction Stop
|
||||
}
|
||||
} catch [System.ServiceProcess.ServiceNotFoundException] {
|
||||
Write-Warning "Service $Name was not found."
|
||||
Write-WinUtilLog -Component "Service" -Message "Service $Name startup type set to $StartupType"
|
||||
} catch {
|
||||
Write-Warning "Unable to set $Name due to unhandled exception."
|
||||
Write-Warning $_.Exception.Message
|
||||
if ($_.FullyQualifiedErrorId -like "NoServiceFoundForGivenName,*") {
|
||||
Write-Warning "Service $Name was not found."
|
||||
Write-WinUtilLog -Level "WARN" -Component "Service" -Message "Service $Name was not found."
|
||||
} else {
|
||||
Write-Warning "Unable to set $Name due to unhandled exception."
|
||||
Write-Warning $_.Exception.Message
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Service" -Message "Unable to set service $Name to $StartupType`: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,12 +61,21 @@ function Set-WinUtilTaskbaritem {
|
||||
if ($overlay) {
|
||||
switch ($overlay) {
|
||||
'logo' {
|
||||
if (-not $sync["logorender"]) {
|
||||
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $true -IncludeStatusAssets $false
|
||||
}
|
||||
$sync["Form"].taskbarItemInfo.Overlay = $sync["logorender"]
|
||||
}
|
||||
'checkmark' {
|
||||
if (-not $sync["checkmarkrender"]) {
|
||||
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true
|
||||
}
|
||||
$sync["Form"].taskbarItemInfo.Overlay = $sync["checkmarkrender"]
|
||||
}
|
||||
'warning' {
|
||||
if (-not $sync["warningrender"]) {
|
||||
Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true
|
||||
}
|
||||
$sync["Form"].taskbarItemInfo.Overlay = $sync["warningrender"]
|
||||
}
|
||||
'None' {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
function Show-WinUtilMessage {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shows a WinUtil message box and returns the selected result.
|
||||
#>
|
||||
param (
|
||||
[string]$Message,
|
||||
[string]$Title = "Winutil",
|
||||
$Button = "OK",
|
||||
$Icon = "Information"
|
||||
)
|
||||
|
||||
[System.Windows.MessageBox]::Show($Message, $Title, $Button, $Icon)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
function Invoke-WinUtilInstallAppRenderBatch {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
$CategoryBatch
|
||||
)
|
||||
|
||||
foreach ($appKey in $CategoryBatch.AppKeys) {
|
||||
$sync.$appKey = Initialize-InstallAppEntry -TargetElement $CategoryBatch.TargetElement -AppKey $appKey
|
||||
}
|
||||
|
||||
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
|
||||
}
|
||||
}
|
||||
|
||||
function Complete-WinUtilInstallAppRendering {
|
||||
$sync.InstallAppEntriesRendered = $true
|
||||
Write-WinUtilPerformanceCheckpoint -Name "Install app entries rendered"
|
||||
}
|
||||
|
||||
function Invoke-WinUtilInstallAppRenderNextBatch {
|
||||
if ($sync.InstallAppRenderQueue.Count -gt 0) {
|
||||
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
|
||||
Invoke-WinUtilInstallAppRenderBatch -CategoryBatch $categoryBatch
|
||||
}
|
||||
|
||||
if ($sync.InstallAppRenderQueue.Count -gt 0) {
|
||||
$sync.Form.Dispatcher.BeginInvoke(
|
||||
[System.Windows.Threading.DispatcherPriority]::Background,
|
||||
[action]{ Invoke-WinUtilInstallAppRenderNextBatch }
|
||||
) | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
Complete-WinUtilInstallAppRendering
|
||||
}
|
||||
|
||||
function Start-WinUtilInstallAppRendering {
|
||||
if ($null -eq $sync.InstallAppRenderQueue) {
|
||||
return
|
||||
}
|
||||
|
||||
$sync.InstallAppEntriesRendered = $false
|
||||
|
||||
if ($sync.Form -and $sync.Form.Dispatcher) {
|
||||
$sync.Form.Dispatcher.BeginInvoke(
|
||||
[System.Windows.Threading.DispatcherPriority]::Background,
|
||||
[action]{ Invoke-WinUtilInstallAppRenderNextBatch }
|
||||
) | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
while ($sync.InstallAppRenderQueue.Count -gt 0) {
|
||||
$categoryBatch = $sync.InstallAppRenderQueue.Dequeue()
|
||||
Invoke-WinUtilInstallAppRenderBatch -CategoryBatch $categoryBatch
|
||||
}
|
||||
|
||||
Complete-WinUtilInstallAppRendering
|
||||
}
|
||||
@@ -1,52 +1,14 @@
|
||||
function Update-WinUtilSelections {
|
||||
<#
|
||||
function Update-WinUtilSelections ($flatJson) {
|
||||
foreach ($cbkey in $flatJson) {
|
||||
|
||||
.SYNOPSIS
|
||||
Updates the $sync.selected variables with a given preset.
|
||||
|
||||
.PARAMETER flatJson
|
||||
The flattened json list of $sync values to select.
|
||||
#>
|
||||
|
||||
param (
|
||||
$flatJson
|
||||
)
|
||||
|
||||
foreach ($item in $flatJson) {
|
||||
# Ensure each item is treated as a string to handle PSCustomObject from JSON deserialization
|
||||
$cbkey = [string]$item
|
||||
$group = if ($cbkey.StartsWith("WPFInstall")) { "Install" }
|
||||
elseif ($cbkey.StartsWith("WPFTweaks")) { "Tweaks" }
|
||||
elseif ($cbkey.StartsWith("WPFToggle")) { "Toggle" }
|
||||
elseif ($cbkey.StartsWith("WPFFeature")) { "Feature" }
|
||||
else { "na" }
|
||||
|
||||
switch ($group) {
|
||||
"Install" {
|
||||
if (!$sync.selectedApps.Contains($cbkey)) {
|
||||
$sync.selectedApps.Add($cbkey)
|
||||
# The List type needs to be specified again, because otherwise Sort-Object will convert the list to a string if there is only a single entry
|
||||
[System.Collections.Generic.List[string]]$sync.selectedApps = $sync.SelectedApps | Sort-Object
|
||||
}
|
||||
}
|
||||
"Tweaks" {
|
||||
if (!$sync.selectedTweaks.Contains($cbkey)) {
|
||||
$sync.selectedTweaks.Add($cbkey)
|
||||
}
|
||||
}
|
||||
"Toggle" {
|
||||
if (!$sync.selectedToggles.Contains($cbkey)) {
|
||||
$sync.selectedToggles.Add($cbkey)
|
||||
}
|
||||
}
|
||||
"Feature" {
|
||||
if (!$sync.selectedFeatures.Contains($cbkey)) {
|
||||
$sync.selectedFeatures.Add($cbkey)
|
||||
}
|
||||
}
|
||||
default {
|
||||
Write-Host "Unknown group for checkbox: $($cbkey)"
|
||||
}
|
||||
$listName = switch -Regex ($cbkey) {
|
||||
'^WPFInstall' { 'selectedApps' }
|
||||
'^WPFTweaks' { 'selectedTweaks' }
|
||||
'^WPFToggle' { 'selectedToggles' }
|
||||
'^WPFFeature' { 'selectedFeatures' }
|
||||
'^WPFAppx' { 'selectedAppx' }
|
||||
}
|
||||
|
||||
$sync.$listName.Add($cbkey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
function Write-WinUtilLog {
|
||||
<#
|
||||
|
||||
.SYNOPSIS
|
||||
Writes a timestamped WinUtil log entry to the active session log.
|
||||
|
||||
.PARAMETER Message
|
||||
The message to write.
|
||||
|
||||
.PARAMETER Level
|
||||
The severity level for the log entry.
|
||||
|
||||
.PARAMETER Component
|
||||
The WinUtil component producing the log entry.
|
||||
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Message,
|
||||
|
||||
[ValidateSet("INFO", "WARN", "ERROR", "DEBUG")]
|
||||
[string]$Level = "INFO",
|
||||
|
||||
[string]$Component = "WinUtil"
|
||||
)
|
||||
|
||||
try {
|
||||
$logPath = $null
|
||||
$transcriptPath = $null
|
||||
if ($null -ne $sync -and $sync.ContainsKey("logPath")) {
|
||||
$logPath = $sync.logPath
|
||||
}
|
||||
|
||||
if ($null -ne $sync -and $sync.ContainsKey("transcriptPath")) {
|
||||
$transcriptPath = $sync.transcriptPath
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($logPath) -and -not [string]::IsNullOrWhiteSpace($transcriptPath)) {
|
||||
$logPath = $transcriptPath
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($logPath) -and $null -ne $sync -and $sync.ContainsKey("winutildir")) {
|
||||
$logDirectory = Join-Path $sync.winutildir "logs"
|
||||
$logPath = Join-Path $logDirectory "winutil_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log"
|
||||
$sync.logPath = $logPath
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($logPath) -and -not [string]::IsNullOrWhiteSpace($env:LocalAppData)) {
|
||||
if ([string]::IsNullOrWhiteSpace($script:WinUtilLogPath)) {
|
||||
$logDirectory = Join-Path (Join-Path $env:LocalAppData "winutil") "logs"
|
||||
$script:WinUtilLogPath = Join-Path $logDirectory "winutil_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log"
|
||||
}
|
||||
$logPath = $script:WinUtilLogPath
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($logPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$logDirectory = Split-Path -Path $logPath -Parent
|
||||
if (-not (Test-Path $logDirectory)) {
|
||||
New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"
|
||||
$line = "[$timestamp] [$Level] [$Component] $Message"
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($transcriptPath) -and $logPath -eq $transcriptPath) {
|
||||
Write-Host $line
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Add-Content -Path $logPath -Value $line -Encoding UTF8 -ErrorAction Stop
|
||||
} catch [System.IO.IOException] {
|
||||
Write-Host $line
|
||||
}
|
||||
} catch {
|
||||
Write-Warning "Unable to write WinUtil log entry: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
function Invoke-WPFAppxRemoval {
|
||||
if ($null -eq $sync.selectedAppx -or $sync.selectedAppx.Count -eq 0) {
|
||||
Show-WinUtilMessage -Message "No AppX Package selected" -Title "Error" -Button "OK" -Icon "Error"
|
||||
return
|
||||
}
|
||||
|
||||
$selected = $sync.selectedAppx
|
||||
$apps = $sync.configs.appxHashtable
|
||||
|
||||
$handle = Invoke-WPFRunspace -ParameterList @(("selected", $selected), ("apps", $apps)) -ScriptBlock {
|
||||
param($selected, $apps)
|
||||
|
||||
$sync.ProcessRunning = $true
|
||||
Write-WinUtilLog -Component "AppX" -Message "Starting AppX removal for $(@($selected).Count) selected package(s)."
|
||||
|
||||
foreach ($key in $selected) {
|
||||
if ($key -eq "WPFAppxMicrosoft_XboxGamingOverlay") {
|
||||
# Making sure Game Bar isn't running
|
||||
Write-WinUtilLog -Component "AppX" -Message "Stopping GameBarFTServer before removing Xbox Gaming Overlay."
|
||||
Stop-Process -Name GameBarFTServer
|
||||
|
||||
# This stops annoying ms-gamebar popup when launching games.
|
||||
Write-WinUtilLog -Component "AppX" -Message "Disabling Game DVR capture before removing Xbox Gaming Overlay."
|
||||
Set-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\GameDVR -Name AppCaptureEnabled -Value 0
|
||||
}
|
||||
|
||||
if ($key -eq "WPFAppxMicrosoft_WindowsNotepad") {
|
||||
# i hope your having fun reading this
|
||||
Write-WinUtilLog -Component "AppX" -Message "Stopping dllhost before removing Notepad."
|
||||
Stop-Process -Name dllhost
|
||||
}
|
||||
|
||||
Write-Host "Removing $($apps[$key].Content)"
|
||||
Write-WinUtilLog -Component "AppX" -Message "Removing $($apps[$key].Content) ($($apps[$key].PackageId))."
|
||||
Get-AppxPackage -Name $apps[$key].PackageId -AllUsers | Remove-AppxPackage -AllUsers
|
||||
|
||||
if ($key -eq "WPFAppxMSTeams") {
|
||||
# Uninstalls Microsoft Teams Meeting Add-in for Microsoft Office
|
||||
Write-WinUtilLog -Component "AppX" -Message "Uninstalling Microsoft Teams meeting add-in package."
|
||||
Get-Package -Name "Microsoft Teams*" -ErrorAction SilentlyContinue | Uninstall-Package -Force
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "================================="
|
||||
Write-Host "-- AppX Removal Finished ---"
|
||||
Write-Host "================================="
|
||||
Write-WinUtilLog -Component "AppX" -Message "AppX removal finished."
|
||||
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,22 @@ function Invoke-WPFButton {
|
||||
"WPFUpdatessecurity" {Invoke-WPFUpdatessecurity}
|
||||
"WPFGetInstalled" {Invoke-WPFGetInstalled -CheckBox "winget"}
|
||||
"WPFGetInstalledTweaks" {Invoke-WPFGetInstalled -CheckBox "tweaks"}
|
||||
"WPFRemoveSelectedAppx" {Invoke-WPFAppxRemoval}
|
||||
"WPFDefaultAppxSelection" {Invoke-WPFPresets "AppxDefault" -checkboxfilterpattern "WPFAppx*"}
|
||||
"WPFSelectAllAppx" {
|
||||
$sync.configs.appxHashtable.Keys | ForEach-Object {$sync.$_.IsChecked = $true}
|
||||
}
|
||||
"WPFClearAppxSelection" {
|
||||
$sync.configs.appxHashtable.Keys | ForEach-Object {$sync.$_.IsChecked = $false}
|
||||
}
|
||||
"WPFGetInstalledAppx" {
|
||||
$installedAppxPackages = Get-AppxPackage -AllUsers | Select-Object -ExpandProperty Name
|
||||
foreach ($appx in $sync.configs.appxHashtable.GetEnumerator()) {
|
||||
if ($appx.Value.PackageId -in $installedAppxPackages) {
|
||||
$sync.$($appx.Key).IsChecked = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
"WPFCloseButton" {$sync.Form.Close(); Write-Host "Bye bye!"}
|
||||
"WPFMinimizeButton" {$sync.Form.WindowState = [Windows.WindowState]::Minimized}
|
||||
"WPFselectedAppsButton" {$sync.selectedAppsPopup.IsOpen = -not $sync.selectedAppsPopup.IsOpen}
|
||||
|
||||
@@ -44,10 +44,10 @@ function Invoke-WPFImpex {
|
||||
try {
|
||||
$Config = ConfigDialog
|
||||
if ($Config) {
|
||||
$allConfs = ($sync.selectedApps + $sync.selectedTweaks + $sync.selectedToggles + $sync.selectedFeatures) | ForEach-Object { [string]$_ }
|
||||
$allConfs = ($sync.selectedApps + $sync.selectedTweaks + $sync.selectedToggles + $sync.selectedFeatures + $sync.selectedAppx) | ForEach-Object { [string]$_ }
|
||||
if (-not $allConfs) {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"No settings are selected to export. Please select at least one app, tweak, toggle, or feature before exporting.",
|
||||
"No settings are selected to export. Please select at least one app, tweak, toggle, feature, or AppX package before exporting.",
|
||||
"Nothing to Export", "OK", "Warning")
|
||||
return
|
||||
}
|
||||
@@ -86,6 +86,7 @@ function Invoke-WPFImpex {
|
||||
|
||||
# Clear all existing selections before importing so the import replaces
|
||||
# the current state rather than merging with it
|
||||
$sync.selectedAppx = [System.Collections.Generic.List[string]]::new()
|
||||
$sync.selectedApps = [System.Collections.Generic.List[string]]::new()
|
||||
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
|
||||
$sync.selectedToggles = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
@@ -9,17 +9,20 @@ function Invoke-WPFInstall {
|
||||
|
||||
if($sync.ProcessRunning) {
|
||||
$msg = "[Invoke-WPFInstall] An Install process is currently running."
|
||||
[System.Windows.MessageBox]::Show($msg, "Winutil", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
|
||||
Show-WinUtilMessage -Message $msg -Title "Winutil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
if ($PackagesToInstall.Count -eq 0) {
|
||||
$WarningMsg = "Please select the program(s) to install or upgrade."
|
||||
[System.Windows.MessageBox]::Show($WarningMsg, $AppTitle, [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
|
||||
Show-WinUtilMessage -Message $WarningMsg -Title $AppTitle -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
$ManagerPreference = $sync.preferences.packagemanager
|
||||
Write-WinUtilLog -Component "Install" -Message "Install requested for $(@($PackagesToInstall).Count) selected package(s) using preference: $ManagerPreference"
|
||||
$packageSummary = Get-WinUtilPackageLogSummary -Packages $PackagesToInstall -Preference $ManagerPreference
|
||||
Write-WinUtilLog -Component "Install" -Message "Install selected package(s): $($packageSummary -join '; ')"
|
||||
|
||||
$handle = Invoke-WPFRunspace -ParameterList @(("PackagesToInstall", $PackagesToInstall),("ManagerPreference", $ManagerPreference)) -ScriptBlock {
|
||||
param($PackagesToInstall, $ManagerPreference)
|
||||
@@ -28,6 +31,7 @@ function Invoke-WPFInstall {
|
||||
|
||||
$packagesWinget = $packagesSorted[[PackageManagers]::Winget]
|
||||
$packagesChoco = $packagesSorted[[PackageManagers]::Choco]
|
||||
Write-WinUtilLog -Component "Install" -Message "Install package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
|
||||
|
||||
try {
|
||||
$sync.ProcessRunning = $true
|
||||
@@ -44,13 +48,17 @@ function Invoke-WPFInstall {
|
||||
Write-Host "==========================================="
|
||||
Write-Host "-- Installs have finished ---"
|
||||
Write-Host "==========================================="
|
||||
Write-WinUtilLog -Component "Install" -Message "Install workflow completed."
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
} catch {
|
||||
Hide-WPFInstallAppBusy
|
||||
Write-Host "==========================================="
|
||||
Write-Host "Error: $_"
|
||||
Write-Host "==========================================="
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Install" -Message "Install workflow failed: $($_.Exception.Message)"
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
} finally {
|
||||
$sync.ProcessRunning = $False
|
||||
}
|
||||
$sync.ProcessRunning = $False
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ function Invoke-WPFPresets {
|
||||
switch ($checkboxfilterpattern) {
|
||||
"WPFTweak*" { $sync.selectedTweaks = [System.Collections.Generic.List[string]]::new() }
|
||||
"WPFInstall*" { $sync.selectedApps = [System.Collections.Generic.List[string]]::new() }
|
||||
"WPFAppx*" { $sync.selectedAppx = [System.Collections.Generic.List[string]]::new() }
|
||||
"WPFeatures" { $sync.selectedFeatures = [System.Collections.Generic.List[string]]::new() }
|
||||
"WPFToggle" { $sync.selectedToggles = [System.Collections.Generic.List[string]]::new() }
|
||||
default {}
|
||||
|
||||
@@ -30,30 +30,67 @@ function Invoke-WPFRunspace {
|
||||
$ParameterList
|
||||
)
|
||||
|
||||
if (-not ("WinUtilRunspaceCleanup" -as [type])) {
|
||||
Add-Type @"
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
|
||||
public sealed class WinUtilRunspaceCleanupState
|
||||
{
|
||||
public PowerShell PowerShell { get; set; }
|
||||
public IAsyncResult Handle { get; set; }
|
||||
}
|
||||
|
||||
public static class WinUtilRunspaceCleanup
|
||||
{
|
||||
public static void Cleanup(object state, bool timedOut)
|
||||
{
|
||||
var cleanupState = state as WinUtilRunspaceCleanupState;
|
||||
if (cleanupState == null || cleanupState.PowerShell == null || cleanupState.Handle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
cleanupState.PowerShell.EndInvoke(cleanupState.Handle);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
cleanupState.PowerShell.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
Initialize-WinUtilRunspacePool | Out-Null
|
||||
|
||||
# Create a PowerShell instance
|
||||
$script:powershell = [powershell]::Create()
|
||||
$powershell = [powershell]::Create()
|
||||
|
||||
# Add Scriptblock and Arguments to runspace
|
||||
$script:powershell.AddScript($ScriptBlock)
|
||||
$script:powershell.AddArgument($ArgumentList)
|
||||
[void]$powershell.AddScript($ScriptBlock)
|
||||
[void]$powershell.AddArgument($ArgumentList)
|
||||
|
||||
foreach ($parameter in $ParameterList) {
|
||||
$script:powershell.AddParameter($parameter[0], $parameter[1])
|
||||
[void]$powershell.AddParameter($parameter[0], $parameter[1])
|
||||
}
|
||||
|
||||
$script:powershell.RunspacePool = $sync.runspace
|
||||
$powershell.RunspacePool = $sync.runspace
|
||||
|
||||
# Execute the RunspacePool
|
||||
$script:handle = $script:powershell.BeginInvoke()
|
||||
$handle = $powershell.BeginInvoke()
|
||||
|
||||
$cleanupState = [WinUtilRunspaceCleanupState]::new()
|
||||
$cleanupState.PowerShell = $powershell
|
||||
$cleanupState.Handle = $handle
|
||||
$cleanupCallback = [System.Threading.WaitOrTimerCallback][WinUtilRunspaceCleanup]::Cleanup
|
||||
[System.Threading.ThreadPool]::RegisterWaitForSingleObject($handle.AsyncWaitHandle, $cleanupCallback, $cleanupState, -1, $true) | Out-Null
|
||||
|
||||
# Clean up the RunspacePool threads when they are complete, and invoke the garbage collector to clean up the memory
|
||||
if ($script:handle.IsCompleted) {
|
||||
$script:powershell.EndInvoke($script:handle)
|
||||
$script:powershell.Dispose()
|
||||
$sync.runspace.Dispose()
|
||||
$sync.runspace.Close()
|
||||
[System.GC]::Collect()
|
||||
}
|
||||
# Return the handle
|
||||
return $handle
|
||||
}
|
||||
|
||||
@@ -1,88 +1,17 @@
|
||||
function Invoke-WPFSelectedCheckboxesUpdate{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
This is a helper function that is called by the Checked and Unchecked events of the Checkboxes.
|
||||
It also Updates the "Selected Apps" selectedAppLabel on the Install Tab to represent the current collection
|
||||
.PARAMETER type
|
||||
Either: Add | Remove
|
||||
.PARAMETER checkboxName
|
||||
should contain the name of the current instance of the checkbox that triggered the Event.
|
||||
Most of the time will be the automatic variable $this.Parent.Tag
|
||||
.EXAMPLE
|
||||
$checkbox.Add_Unchecked({Invoke-WPFSelectedCheckboxesUpdate -type "Remove" -checkboxName $this.Parent.Tag})
|
||||
OR
|
||||
Invoke-WPFSelectedCheckboxesUpdate -type "Add" -checkboxName $specificCheckbox.Parent.Tag
|
||||
#>
|
||||
param (
|
||||
$type,
|
||||
$checkboxName
|
||||
)
|
||||
|
||||
if (($type -ne "Add") -and ($type -ne "Remove"))
|
||||
{
|
||||
Write-Error "Type: $type not implemented"
|
||||
return
|
||||
function Invoke-WPFSelectedCheckboxesUpdate ($type, $checkboxName) {
|
||||
$listName = switch -Regex ($checkboxName) {
|
||||
'^WPFInstall' { 'selectedApps' }
|
||||
'^WPFTweaks' { 'selectedTweaks' }
|
||||
'^WPFToggle' { 'selectedToggles' }
|
||||
'^WPFFeature' { 'selectedFeatures' }
|
||||
'^WPFAppx' { 'selectedAppx' }
|
||||
}
|
||||
|
||||
# Get the actual Name from the selectedAppLabel inside the Checkbox
|
||||
$appKey = $checkboxName
|
||||
$group = if ($appKey.StartsWith("WPFInstall")) { "Install" }
|
||||
elseif ($appKey.StartsWith("WPFTweaks")) { "Tweaks" }
|
||||
elseif ($appKey.StartsWith("WPFToggle")) { "Toggle" }
|
||||
elseif ($appKey.StartsWith("WPFFeature")) { "Feature" }
|
||||
else { "na" }
|
||||
|
||||
switch ($group) {
|
||||
"Install" {
|
||||
if ($type -eq "Add") {
|
||||
if (!$sync.selectedApps.Contains($appKey)) {
|
||||
$sync.selectedApps.Add($appKey)
|
||||
# The List type needs to be specified again, because otherwise Sort-Object will convert the list to a string if there is only a single entry
|
||||
[System.Collections.Generic.List[string]]$sync.selectedApps = $sync.SelectedApps | Sort-Object
|
||||
}
|
||||
}
|
||||
else{
|
||||
$sync.selectedApps.Remove($appKey)
|
||||
}
|
||||
|
||||
$count = $sync.SelectedApps.Count
|
||||
$sync.WPFselectedAppsButton.Content = "Selected Apps: $count"
|
||||
# On every change, remove all entries inside the Popup Menu. This is done, so we can keep the alphabetical order even if elements are selected in a random way
|
||||
$sync.selectedAppsstackPanel.Children.Clear()
|
||||
$sync.selectedApps | Foreach-Object { Add-SelectedAppsMenuItem -name $($sync.configs.applicationsHashtable.$_.Content) -key $_ }
|
||||
}
|
||||
"Tweaks" {
|
||||
if ($type -eq "Add") {
|
||||
if (!$sync.selectedTweaks.Contains($appKey)) {
|
||||
$sync.selectedTweaks.Add($appKey)
|
||||
}
|
||||
}
|
||||
else{
|
||||
$sync.selectedTweaks.Remove($appKey)
|
||||
}
|
||||
}
|
||||
"Toggle" {
|
||||
if ($type -eq "Add") {
|
||||
if (!$sync.selectedToggles.Contains($appKey)) {
|
||||
$sync.selectedToggles.Add($appKey)
|
||||
}
|
||||
}
|
||||
else{
|
||||
$sync.selectedToggles.Remove($appKey)
|
||||
}
|
||||
}
|
||||
"Feature" {
|
||||
if ($type -eq "Add") {
|
||||
if (!$sync.selectedFeatures.Contains($appKey)) {
|
||||
$sync.selectedFeatures.Add($appKey)
|
||||
}
|
||||
}
|
||||
else{
|
||||
$sync.selectedFeatures.Remove($appKey)
|
||||
}
|
||||
}
|
||||
default {
|
||||
Write-Host "Unknown group for checkbox: $($appKey)"
|
||||
if ($type -eq "Add") {
|
||||
if (-not $sync.$listName.Contains($checkboxName)) {
|
||||
$sync.$listName.Add($checkboxName)
|
||||
}
|
||||
} else {
|
||||
$sync.$listName.Remove($checkboxName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ function Invoke-WPFTab {
|
||||
}
|
||||
}
|
||||
$sync.currentTab = $sync.$tabNav.Items[$tabNumber].Header
|
||||
Initialize-WinUtilTabContent -TabName $sync.currentTab
|
||||
|
||||
# Always reset the filter for the current tab
|
||||
if ($sync.currentTab -eq "Install") {
|
||||
@@ -37,10 +38,13 @@ function Invoke-WPFTab {
|
||||
} elseif ($sync.currentTab -eq "Tweaks") {
|
||||
# Reset Tweaks tab filter
|
||||
Find-TweaksByNameOrDescription -SearchString ""
|
||||
} elseif ($sync.currentTab -eq "AppX") {
|
||||
# Reset AppX tab filter
|
||||
Find-TweaksByNameOrDescription -SearchString ""
|
||||
}
|
||||
|
||||
# Show search bar in Install and Tweaks tabs
|
||||
if ($tabNumber -eq 0 -or $tabNumber -eq 1) {
|
||||
# Show search bar in Install, Tweaks, and AppX tabs
|
||||
if ($tabNumber -eq 0 -or $tabNumber -eq 1 -or $tabNumber -eq 5) {
|
||||
$sync.SearchBar.Visibility = "Visible"
|
||||
$searchIcon = ($sync.Form.FindName("SearchBar").Parent.Children | Where-Object { $_ -is [System.Windows.Controls.TextBlock] -and $_.Text -eq [char]0xE721 })[0]
|
||||
if ($searchIcon) {
|
||||
|
||||
@@ -11,26 +11,29 @@ function Invoke-WPFUnInstall {
|
||||
|
||||
if($sync.ProcessRunning) {
|
||||
$msg = "[Invoke-WPFUnInstall] Install process is currently running"
|
||||
[System.Windows.MessageBox]::Show($msg, "Winutil", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
|
||||
Show-WinUtilMessage -Message $msg -Title "Winutil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
if ($PackagesToUninstall.Count -eq 0) {
|
||||
$WarningMsg = "Please select the program(s) to uninstall"
|
||||
[System.Windows.MessageBox]::Show($WarningMsg, $AppTitle, [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
|
||||
Show-WinUtilMessage -Message $WarningMsg -Title $AppTitle -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
$ButtonType = [System.Windows.MessageBoxButton]::YesNo
|
||||
$ButtonType = "YesNo"
|
||||
$MessageboxTitle = "Are you sure?"
|
||||
$Messageboxbody = ("This will uninstall the following applications: `n $($PackagesToUninstall | Select-Object Name, Description| Out-String)")
|
||||
$MessageIcon = [System.Windows.MessageBoxImage]::Information
|
||||
$MessageIcon = "Information"
|
||||
|
||||
$confirm = [System.Windows.MessageBox]::Show($Messageboxbody, $MessageboxTitle, $ButtonType, $MessageIcon)
|
||||
$confirm = Show-WinUtilMessage -Message $Messageboxbody -Title $MessageboxTitle -Button $ButtonType -Icon $MessageIcon
|
||||
|
||||
if($confirm -eq "No") {return}
|
||||
|
||||
$ManagerPreference = $sync.preferences.packagemanager
|
||||
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall requested for $(@($PackagesToUninstall).Count) selected package(s) using preference: $ManagerPreference"
|
||||
$packageSummary = Get-WinUtilPackageLogSummary -Packages $PackagesToUninstall -Preference $ManagerPreference
|
||||
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall selected package(s): $($packageSummary -join '; ')"
|
||||
|
||||
Invoke-WPFRunspace -ParameterList @(("PackagesToUninstall", $PackagesToUninstall),("ManagerPreference", $ManagerPreference)) -ScriptBlock {
|
||||
param($PackagesToUninstall, $ManagerPreference)
|
||||
@@ -38,6 +41,7 @@ function Invoke-WPFUnInstall {
|
||||
$packagesSorted = Get-WinUtilSelectedPackages -PackageList $PackagesToUninstall -Preference $ManagerPreference
|
||||
$packagesWinget = $packagesSorted[[PackageManagers]::Winget]
|
||||
$packagesChoco = $packagesSorted[[PackageManagers]::Choco]
|
||||
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
|
||||
|
||||
try {
|
||||
$sync.ProcessRunning = $true
|
||||
@@ -58,14 +62,18 @@ function Invoke-WPFUnInstall {
|
||||
Write-Host "==========================================="
|
||||
Write-Host "-- Uninstalls have finished ---"
|
||||
Write-Host "==========================================="
|
||||
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall workflow completed."
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
} catch {
|
||||
Hide-WPFInstallAppBusy
|
||||
Write-Host "==========================================="
|
||||
Write-Host "Error: $_"
|
||||
Write-Host "==========================================="
|
||||
Write-WinUtilLog -Level "ERROR" -Component "Uninstall" -Message "Uninstall workflow failed: $($_.Exception.Message)"
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
} finally {
|
||||
$sync.ProcessRunning = $False
|
||||
}
|
||||
$sync.ProcessRunning = $False
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ function Invoke-WPFUpdatesdefault {
|
||||
|
||||
#>
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Write-WinUtilLog -Component "Updates" -Message "Resetting Windows Update settings to default."
|
||||
|
||||
Write-Host "Removing Windows Update policy settings..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Removing Windows Update policy registry paths."
|
||||
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization" -Recurse -Force
|
||||
@@ -17,24 +19,31 @@ function Invoke-WPFUpdatesdefault {
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force
|
||||
|
||||
Write-Host "Showing Windows Updates in settings..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Showing Windows Update settings page."
|
||||
Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility
|
||||
|
||||
Write-Host "Reenabling Windows Update Services..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update service startup types."
|
||||
|
||||
Write-Host "Restored BITS to Manual."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring BITS service to Manual."
|
||||
Set-Service -Name BITS -StartupType Manual
|
||||
|
||||
Write-Host "Restored wuauserv to Manual."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring wuauserv service to Manual."
|
||||
Set-Service -Name wuauserv -StartupType Manual
|
||||
|
||||
Write-Host "Restored UsoSvc to Automatic."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Starting UsoSvc service and restoring startup type to Automatic."
|
||||
Start-Service -Name UsoSvc
|
||||
Set-Service -Name UsoSvc -StartupType Automatic
|
||||
|
||||
Write-Host "Restored WaaSMedicSvc to Manual."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring WaaSMedicSvc service to Manual."
|
||||
Set-Service -Name WaaSMedicSvc -StartupType Manual
|
||||
|
||||
Write-Host "Enabling update related scheduled tasks..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Enabling update related scheduled tasks."
|
||||
|
||||
$Tasks =
|
||||
'\Microsoft\Windows\InstallService\*',
|
||||
@@ -49,6 +58,7 @@ function Invoke-WPFUpdatesdefault {
|
||||
}
|
||||
|
||||
Write-Host "Windows Local Policies Reset to Default."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Resetting local security policy to defaults with secedit."
|
||||
secedit /configure /cfg "$Env:SystemRoot\inf\defltbase.inf" /db defltbase.sdb
|
||||
|
||||
Write-Host "===================================================" -ForegroundColor Green
|
||||
@@ -56,4 +66,5 @@ function Invoke-WPFUpdatesdefault {
|
||||
Write-Host "===================================================" -ForegroundColor Green
|
||||
|
||||
Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow
|
||||
Write-WinUtilLog -Component "Updates" -Message "Windows Update default workflow completed. Restart required."
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ function Invoke-WPFUpdatesdisable {
|
||||
|
||||
#>
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update settings."
|
||||
|
||||
Write-Host "Configuring registry settings..." -ForegroundColor Yellow
|
||||
Write-WinUtilLog -Component "Updates" -Message "Configuring Windows Update registry policy values for disable mode."
|
||||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Force
|
||||
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -Type DWord -Value 1
|
||||
@@ -20,22 +22,28 @@ function Invoke-WPFUpdatesdisable {
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -Type DWord -Value 0
|
||||
|
||||
Write-Host "Hiding Windows Updates from settings..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Hiding Windows Update settings page."
|
||||
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility -Value hide:windowsupdate
|
||||
|
||||
Write-Host "Disabled BITS Service."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling BITS service."
|
||||
Set-Service -Name BITS -StartupType Disabled
|
||||
|
||||
Write-Host "Disabled wuauserv Service."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling wuauserv service."
|
||||
Set-Service -Name wuauserv -StartupType Disabled
|
||||
|
||||
Write-Host "Disabled UsoSvc Service."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Stopping and disabling UsoSvc service."
|
||||
Stop-Service -Name UsoSvc -Force
|
||||
Set-Service -Name UsoSvc -StartupType Disabled
|
||||
|
||||
Remove-Item "C:\Windows\SoftwareDistribution\*" -Recurse -Force
|
||||
Write-Host "Cleared SoftwareDistribution folder."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Cleared SoftwareDistribution folder."
|
||||
|
||||
Write-Host "Disabling update related scheduled tasks..." -ForegroundColor Yellow
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling update related scheduled tasks."
|
||||
|
||||
$Tasks =
|
||||
'\Microsoft\Windows\InstallService\*',
|
||||
@@ -54,4 +62,5 @@ function Invoke-WPFUpdatesdisable {
|
||||
Write-Host "=================================" -ForegroundColor Green
|
||||
|
||||
Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow
|
||||
Write-WinUtilLog -Component "Updates" -Message "Windows Update disable workflow completed. Restart required."
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ function Invoke-WPFUpdatessecurity {
|
||||
#>
|
||||
|
||||
Write-Host "Disabling driver offering through Windows Update..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Applying recommended Windows Update settings."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling driver offering through Windows Update."
|
||||
|
||||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Force
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Name "PreventDeviceMetadataFromNetwork" -Type DWord -Value 1
|
||||
@@ -28,6 +30,7 @@ function Invoke-WPFUpdatessecurity {
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1
|
||||
|
||||
Write-Host "Setting cumulative updates back by 1 year and security updates by 4 days..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Deferring feature updates by 365 days and quality updates by 4 days."
|
||||
|
||||
New-Item -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Force
|
||||
|
||||
@@ -36,6 +39,7 @@ function Invoke-WPFUpdatessecurity {
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4
|
||||
|
||||
Write-Host "Disabling Windows Update automatic restart..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update automatic restart while users are logged in."
|
||||
|
||||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Force
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoRebootWithLoggedOnUsers" -Type DWord -Value 1
|
||||
@@ -44,4 +48,5 @@ function Invoke-WPFUpdatessecurity {
|
||||
Write-Host "================================="
|
||||
Write-Host "-- Updates Set to Recommended ---"
|
||||
Write-Host "================================="
|
||||
Write-WinUtilLog -Component "Updates" -Message "Recommended Windows Update settings workflow completed."
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user