mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-10 01:51:18 +10:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70f1c50bdd | ||
|
|
2e5df82979 | ||
|
|
29595f90e2 | ||
|
|
9ed06e793a | ||
|
|
51cb3811dd | ||
|
|
975616b58f | ||
|
|
11783c75a8 | ||
|
|
13a18eeeb2 | ||
|
|
822003d87f | ||
|
|
c8c3c7869f |
@@ -77,7 +77,7 @@ See https://github.com/ChrisTitusTech/winutil/blob/main/.github/CONTRIBUTING.md
|
||||
|
||||
These are the sponsors that help keep this project alive with monthly contributions.
|
||||
|
||||
<!-- sponsors --><a href="https://github.com/ysaito8015"><img src="https://github.com/ysaito8015.png" width="60px" alt="User avatar: Yusuke Saito" /></a><a href="https://github.com/dwelfusius"><img src="https://github.com/dwelfusius.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/mews-se"><img src="https://github.com/mews-se.png" width="60px" alt="User avatar: Martin Stockzell" /></a><a href="https://github.com/jdiegmueller"><img src="https://github.com/jdiegmueller.png" width="60px" alt="User avatar: Jason A. Diegmueller" /></a><a href="https://github.com/robertsandrock"><img src="https://github.com/robertsandrock.png" width="60px" alt="User avatar: RMS" /></a><a href="https://github.com/paulsheets"><img src="https://github.com/paulsheets.png" width="60px" alt="User avatar: Paul" /></a><a href="https://github.com/djones369"><img src="https://github.com/djones369.png" width="60px" alt="User avatar: Dave J (WhamGeek)" /></a><a href="https://github.com/anthonymendez"><img src="https://github.com/anthonymendez.png" width="60px" alt="User avatar: Anthony Mendez" /></a><a href="https://github.com/FatBastard0"><img src="https://github.com/FatBastard0.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/DursleyGuy"><img src="https://github.com/DursleyGuy.png" width="60px" alt="User avatar: DursleyGuy" /></a><a href="https://github.com/DwayneTheRockLobster1"><img src="https://github.com/DwayneTheRockLobster1.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/KieraKujisawa"><img src="https://github.com/KieraKujisawa.png" width="60px" alt="User avatar: Kiera Meredith" /></a><a href="https://github.com/andrewpayne68"><img src="https://github.com/andrewpayne68.png" width="60px" alt="User avatar: Andrew P" /></a><a href="https://github.com/johanwildeboer"><img src="https://github.com/johanwildeboer.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/lukas346"><img src="https://github.com/lukas346.png" width="60px" alt="User avatar: Wook" /></a><a href="https://github.com/tsv31"><img src="https://github.com/tsv31.png" width="60px" alt="User avatar: Sorin" /></a><!-- sponsors -->
|
||||
<!-- sponsors --><a href="https://github.com/ysaito8015"><img src="https://github.com/ysaito8015.png" width="60px" alt="User avatar: Yusuke Saito" /></a><a href="https://github.com/dwelfusius"><img src="https://github.com/dwelfusius.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/mews-se"><img src="https://github.com/mews-se.png" width="60px" alt="User avatar: Martin Stockzell" /></a><a href="https://github.com/jdiegmueller"><img src="https://github.com/jdiegmueller.png" width="60px" alt="User avatar: Jason A. Diegmueller" /></a><a href="https://github.com/robertsandrock"><img src="https://github.com/robertsandrock.png" width="60px" alt="User avatar: RMS" /></a><a href="https://github.com/paulsheets"><img src="https://github.com/paulsheets.png" width="60px" alt="User avatar: Paul" /></a><a href="https://github.com/djones369"><img src="https://github.com/djones369.png" width="60px" alt="User avatar: Dave J (WhamGeek)" /></a><a href="https://github.com/anthonymendez"><img src="https://github.com/anthonymendez.png" width="60px" alt="User avatar: Anthony Mendez" /></a><a href="https://github.com/FatBastard0"><img src="https://github.com/FatBastard0.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/DursleyGuy"><img src="https://github.com/DursleyGuy.png" width="60px" alt="User avatar: DursleyGuy" /></a><a href="https://github.com/DwayneTheRockLobster1"><img src="https://github.com/DwayneTheRockLobster1.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/KieraKujisawa"><img src="https://github.com/KieraKujisawa.png" width="60px" alt="User avatar: Kiera Meredith" /></a><a href="https://github.com/andrewpayne68"><img src="https://github.com/andrewpayne68.png" width="60px" alt="User avatar: Andrew P" /></a><a href="https://github.com/johanwildeboer"><img src="https://github.com/johanwildeboer.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/lukas346"><img src="https://github.com/lukas346.png" width="60px" alt="User avatar: Wook" /></a><a href="https://github.com/tsv31"><img src="https://github.com/tsv31.png" width="60px" alt="User avatar: Sorin" /></a><a href="https://github.com/seanh1995"><img src="https://github.com/seanh1995.png" width="60px" alt="User avatar: Sean (ANGRYxScotsman)" /></a><!-- sponsors -->
|
||||
|
||||
---
|
||||
|
||||
|
||||
+60
-30
@@ -4,203 +4,232 @@
|
||||
"Content": "Feedback Hub",
|
||||
"Description": "Allows users to submit bug reports, feature suggestions, and diagnostic data directly to Microsoft.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsFeedbackHub"
|
||||
"PackageId": "Microsoft.WindowsFeedbackHub",
|
||||
"StoreId": "9NBLGGH4R32N"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.GetHelp",
|
||||
"StoreId": "9PKDZBMV1H3T"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.OutlookForWindows",
|
||||
"StoreId": "9NRX63209R7B"
|
||||
},
|
||||
"WPFAppxMSTeams": {
|
||||
"Category": "Microsoft Apps",
|
||||
"Content": "Microsoft Teams",
|
||||
"Description": "Facilitates instant messaging, video conferencing, file sharing, and workspace collaboration.",
|
||||
"Panel": "0",
|
||||
"PackageId": "MSTeams"
|
||||
"PackageId": "MSTeams",
|
||||
"StoreId": "XP8BT8DW290MPQ"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Clipchamp.Clipchamp",
|
||||
"StoreId": "9P1J8S7CCWWT"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.MicrosoftOfficeHub",
|
||||
"StoreId": "9WZDNCRD29V9"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.ZuneMusic",
|
||||
"StoreId": "9WZDNCRFJ3PT"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.BingSearch",
|
||||
"StoreId": "9NZBF4GT040C"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "MicrosoftCorporationII.QuickAssist",
|
||||
"StoreId": "9P7BP5VNWKX5"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.Windows.DevHome",
|
||||
"StoreId": "9N8MHTPHNGVV"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "MicrosoftWindows.CrossDevice",
|
||||
"StoreId": "9NTXGKQ8P7N0"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.Todos",
|
||||
"StoreId": "9NBLGGH5R558"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.PowerAutomateDesktop",
|
||||
"StoreId": "9NFTCH6J7FHV"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.YourPhone",
|
||||
"StoreId": "9NMPJ99VJBWV"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.MicrosoftStickyNotes",
|
||||
"StoreId": "9NBLGGH4QGHW"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.WindowsSoundRecorder",
|
||||
"StoreId": "9WZDNCRFHWKN"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.WindowsAlarms",
|
||||
"StoreId": "9WZDNCRFJ3PR"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.Paint",
|
||||
"StoreId": "9PCFS5B6T72H"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.WindowsNotepad",
|
||||
"StoreId": "9MSMLRH6LZF3"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.ScreenSketch",
|
||||
"StoreId": "9MZ95KL8MR0L"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.Copilot",
|
||||
"StoreId": "9NHT9RB2F4HD"
|
||||
},
|
||||
"WPFAppxMicrosoft_WindowsCalculator": {
|
||||
"Category": "Utilities & Productivity",
|
||||
"Content": "Calculator",
|
||||
"Description": "Performs standard arithmetic, scientific operations, programming calculations, and unit conversions.",
|
||||
"Panel": "0",
|
||||
"PackageId": "Microsoft.WindowsCalculator"
|
||||
"PackageId": "Microsoft.WindowsCalculator",
|
||||
"StoreId": "9WZDNCRFHVN5"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.WindowsCamera",
|
||||
"StoreId": "9WZDNCRFJBBG"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.Windows.Photos",
|
||||
"StoreId": "9WZDNCRFJBH4"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.BingNews",
|
||||
"StoreId": "9WZDNCRFHVFW"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.BingWeather",
|
||||
"StoreId": "9WZDNCRFJ3Q2"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.GamingApp",
|
||||
"StoreId": "9MV0B5HZVK9Z"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.XboxGamingOverlay",
|
||||
"StoreId": "9NZKPSTSNW4P"
|
||||
},
|
||||
"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"
|
||||
"PackageId": "Microsoft.XboxIdentityProvider",
|
||||
"StoreId": "9WZDNCRD1HKW"
|
||||
},
|
||||
"WPFAppxMicrosoft_XboxSpeechToTextOverlay": {
|
||||
"Category": "Xbox & Gaming",
|
||||
@@ -221,7 +250,8 @@
|
||||
"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"
|
||||
"PackageId": "Microsoft.StartExperiencesApp",
|
||||
"StoreId": "9PC1H9VN18CM"
|
||||
},
|
||||
"WPFAppxMicrosoft_MicrosoftSolitaireCollection": {
|
||||
"Category": "Xbox & Gaming",
|
||||
|
||||
+2
-6
@@ -25,7 +25,7 @@
|
||||
"IconFontSize": "14",
|
||||
"IconButtonSize": "35",
|
||||
"SettingsIconFontSize": "18",
|
||||
"CloseIconFontSize": "18",
|
||||
"CloseIconFontSize": "12",
|
||||
"GroupBorderBackgroundColor": "#232629",
|
||||
"ButtonFontSize": "12",
|
||||
"ButtonFontFamily": "Arial",
|
||||
@@ -46,7 +46,6 @@
|
||||
"AppInstallUnselectedColor": "#F7F7F7",
|
||||
"AppInstallHighlightedColor": "#CFCFCF",
|
||||
"AppInstallSelectedColor": "#C2C2C2",
|
||||
"AppInstallOverlayBackgroundColor": "#6A6D72",
|
||||
"ComboBoxForegroundColor": "#232629",
|
||||
"ComboBoxBackgroundColor": "#F7F7F7",
|
||||
"LabelboxForegroundColor": "#232629",
|
||||
@@ -60,7 +59,6 @@
|
||||
"ScrollBarDraggingColor": "#6A6D72",
|
||||
"ProgressBarForegroundColor": "#2E77FF",
|
||||
"ProgressBarBackgroundColor": "Transparent",
|
||||
"ProgressBarTextColor": "#232629",
|
||||
"ButtonInstallBackgroundColor": "#F7F7F7",
|
||||
"ButtonTweaksBackgroundColor": "#F7F7F7",
|
||||
"ButtonConfigBackgroundColor": "#F7F7F7",
|
||||
@@ -88,7 +86,6 @@
|
||||
"AppInstallUnselectedColor": "#232629",
|
||||
"AppInstallHighlightedColor": "#3C3C3C",
|
||||
"AppInstallSelectedColor": "#4C4C4C",
|
||||
"AppInstallOverlayBackgroundColor": "#2E3135",
|
||||
"ComboBoxForegroundColor": "#F7F7F7",
|
||||
"ComboBoxBackgroundColor": "#1E3747",
|
||||
"LabelboxForegroundColor": "#5BDCFF",
|
||||
@@ -100,9 +97,8 @@
|
||||
"ScrollBarBackgroundColor": "#2E3135",
|
||||
"ScrollBarHoverColor": "#3B4252",
|
||||
"ScrollBarDraggingColor": "#5E81AC",
|
||||
"ProgressBarForegroundColor": "#222222",
|
||||
"ProgressBarForegroundColor": "#6EFF72",
|
||||
"ProgressBarBackgroundColor": "Transparent",
|
||||
"ProgressBarTextColor": "#232629",
|
||||
"ButtonInstallBackgroundColor": "#222222",
|
||||
"ButtonTweaksBackgroundColor": "#333333",
|
||||
"ButtonConfigBackgroundColor": "#444444",
|
||||
|
||||
@@ -5,15 +5,53 @@ description: ""
|
||||
|
||||
```powershell {filename="functions/public/Invoke-WPFOOSU.ps1",linenos=inline,linenostart=1}
|
||||
function Invoke-WPFOOSU {
|
||||
try {
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
if ($sync.ProcessRunning) {
|
||||
Show-WinUtilMessage -Message "Another process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
Invoke-WebRequest -Uri https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe -OutFile "$winutildir\ooshutup10.exe"
|
||||
Start-Process -FilePath "$winutildir\ooshutup10.exe"
|
||||
$downloadPath = Join-Path $sync.winutildir "ooshutup10.exe"
|
||||
$sync.ProcessRunning = $true
|
||||
|
||||
$ProgressPreference = 'Continue'
|
||||
} catch {
|
||||
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
|
||||
Invoke-WPFRunspace -ParameterList @(,("downloadPath", $downloadPath)) -ScriptBlock {
|
||||
param($downloadPath)
|
||||
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
|
||||
try {
|
||||
Write-WinUtilLog -Component "OOSU" -Message "Downloading O&O ShutUp10++."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ (0%)" -Percent 0
|
||||
}
|
||||
|
||||
Save-WinUtilFile -Uri "https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe" -DestinationPath $downloadPath -ProgressCallback {
|
||||
param($percent)
|
||||
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ ($percent%)" -Percent $percent
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Launching O&O ShutUp10++" -Percent 100
|
||||
}
|
||||
Start-Process -FilePath $downloadPath
|
||||
|
||||
Write-WinUtilLog -Component "OOSU" -Message "O&O ShutUp10++ launched."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ launched" -Percent 100
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-WinUtilLog -Level "ERROR" -Component "OOSU" -Message "O&O ShutUp10++ download failed: $($_.Exception.Message)"
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ download failed" -Percent 100
|
||||
}
|
||||
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
|
||||
}
|
||||
finally {
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -138,7 +138,7 @@ For a better Windows experience with minimal risk:
|
||||
|
||||
1. Check multiple application boxes
|
||||
2. All checked apps will install in sequence
|
||||
3. Progress is shown in the bottom panel
|
||||
3. Install and uninstall progress is shown in the window-level bottom panel, including the current package or package-manager batch and overall completion
|
||||
|
||||
### Applying Tweaks
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ Use the quick-selection buttons at the top of the Tweaks tab to speed up setup:
|
||||
* **Select Tweaks to Remove**: Choose the tweaks you want to disable or remove.
|
||||
* **Undo Tweaks**: Click **Undo Selected Tweaks** at the bottom of the screen to apply the changes.
|
||||
|
||||
### AppX Packages
|
||||
|
||||
Open **AppX Removal** from the Tweaks tab to manage the listed Windows apps. Select one or more packages, then choose **Install Selected** or **Remove Selected**.
|
||||
|
||||
When installing a selected package, WinUtil first registers an existing local `AppxManifest.xml`. If no usable local manifest remains, WinUtil installs the package from the Microsoft Store through WinGet when a Store product ID is available.
|
||||
|
||||
During AppX installation or removal, the window-level progress bar shows the current package, completed package count, and overall progress. The Windows taskbar also reflects progress and the final success or failure state.
|
||||
|
||||
### Essential Tweaks
|
||||
Essential Tweaks are the safest starting point for most systems. They focus on lower-risk changes that improve usability, reduce noise, and avoid the more invasive changes found in advanced options.
|
||||
|
||||
|
||||
@@ -9,28 +9,30 @@ WinUtil provides three update modes so you can choose how aggressively Windows U
|
||||
|
||||
Changing modes adjusts system-wide Windows Update behavior. After switching modes, give Windows a moment to apply the policy and plan for a restart if the new state does not appear immediately.
|
||||
|
||||
{{< image src="images/updates-tab-new" alt="Updates tab in WinUtil" >}}
|
||||
- **Recommended**: Prioritizes stability while still receiving security updates
|
||||
- **Windows Default**: Restores standard Windows Update behavior
|
||||
- **Disable Updates**: Blocks Windows Update and should only be used with extreme caution
|
||||
|
||||
- **Default (Out of the Box) Settings**: Restores standard Windows Update behavior
|
||||
- **Security (Recommended) Settings**: Prioritizes stability while still receiving security updates
|
||||
- **Disable ALL Updates**: Turns off Windows Update entirely and should only be used with extreme caution
|
||||
### Windows Default
|
||||
|
||||
### Default (Out of Box) Settings
|
||||
|
||||
- **What it does**: Restores the default Windows Update configuration.
|
||||
- **What it does**: Removes Windows Update policies managed by WinUtil, restores update service startup settings, and re-enables update scheduled tasks.
|
||||
- **Best for**: Systems where you want Windows to manage updates normally.
|
||||
- **Notes**: This removes custom update settings previously applied by WinUtil. If update errors continue, use the reset option in the **Config** tab to restore Microsoft Update services to their default state.
|
||||
- **Notes**: Only values managed by WinUtil are removed; other Windows Update policies are left in place. If update errors continue, use the reset option in the **Config** tab to repair Microsoft Update components.
|
||||
|
||||
### Security (Recommended) Settings
|
||||
### Recommended
|
||||
|
||||
- **What it does**: Applies a more conservative update strategy designed for most users.
|
||||
- **Feature updates**: Delayed by **365 days** to reduce the chance of disruption from major Windows changes.
|
||||
- **Security updates**: Delayed by **4 days** to allow time for early issues to surface while still keeping the system protected.
|
||||
- **Quality updates**: Delayed by **4 days** to allow time for early issues to surface while still keeping the system protected.
|
||||
- **Drivers**: Excluded from Windows quality updates.
|
||||
- **Restarts**: Scheduled updates do not automatically restart Windows while a user is signed in. A restart explicitly scheduled by a user still takes precedence.
|
||||
- **Availability**: Update deferral policies apply to Windows Pro, Enterprise, and Education editions.
|
||||
- **Why use it**: This mode offers the best balance between security and stability, which is why it is the recommended option for most PCs.
|
||||
|
||||
### Disable ALL Updates (NOT RECOMMENDED!)
|
||||
### Disable Updates (NOT RECOMMENDED!)
|
||||
|
||||
- **What it does**: Disables all Windows updates.
|
||||
- **What it does**: Disables automatic update policy, stops and disables update services, disables update scheduled tasks, and clears downloaded update files.
|
||||
- **Best for**: Highly controlled or special-purpose systems where updates must remain off temporarily.
|
||||
- **Warning**: This leaves the system without security patches and significantly increases security risk.
|
||||
- **Notes**: Windows servicing can restore update components in some circumstances. Use **Restore Defaults** when you are ready to receive updates again.
|
||||
- **Recommendation**: Avoid this mode unless you fully understand the tradeoffs and have a specific reason to use it.
|
||||
|
||||
@@ -10,6 +10,9 @@ function Find-AppsByNameOrDescription {
|
||||
.PARAMETER SearchString
|
||||
The string to be searched for. Wildcards are treated as literal characters.
|
||||
|
||||
.PARAMETER Category
|
||||
When provided, only applications in this exact category are shown.
|
||||
|
||||
.NOTES
|
||||
- Uses module-scope $sync (no parameter needed; inherits from caller's scope)
|
||||
- Performs literal matching (no wildcard expansion)
|
||||
@@ -18,7 +21,10 @@ function Find-AppsByNameOrDescription {
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$SearchString = ""
|
||||
[string]$SearchString = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Category = ""
|
||||
)
|
||||
|
||||
# Validate that $sync exists and has required structure
|
||||
@@ -39,7 +45,7 @@ function Find-AppsByNameOrDescription {
|
||||
|
||||
try {
|
||||
# Reset the visibility if the search string is empty or the search is cleared
|
||||
if ([string]::IsNullOrWhiteSpace($SearchString)) {
|
||||
if ([string]::IsNullOrWhiteSpace($SearchString) -and [string]::IsNullOrWhiteSpace($Category)) {
|
||||
$sync.ItemsControl.Items | ForEach-Object {
|
||||
# Each item is a StackPanel container
|
||||
$_.Visibility = [Windows.Visibility]::Visible
|
||||
@@ -94,10 +100,11 @@ function Find-AppsByNameOrDescription {
|
||||
|
||||
# Check if app matches search criteria
|
||||
if ($null -ne $appEntry) {
|
||||
$contentMatch = $appEntry.Content -like "*$escapedSearchString*"
|
||||
$descriptionMatch = $appEntry.Description -like "*$escapedSearchString*"
|
||||
$categoryMatch = -not [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Category -eq $Category
|
||||
$contentMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Content -like "*$escapedSearchString*"
|
||||
$descriptionMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Description -like "*$escapedSearchString*"
|
||||
|
||||
if ($contentMatch -or $descriptionMatch) {
|
||||
if ($categoryMatch -or $contentMatch -or $descriptionMatch) {
|
||||
# Show the App and mark that this category has a match
|
||||
$appControl.Visibility = [Windows.Visibility]::Visible
|
||||
$categoryHasMatch = $true
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
function Get-WinUtilInstalledAPPX {
|
||||
<#
|
||||
|
||||
.SYNOPSIS
|
||||
Gets the names of AppX packages installed for all users
|
||||
|
||||
#>
|
||||
|
||||
# AppX module auto-loading can leave PowerShell 7 dependent on a temporary Windows PowerShell
|
||||
# compatibility proxy. Run the query in Windows PowerShell 5.1 so it remains available after
|
||||
# those temporary proxy files are removed.
|
||||
$ps5Command = {
|
||||
Get-AppxPackage -AllUsers -ErrorAction Stop | Select-Object -ExpandProperty Name
|
||||
}
|
||||
|
||||
$packageOutput = powershell.exe -NoProfile -NonInteractive -Command $ps5Command 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$failureDetails = ($packageOutput | Out-String).Trim()
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "Failed to get installed AppX packages: $failureDetails"
|
||||
return @()
|
||||
}
|
||||
|
||||
return @($packageOutput)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
function Hide-WPFInstallAppBusy {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Hides the busy overlay in the install app area of the WPF form.
|
||||
This is used to indicate that an install or uninstall has finished.
|
||||
#>
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
$sync.InstallAppAreaOverlay.Visibility = [Windows.Visibility]::Collapsed
|
||||
$sync.InstallAppAreaBorder.IsEnabled = $true
|
||||
$sync.InstallAppAreaScrollViewer.Effect.Radius = 0
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@
|
||||
This is used as the parent object for all category and app entries on the install tab
|
||||
Used to as part of the Install Tab UI generation
|
||||
|
||||
Also creates an overlay with a progress bar and text to indicate that an install or uninstall is in progress
|
||||
|
||||
.PARAMETER TargetElement
|
||||
The element to which the AppArea should be added
|
||||
|
||||
@@ -19,22 +17,14 @@
|
||||
$Border = New-Object Windows.Controls.Border
|
||||
$Border.VerticalAlignment = "Stretch"
|
||||
$Border.SetResourceReference([Windows.Controls.Control]::StyleProperty, "BorderStyle")
|
||||
$sync.InstallAppAreaBorder = $Border
|
||||
|
||||
# Add a ScrollViewer, because the ItemsControl does not support scrolling by itself
|
||||
$scrollViewer = New-Object Windows.Controls.ScrollViewer
|
||||
$scrollViewer.VerticalScrollBarVisibility = 'Auto'
|
||||
$scrollViewer.HorizontalAlignment = 'Stretch'
|
||||
$scrollViewer.VerticalAlignment = 'Stretch'
|
||||
$scrollViewer.CanContentScroll = $true
|
||||
$sync.InstallAppAreaScrollViewer = $scrollViewer
|
||||
$Border.Child = $scrollViewer
|
||||
|
||||
# Initialize the Blur Effect for the ScrollViewer, which will be used to indicate that an install/uninstall is in progress
|
||||
$blurEffect = New-Object Windows.Media.Effects.BlurEffect
|
||||
$blurEffect.Radius = 0
|
||||
$scrollViewer.Effect = $blurEffect
|
||||
|
||||
## Create the ItemsControl, which will be the parent of all the app entries
|
||||
$itemsControl = New-Object Windows.Controls.ItemsControl
|
||||
$itemsControl.HorizontalAlignment = 'Stretch'
|
||||
@@ -52,61 +42,5 @@
|
||||
# Add the Border containing the App Area to the target Grid
|
||||
$targetGrid.Children.Add($Border) | Out-Null
|
||||
|
||||
$overlay = New-Object Windows.Controls.Border
|
||||
$overlay.CornerRadius = New-Object Windows.CornerRadius(10)
|
||||
$overlay.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallOverlayBackgroundColor")
|
||||
$overlay.Visibility = [Windows.Visibility]::Collapsed
|
||||
|
||||
# Also add the overlay to the target Grid on top of the App Area
|
||||
$targetGrid.Children.Add($overlay) | Out-Null
|
||||
$sync.InstallAppAreaOverlay = $overlay
|
||||
|
||||
$overlayText = New-Object Windows.Controls.TextBlock
|
||||
$overlayText.Text = "Installing apps..."
|
||||
$overlayText.HorizontalAlignment = 'Center'
|
||||
$overlayText.VerticalAlignment = 'Center'
|
||||
$overlayText.SetResourceReference([Windows.Controls.TextBlock]::ForegroundProperty, "MainForegroundColor")
|
||||
$overlayText.Background = "Transparent"
|
||||
$overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontSizeProperty, "HeaderFontSize")
|
||||
$overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontFamilyProperty, "MainFontFamily")
|
||||
$overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontWeightProperty, "MainFontWeight")
|
||||
$overlayText.SetResourceReference([Windows.Controls.TextBlock]::MarginProperty, "MainMargin")
|
||||
$sync.InstallAppAreaOverlayText = $overlayText
|
||||
|
||||
$progressbar = New-Object Windows.Controls.ProgressBar
|
||||
$progressbar.Name = "ProgressBar"
|
||||
$progressbar.Width = 250
|
||||
$progressbar.Height = 50
|
||||
$sync.ProgressBar = $progressbar
|
||||
|
||||
# Add a TextBlock overlay for the progress bar text
|
||||
$progressBarTextBlock = New-Object Windows.Controls.TextBlock
|
||||
$progressBarTextBlock.Name = "progressBarTextBlock"
|
||||
$progressBarTextBlock.FontWeight = [Windows.FontWeights]::Bold
|
||||
$progressBarTextBlock.FontSize = 16
|
||||
$progressBarTextBlock.Width = $progressbar.Width
|
||||
$progressBarTextBlock.Height = $progressbar.Height
|
||||
$progressBarTextBlock.SetResourceReference([Windows.Controls.TextBlock]::ForegroundProperty, "ProgressBarTextColor")
|
||||
$progressBarTextBlock.TextTrimming = "CharacterEllipsis"
|
||||
$progressBarTextBlock.Background = "Transparent"
|
||||
$sync.progressBarTextBlock = $progressBarTextBlock
|
||||
|
||||
# Create a Grid to overlay the text on the progress bar
|
||||
$progressGrid = New-Object Windows.Controls.Grid
|
||||
$progressGrid.Width = $progressbar.Width
|
||||
$progressGrid.Height = $progressbar.Height
|
||||
$progressGrid.Margin = "0,10,0,10"
|
||||
$progressGrid.Children.Add($progressbar) | Out-Null
|
||||
$progressGrid.Children.Add($progressBarTextBlock) | Out-Null
|
||||
|
||||
$overlayStackPanel = New-Object Windows.Controls.StackPanel
|
||||
$overlayStackPanel.Orientation = "Vertical"
|
||||
$overlayStackPanel.HorizontalAlignment = 'Center'
|
||||
$overlayStackPanel.VerticalAlignment = 'Center'
|
||||
$overlayStackPanel.Children.Add($overlayText) | Out-Null
|
||||
$overlayStackPanel.Children.Add($progressGrid) | Out-Null
|
||||
|
||||
$overlay.Child = $overlayStackPanel
|
||||
|
||||
return $itemsControl
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ function Initialize-WinUtilTabContent {
|
||||
"AppX" {
|
||||
Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2
|
||||
}
|
||||
"Win11 Creator" {
|
||||
"Win11ISO" {
|
||||
if ($sync.Form -and $sync.Form.Dispatcher) {
|
||||
$sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
function Install-WinUtilAPPX {
|
||||
<#
|
||||
|
||||
.SYNOPSIS
|
||||
Registers a local AppX package or installs it from the Microsoft Store
|
||||
|
||||
.PARAMETER Name
|
||||
The AppX package name to install
|
||||
|
||||
.PARAMETER StoreId
|
||||
The optional Microsoft Store product ID used when no local manifest is available
|
||||
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
|
||||
[string]$StoreId
|
||||
)
|
||||
|
||||
Write-WinUtilLog -Component "AppX" -Message "Installing AppX package: $Name"
|
||||
|
||||
# AppX and DISM cmdlets are more reliable in Windows PowerShell 5.1. Query both installed and
|
||||
# provisioned package metadata because either can expose a local manifest that can be registered.
|
||||
$ps5Command = {
|
||||
$packageName = $args[0]
|
||||
$manifestPaths = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
Get-AppxPackage -AllUsers -Name $packageName -ErrorAction SilentlyContinue |
|
||||
Sort-Object -Property Version -Descending |
|
||||
ForEach-Object {
|
||||
if (-not [string]::IsNullOrWhiteSpace($_.InstallLocation)) {
|
||||
$manifestPaths.Add((Join-Path $_.InstallLocation "AppxManifest.xml"))
|
||||
}
|
||||
}
|
||||
|
||||
Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
|
||||
Where-Object DisplayName -EQ $packageName |
|
||||
ForEach-Object {
|
||||
if (-not [string]::IsNullOrWhiteSpace($_.InstallLocation)) {
|
||||
$manifestPaths.Add((Join-Path $_.InstallLocation "AppxManifest.xml"))
|
||||
}
|
||||
}
|
||||
|
||||
$manifestPath = $manifestPaths |
|
||||
Select-Object -Unique |
|
||||
Where-Object { Test-Path -LiteralPath $_ } |
|
||||
Select-Object -First 1
|
||||
|
||||
if ($null -ne $manifestPath) {
|
||||
Add-AppxPackage -Register $manifestPath -DisableDevelopmentMode -ErrorAction Stop
|
||||
Write-Output $manifestPath
|
||||
}
|
||||
}
|
||||
|
||||
$manifestOutput = powershell.exe -NoProfile -NonInteractive -Command $ps5Command -args $Name 2>&1
|
||||
if ($LASTEXITCODE -eq 0 -and $null -ne $manifestOutput) {
|
||||
$manifestPath = ($manifestOutput | Select-Object -Last 1).ToString().Trim()
|
||||
if (-not [string]::IsNullOrWhiteSpace($manifestPath)) {
|
||||
Write-WinUtilLog -Component "AppX" -Message "Registered local AppX manifest for $Name`: $manifestPath"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
$failureDetails = ($manifestOutput | Out-String).Trim()
|
||||
Write-WinUtilLog -Level "WARN" -Component "AppX" -Message "Local AppX registration failed for $Name`: $failureDetails"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($StoreId)) {
|
||||
$errorMessage = "Unable to install $Name because no local manifest or Microsoft Store ID is available."
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message $errorMessage
|
||||
throw $errorMessage
|
||||
}
|
||||
|
||||
Write-WinUtilLog -Component "AppX" -Message "No usable local manifest found for $Name. Installing Microsoft Store product $StoreId."
|
||||
Install-WinUtilWinget
|
||||
Install-WinUtilProgramWinget -Action Install -Programs @("msstore:$StoreId")
|
||||
}
|
||||
@@ -47,7 +47,7 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
}
|
||||
|
||||
Write-Win11ISOLog "Mounting ISO: $isoPath"
|
||||
Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Mounting ISO..." -Percent 10
|
||||
|
||||
try {
|
||||
Mount-DiskImage -ImagePath $isoPath
|
||||
@@ -59,7 +59,7 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
$driveLetter = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter + ":"
|
||||
Write-Win11ISOLog "Mounted at drive $driveLetter"
|
||||
|
||||
Set-WinUtilProgressBar -Label "Verifying ISO contents..." -Percent 30
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Verifying ISO contents..." -Percent 30
|
||||
|
||||
$wimPath = Join-Path $driveLetter "sources\install.wim"
|
||||
$esdPath = Join-Path $driveLetter "sources\install.esd"
|
||||
@@ -70,13 +70,13 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.",
|
||||
"Invalid ISO", "OK", "Error")
|
||||
Set-WinUtilProgressBar -Label "" -Percent 0
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $false
|
||||
return
|
||||
}
|
||||
|
||||
$activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath }
|
||||
|
||||
Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Reading image metadata..." -Percent 55
|
||||
$imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName
|
||||
|
||||
if (-not ($imageInfo | Where-Object { $_.ImageName -match "Windows 11" })) {
|
||||
@@ -85,7 +85,7 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
[System.Windows.MessageBox]::Show(
|
||||
"No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.",
|
||||
"Not a Windows 11 ISO", "OK", "Error")
|
||||
Set-WinUtilProgressBar -Label "" -Percent 0
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
$sync["Win11ISOImagePath"] = $isoPath
|
||||
$sync["WPFWin11ISOModifySection"].Visibility = "Visible"
|
||||
|
||||
Set-WinUtilProgressBar -Label "ISO verified" -Percent 100
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "ISO verified" -Percent 100
|
||||
Write-Win11ISOLog "ISO verified OK. Editions found: $($imageInfo.Count)"
|
||||
} catch {
|
||||
Write-Win11ISOLog "ERROR during mount/verify: $_"
|
||||
@@ -123,7 +123,7 @@ function Invoke-WinUtilISOMountAndVerify {
|
||||
"Error", "OK", "Error")
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
Set-WinUtilProgressBar -Label "" -Percent 0
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ function Invoke-WinUtilISOModify {
|
||||
|
||||
$sync["WPFWin11ISOModifyButton"].IsEnabled = $false
|
||||
$sync["Win11ISOModifying"] = $true
|
||||
$sync["Win11ISOProcessRunning"] = $true
|
||||
|
||||
$workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||||
if (Test-Path $workDir) {
|
||||
@@ -203,9 +204,10 @@ function Invoke-WinUtilISOModify {
|
||||
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Visible"
|
||||
$sync["WPFTweaksProgressLabel"].Text = $label
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = $label
|
||||
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -452,10 +454,12 @@ function Invoke-WinUtilISOModify {
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
$sync["Win11ISOModifying"] = $false
|
||||
$sync["Win11ISOProcessRunning"] = $false
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
|
||||
$sync["WPFTweaksProgressLabel"].Text = ""
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = ""
|
||||
$sync["WPFTweaksProgressValue"].Value = 0
|
||||
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true
|
||||
if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") {
|
||||
$sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
|
||||
@@ -514,6 +518,7 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
}
|
||||
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false
|
||||
$sync["Win11ISOProcessRunning"] = $true
|
||||
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
$runspace.ApartmentState = "STA"
|
||||
@@ -538,9 +543,10 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Visible"
|
||||
$sync["WPFTweaksProgressLabel"].Text = $label
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = $label
|
||||
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -628,20 +634,24 @@ function Invoke-WinUtilISOCleanAndReset {
|
||||
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
|
||||
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
|
||||
$sync["WPFTweaksProgressLabel"].Text = ""
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = ""
|
||||
$sync["WPFTweaksProgressValue"].Value = 0
|
||||
|
||||
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
|
||||
})
|
||||
} catch {
|
||||
Log "ERROR during Clean & Reset: $_"
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
|
||||
$sync["WPFTweaksProgressLabel"].Text = ""
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = ""
|
||||
$sync["WPFTweaksProgressValue"].Value = 0
|
||||
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
|
||||
})
|
||||
} finally {
|
||||
$sync["Win11ISOProcessRunning"] = $false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -706,6 +716,7 @@ function Invoke-WinUtilISOExport {
|
||||
}
|
||||
|
||||
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $false
|
||||
$sync["Win11ISOProcessRunning"] = $true
|
||||
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
$runspace.ApartmentState = "STA"
|
||||
@@ -726,9 +737,10 @@ function Invoke-WinUtilISOExport {
|
||||
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Visible"
|
||||
$sync["WPFTweaksProgressLabel"].Text = $label
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = $label
|
||||
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -788,10 +800,12 @@ function Invoke-WinUtilISOExport {
|
||||
})
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
$sync["Win11ISOProcessRunning"] = $false
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
|
||||
$sync["WPFTweaksProgressLabel"].Text = ""
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = ""
|
||||
$sync["WPFTweaksProgressValue"].Value = 0
|
||||
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ function Invoke-WinUtilISOWriteUSB {
|
||||
}
|
||||
|
||||
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
|
||||
$sync["Win11ISOProcessRunning"] = $true
|
||||
Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
|
||||
|
||||
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
|
||||
@@ -86,9 +87,10 @@ function Invoke-WinUtilISOWriteUSB {
|
||||
|
||||
function SetProgress($label, $pct) {
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = $label
|
||||
$sync.progressBarTextBlock.ToolTip = $label
|
||||
$sync.ProgressBar.Value = [Math]::Max($pct, 5)
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Visible"
|
||||
$sync["WPFTweaksProgressLabel"].Text = $label
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = $label
|
||||
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -255,10 +257,12 @@ function Invoke-WinUtilISOWriteUSB {
|
||||
})
|
||||
} finally {
|
||||
Start-Sleep -Milliseconds 800
|
||||
$sync["Win11ISOProcessRunning"] = $false
|
||||
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
|
||||
$sync.progressBarTextBlock.Text = ""
|
||||
$sync.progressBarTextBlock.ToolTip = ""
|
||||
$sync.ProgressBar.Value = 0
|
||||
$sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
|
||||
$sync["WPFTweaksProgressLabel"].Text = ""
|
||||
$sync["WPFTweaksProgressLabel"].ToolTip = ""
|
||||
$sync["WPFTweaksProgressValue"].Value = 0
|
||||
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ function Remove-WinUtilProvisionedAPPX {
|
||||
$removalOutput = powershell.exe -NoProfile -NonInteractive -Command $ps5Command -args $PackageList 2>&1
|
||||
if ($LASTEXITCODE -ne 0 -or $null -ne $removalOutput) {
|
||||
$failureDetails = ($removalOutput | Out-String).Trim()
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX provisioned package removal failed: $failureDetails"
|
||||
return
|
||||
$errorMessage = "AppX provisioned package removal failed: $failureDetails"
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message $errorMessage
|
||||
throw $errorMessage
|
||||
}
|
||||
|
||||
Write-WinUtilLog -Component "AppX" -Message "AppX provisioned package removal completed."
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
function Save-WinUtilFile {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Downloads a file and reports transfer progress.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[uri]$Uri,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$DestinationPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[scriptblock]$ProgressCallback
|
||||
)
|
||||
|
||||
$response = $null
|
||||
$responseStream = $null
|
||||
$outputStream = $null
|
||||
|
||||
try {
|
||||
$request = [System.Net.WebRequest]::Create($Uri)
|
||||
$response = $request.GetResponse()
|
||||
$totalBytes = $response.ContentLength
|
||||
$responseStream = $response.GetResponseStream()
|
||||
$outputStream = [System.IO.File]::Create($DestinationPath)
|
||||
$buffer = New-Object byte[] 81920
|
||||
$downloadedBytes = 0L
|
||||
$lastPercent = -1
|
||||
|
||||
while (($bytesRead = $responseStream.Read($buffer, 0, $buffer.Length)) -gt 0) {
|
||||
$outputStream.Write($buffer, 0, $bytesRead)
|
||||
$downloadedBytes += $bytesRead
|
||||
|
||||
if ($totalBytes -gt 0) {
|
||||
$percent = [Math]::Min(100, [int](($downloadedBytes / $totalBytes) * 100))
|
||||
if ($percent -ne $lastPercent) {
|
||||
& $ProgressCallback $percent
|
||||
$lastPercent = $percent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($lastPercent -ne 100) {
|
||||
& $ProgressCallback 100
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $outputStream) {
|
||||
$outputStream.Dispose()
|
||||
}
|
||||
if ($null -ne $responseStream) {
|
||||
$responseStream.Dispose()
|
||||
}
|
||||
if ($null -ne $response) {
|
||||
$response.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
function Set-WinUtilAppCategoryFilter {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Applies an exact application category filter from an Install tab search chip.
|
||||
|
||||
.PARAMETER Category
|
||||
The application category to show. An empty value clears the filter.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Category = ""
|
||||
)
|
||||
|
||||
$sync.SearchBar.Tag = $Category
|
||||
$sync.SearchBar.Text = $Category
|
||||
Find-AppsByNameOrDescription -SearchString $Category -Category $Category
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
function Set-WinUtilProgressbar{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
This function is used to Update the Progress Bar displayed in the winutil GUI.
|
||||
It will be automatically hidden if the user clicks something and no process is running
|
||||
.PARAMETER Label
|
||||
The Text to be overlaid onto the Progress Bar
|
||||
.PARAMETER PERCENT
|
||||
The percentage of the Progress Bar that should be filled (0-100)
|
||||
#>
|
||||
param(
|
||||
[string]$Label,
|
||||
[ValidateRange(0,100)]
|
||||
[int]$Percent
|
||||
)
|
||||
|
||||
$progressLabel = $Label
|
||||
|
||||
Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.Text = $progressLabel}
|
||||
Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.ToolTip = $progressLabel}
|
||||
if ($Percent -lt 5 ) {
|
||||
$Percent = 5 # Ensure the progress bar is not empty, as it looks weird
|
||||
}
|
||||
Invoke-WPFUIThread -ScriptBlock { $sync.ProgressBar.Value = $Percent}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Shows, updates, or hides the window-level progress indicator used by long-running
|
||||
workflows such as app management, Tweaks, AppX management, and Win11 Creator.
|
||||
It lives outside the TabControl, so it stays visible no matter which tab is active.
|
||||
.PARAMETER Visible
|
||||
Whether the indicator should be shown or hidden.
|
||||
.PARAMETER Label
|
||||
The text to display above the progress bar.
|
||||
.PARAMETER Percent
|
||||
The percentage of the progress bar that should be filled (0-100).
|
||||
#>
|
||||
param(
|
||||
[bool]$Visible,
|
||||
[string]$Label,
|
||||
[ValidateRange(0,100)]
|
||||
[int]$Percent
|
||||
)
|
||||
|
||||
$indicatorVisible = if ($Visible) { [Windows.Visibility]::Visible } else { [Windows.Visibility]::Collapsed }
|
||||
$indicatorLabel = $Label
|
||||
$hasLabel = $PSBoundParameters.ContainsKey('Label')
|
||||
$hasPercent = $PSBoundParameters.ContainsKey('Percent')
|
||||
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
$sync.WPFTweaksProgressBar.Visibility = $indicatorVisible
|
||||
if ($hasLabel) {
|
||||
$sync.WPFTweaksProgressLabel.Text = $indicatorLabel
|
||||
}
|
||||
if ($hasPercent) {
|
||||
$sync.WPFTweaksProgressValue.Value = $Percent
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
function Show-WPFInstallAppBusy {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Displays a busy overlay in the install app area of the WPF form.
|
||||
This is used to indicate that an install or uninstall is in progress.
|
||||
Dynamically updates the size of the overlay based on the app area on each invocation.
|
||||
.PARAMETER text
|
||||
The text to display in the busy overlay. Defaults to "Installing apps...".
|
||||
#>
|
||||
param (
|
||||
$text = "Installing apps..."
|
||||
)
|
||||
$overlayText = $text
|
||||
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
$sync.InstallAppAreaOverlay.Visibility = [Windows.Visibility]::Visible
|
||||
$sync.InstallAppAreaOverlay.Width = $($sync.InstallAppAreaScrollViewer.ActualWidth * 0.4)
|
||||
$sync.InstallAppAreaOverlay.Height = $($sync.InstallAppAreaScrollViewer.ActualWidth * 0.4)
|
||||
$sync.InstallAppAreaOverlayText.Text = $overlayText
|
||||
$sync.InstallAppAreaBorder.IsEnabled = $false
|
||||
$sync.InstallAppAreaScrollViewer.Effect.Radius = 5
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ function Invoke-WinUtilInstallAppRenderBatch {
|
||||
}
|
||||
|
||||
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
function Invoke-WPFAppxInstall {
|
||||
if ($sync.ProcessRunning) {
|
||||
Show-WinUtilMessage -Message "An AppX process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
$sync.ProcessRunning = $true
|
||||
Invoke-WPFRunspace -ParameterList @(("selected", $selected), ("apps", $apps)) -ScriptBlock {
|
||||
param($selected, $apps)
|
||||
|
||||
$totalPackages = @($selected).Count
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
|
||||
try {
|
||||
Write-WinUtilLog -Component "AppX" -Message "Starting AppX install for $totalPackages selected package(s)."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing AppX install (0/$totalPackages)" -Percent 0
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" }
|
||||
}
|
||||
|
||||
for ($index = 0; $index -lt $totalPackages; $index++) {
|
||||
$key = $selected[$index]
|
||||
$app = $apps[$key]
|
||||
$position = $index + 1
|
||||
$startPercent = [int](($index / $totalPackages) * 100)
|
||||
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installing $($app.Content) ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
Write-Host "Installing $($app.Content)"
|
||||
Install-WinUtilAPPX -Name $app.PackageId -StoreId $app.StoreId
|
||||
|
||||
$completedPercent = [int](($position / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installed $($app.Content) ($position/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "================================="
|
||||
Write-Host "-- AppX Install Finished ---"
|
||||
Write-Host "================================="
|
||||
Write-WinUtilLog -Component "AppX" -Message "AppX install finished."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "AppX install finished" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX install failed: $($_.Exception.Message)"
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "AppX install failed" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,100 @@
|
||||
function Invoke-WPFAppxRemoval {
|
||||
if ($sync.ProcessRunning) {
|
||||
Show-WinUtilMessage -Message "An AppX process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
$selected = @($sync.selectedAppx)
|
||||
$apps = $sync.configs.appxHashtable
|
||||
|
||||
$sync.ProcessRunning = $true
|
||||
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)."
|
||||
|
||||
$totalPackages = @($selected).Count
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
$packageList = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
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 -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
# 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
|
||||
try {
|
||||
Write-WinUtilLog -Component "AppX" -Message "Starting AppX removal for $totalPackages selected package(s)."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing AppX removal (0/$totalPackages)" -Percent 0
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" }
|
||||
}
|
||||
|
||||
if ($key -eq "WPFAppxMicrosoft_WindowsNotepad") {
|
||||
Write-WinUtilLog -Component "AppX" -Message "Stopping dllhost before removing Notepad."
|
||||
Stop-Process -Name dllhost -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
for ($index = 0; $index -lt $totalPackages; $index++) {
|
||||
$key = $selected[$index]
|
||||
$app = $apps[$key]
|
||||
$position = $index + 1
|
||||
$startPercent = [int](($index / $totalPackages) * 90)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Removing $($app.Content) ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
|
||||
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 -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
# 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") {
|
||||
Write-WinUtilLog -Component "AppX" -Message "Stopping dllhost before removing Notepad."
|
||||
Stop-Process -Name dllhost -Force -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Removing $($app.Content)"
|
||||
Write-WinUtilLog -Component "AppX" -Message "Removing $($app.Content) ($($app.PackageId))."
|
||||
Remove-WinUtilAPPX -Name $app.PackageId
|
||||
$packageList.Add($app.PackageId)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$completedPercent = [int](($position / $totalPackages) * 90)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Removed $($app.Content) ($position/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Removing $($apps[$key].Content)"
|
||||
Write-WinUtilLog -Component "AppX" -Message "Removing $($apps[$key].Content) ($($apps[$key].PackageId))."
|
||||
Remove-WinUtilAPPX -Name $apps[$key].PackageId
|
||||
$packageList.Add($apps[$key].PackageId)
|
||||
if ($packageList.Count -gt 0) {
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Removing provisioned AppX packages" -Percent 90
|
||||
}
|
||||
Remove-WinUtilProvisionedAPPX -PackageList $packageList.ToArray()
|
||||
}
|
||||
|
||||
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."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "AppX removal finished" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
}
|
||||
}
|
||||
|
||||
if ($packageList.Count -gt 0) {
|
||||
Remove-WinUtilProvisionedAPPX -PackageList $packageList.ToArray()
|
||||
catch {
|
||||
Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX removal failed: $($_.Exception.Message)"
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "AppX removal failed" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
|
||||
Write-Host "================================="
|
||||
Write-Host "-- AppX Removal Finished ---"
|
||||
Write-Host "================================="
|
||||
Write-WinUtilLog -Component "AppX" -Message "AppX removal finished."
|
||||
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ function Invoke-WPFButton {
|
||||
|
||||
# Use this to get the name of the button
|
||||
#[System.Windows.MessageBox]::Show("$Button","Chris Titus Tech's Windows Utility","OK","Info")
|
||||
if (-not $sync.ProcessRunning) {
|
||||
Set-WinUtilProgressBar -label "" -percent 0
|
||||
if (-not $sync.ProcessRunning -and -not $sync.Win11ISOProcessRunning) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $false
|
||||
}
|
||||
|
||||
# Check if button is defined in feature config with function or InvokeScript
|
||||
@@ -67,6 +67,7 @@ function Invoke-WPFButton {
|
||||
"WPFGetInstalledTweaks" {Invoke-WPFGetInstalled -CheckBox "tweaks"}
|
||||
"WPFAppxRemoval" {Invoke-WPFTab "WPFTab6BT"}
|
||||
"WPFBackToTweaks" {Invoke-WPFTab "WPFTab2BT"}
|
||||
"WPFInstallSelectedAppx" {Invoke-WPFAppxInstall}
|
||||
"WPFRemoveSelectedAppx" {Invoke-WPFAppxRemoval}
|
||||
"WPFDefaultAppxSelection" {Invoke-WPFPresets "AppxDefault" -checkboxfilterpattern "WPFAppx*"}
|
||||
"WPFSelectAllAppx" {
|
||||
@@ -76,7 +77,7 @@ function Invoke-WPFButton {
|
||||
$sync.configs.appxHashtable.Keys | ForEach-Object {$sync.$_.IsChecked = $false}
|
||||
}
|
||||
"WPFGetInstalledAppx" {
|
||||
$installedAppxPackages = Get-AppxPackage -AllUsers | Select-Object -ExpandProperty Name
|
||||
$installedAppxPackages = Get-WinUtilInstalledAPPX
|
||||
foreach ($appx in $sync.configs.appxHashtable.GetEnumerator()) {
|
||||
if ($appx.Value.PackageId -in $installedAppxPackages) {
|
||||
$sync.$($appx.Key).IsChecked = $true
|
||||
@@ -85,6 +86,13 @@ function Invoke-WPFButton {
|
||||
}
|
||||
"WPFCloseButton" {$sync.Form.Close(); Write-Host "Bye bye!"}
|
||||
"WPFMinimizeButton" {$sync.Form.WindowState = [Windows.WindowState]::Minimized}
|
||||
"WPFMaximizeButton" {
|
||||
if ($sync.Form.WindowState -eq [Windows.WindowState]::Normal) {
|
||||
$sync.Form.WindowState = [Windows.WindowState]::Maximized
|
||||
} else {
|
||||
$sync.Form.WindowState = [Windows.WindowState]::Normal
|
||||
}
|
||||
}
|
||||
"WPFselectedAppsButton" {$sync.selectedAppsPopup.IsOpen = -not $sync.selectedAppsPopup.IsOpen}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,33 +31,81 @@ function Invoke-WPFInstall {
|
||||
|
||||
$packagesWinget = $packagesSorted['Winget']
|
||||
$packagesChoco = $packagesSorted['Choco']
|
||||
$totalPackages = @($packagesWinget).Count + @($packagesChoco).Count
|
||||
$completedPackages = 0
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
Write-WinUtilLog -Component "Install" -Message "Install package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
|
||||
|
||||
try {
|
||||
$sync.ProcessRunning = $true
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing app install (0/$totalPackages)" -Percent 0
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
if ($null -ne $sync.ItemsControl) {
|
||||
$sync.ItemsControl.IsEnabled = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($packagesWinget.Count -gt 0 -and $packagesWinget -ne "0") {
|
||||
Show-WPFInstallAppBusy -text "Installing apps..."
|
||||
Install-WinUtilWinget
|
||||
Install-WinUtilProgramWinget -Action Install -Programs $packagesWinget
|
||||
foreach ($program in $packagesWinget) {
|
||||
$position = $completedPackages + 1
|
||||
$startPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installing $program ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
|
||||
Install-WinUtilProgramWinget -Action Install -Programs @($program)
|
||||
$completedPackages++
|
||||
$completedPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installed $program ($completedPackages/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
if($packagesChoco.Count -gt 0) {
|
||||
$position = $completedPackages + 1
|
||||
$startPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installing Chocolatey packages ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
|
||||
Install-WinUtilChoco
|
||||
Install-WinUtilProgramChoco -Action Install -Programs $packagesChoco
|
||||
$completedPackages += @($packagesChoco).Count
|
||||
$completedPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installed Chocolatey packages ($completedPackages/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
Hide-WPFInstallAppBusy
|
||||
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" }
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App install finished" -Percent 100
|
||||
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" }
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App install failed" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
}
|
||||
} finally {
|
||||
if ($hasUI) {
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
if ($null -ne $sync.ItemsControl) {
|
||||
$sync.ItemsControl.IsEnabled = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
$sync.ProcessRunning = $False
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,50 @@
|
||||
function Invoke-WPFOOSU {
|
||||
try {
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
if ($sync.ProcessRunning) {
|
||||
Show-WinUtilMessage -Message "Another process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
|
||||
return
|
||||
}
|
||||
|
||||
Invoke-WebRequest -Uri https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe -OutFile "$winutildir\ooshutup10.exe"
|
||||
Start-Process -FilePath "$winutildir\ooshutup10.exe"
|
||||
$downloadPath = Join-Path $sync.winutildir "ooshutup10.exe"
|
||||
$sync.ProcessRunning = $true
|
||||
|
||||
$ProgressPreference = 'Continue'
|
||||
} catch {
|
||||
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
|
||||
Invoke-WPFRunspace -ParameterList @(,("downloadPath", $downloadPath)) -ScriptBlock {
|
||||
param($downloadPath)
|
||||
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
|
||||
try {
|
||||
Write-WinUtilLog -Component "OOSU" -Message "Downloading O&O ShutUp10++."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ (0%)" -Percent 0
|
||||
}
|
||||
|
||||
Save-WinUtilFile -Uri "https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe" -DestinationPath $downloadPath -ProgressCallback {
|
||||
param($percent)
|
||||
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ ($percent%)" -Percent $percent
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Launching O&O ShutUp10++" -Percent 100
|
||||
}
|
||||
Start-Process -FilePath $downloadPath
|
||||
|
||||
Write-WinUtilLog -Component "OOSU" -Message "O&O ShutUp10++ launched."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ launched" -Percent 100
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-WinUtilLog -Level "ERROR" -Component "OOSU" -Message "O&O ShutUp10++ download failed: $($_.Exception.Message)"
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ download failed" -Percent 100
|
||||
}
|
||||
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
|
||||
}
|
||||
finally {
|
||||
$sync.ProcessRunning = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,11 +404,16 @@ function Invoke-WPFUIElements {
|
||||
$textBlock.ToolTip = $entryInfo.Link
|
||||
$textBlock.Style = $HoverTextBlockStyle
|
||||
$textBlock.UseLayoutRounding = $true
|
||||
|
||||
|
||||
$textBlock.VerticalAlignment = "Center"
|
||||
$textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
|
||||
$textBlock.Tag = $checkBox
|
||||
|
||||
$textBlock.Add_MouseUp({
|
||||
[System.Object]$Sender = $args[0]
|
||||
Start-Process $Sender.ToolTip -ErrorAction Stop
|
||||
})
|
||||
|
||||
$updateLinkMargin = {
|
||||
[System.Object]$Sender = $args[0]
|
||||
$linkedCheckBox = $Sender.Tag
|
||||
|
||||
@@ -42,11 +42,21 @@ function Invoke-WPFUnInstall {
|
||||
|
||||
$packagesWinget = $packagesSorted['Winget']
|
||||
$packagesChoco = $packagesSorted['Choco']
|
||||
$totalPackages = @($packagesWinget).Count + @($packagesChoco).Count
|
||||
$completedPackages = 0
|
||||
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
|
||||
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
|
||||
|
||||
try {
|
||||
$sync.ProcessRunning = $true
|
||||
Show-WPFInstallAppBusy -text "Uninstalling apps..."
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing app uninstall (0/$totalPackages)" -Percent 0
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
if ($null -ne $sync.ItemsControl) {
|
||||
$sync.ItemsControl.IsEnabled = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($packagesWinget -contains "Microsoft.Edge") {
|
||||
New-Item -Path "$Env:SystemRoot\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\MicrosoftEdge.exe" -Force
|
||||
@@ -54,25 +64,62 @@ function Invoke-WPFUnInstall {
|
||||
|
||||
# Uninstall all selected programs in new window
|
||||
if($packagesWinget.Count -gt 0) {
|
||||
Install-WinUtilProgramWinget -Action Uninstall -Programs $packagesWinget
|
||||
foreach ($program in $packagesWinget) {
|
||||
$position = $completedPackages + 1
|
||||
$startPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalling $program ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
|
||||
Install-WinUtilProgramWinget -Action Uninstall -Programs @($program)
|
||||
$completedPackages++
|
||||
$completedPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalled $program ($completedPackages/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
}
|
||||
if($packagesChoco.Count -gt 0) {
|
||||
$position = $completedPackages + 1
|
||||
$startPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalling Chocolatey packages ($position/$totalPackages)" -Percent $startPercent
|
||||
}
|
||||
|
||||
Install-WinUtilProgramChoco -Action Uninstall -Programs $packagesChoco
|
||||
$completedPackages += @($packagesChoco).Count
|
||||
$completedPercent = [int](($completedPackages / $totalPackages) * 100)
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalled Chocolatey packages ($completedPackages/$totalPackages)" -Percent $completedPercent
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) }
|
||||
}
|
||||
}
|
||||
Hide-WPFInstallAppBusy
|
||||
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" }
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App uninstall finished" -Percent 100
|
||||
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" }
|
||||
if ($hasUI) {
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App uninstall failed" -Percent 100
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" }
|
||||
}
|
||||
} finally {
|
||||
if ($hasUI) {
|
||||
Invoke-WPFUIThread -ScriptBlock {
|
||||
if ($null -ne $sync.ItemsControl) {
|
||||
$sync.ItemsControl.IsEnabled = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
$sync.ProcessRunning = $False
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,51 @@ function Invoke-WPFUpdatesdefault {
|
||||
Resets Windows Update settings to default
|
||||
|
||||
#>
|
||||
$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."
|
||||
Write-Host "Removing Windows Update settings managed by WinUtil..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Removing Windows Update registry values managed by WinUtil."
|
||||
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Recurse -Force
|
||||
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force
|
||||
$registryValues = @(
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
|
||||
Names = @("NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement")
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
|
||||
Names = @("ExcludeWUDriversInQualityUpdate", "DeferFeatureUpdates", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdates", "DeferQualityUpdatesPeriodInDays")
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings"
|
||||
Names = @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata"
|
||||
Names = @("PreventDeviceMetadataFromNetwork")
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching"
|
||||
Names = @("DontPromptForWindowsUpdate", "DontSearchWindowsUpdate", "DriverUpdateWizardWuSearchEnabled")
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config"
|
||||
Names = @("DODownloadMode")
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
foreach ($registryEntry in $registryValues) {
|
||||
foreach ($valueName in $registryEntry.Names) {
|
||||
Remove-ItemProperty -Path $registryEntry.Path -Name $valueName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$explorerPolicyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer"
|
||||
$settingsPageVisibility = (Get-ItemProperty -Path $explorerPolicyPath -Name "SettingsPageVisibility" -ErrorAction SilentlyContinue).SettingsPageVisibility
|
||||
if ($settingsPageVisibility -eq "hide:windowsupdate") {
|
||||
Write-Host "Removing WinUtil's legacy Windows Update page restriction..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Removing the legacy Windows Update settings page restriction."
|
||||
Remove-ItemProperty -Path $explorerPolicyPath -Name "SettingsPageVisibility" -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Reenabling Windows Update Services..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update service startup types."
|
||||
@@ -35,12 +64,8 @@ function Invoke-WPFUpdatesdefault {
|
||||
|
||||
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
|
||||
Start-Service -Name UsoSvc
|
||||
|
||||
Write-Host "Enabling update related scheduled tasks..." -ForegroundColor Green
|
||||
Write-WinUtilLog -Component "Updates" -Message "Enabling update related scheduled tasks."
|
||||
@@ -54,13 +79,9 @@ function Invoke-WPFUpdatesdefault {
|
||||
'\Microsoft\WindowsUpdate\*'
|
||||
|
||||
foreach ($Task in $Tasks) {
|
||||
Get-ScheduledTask -TaskPath $Task | Enable-ScheduledTask -ErrorAction SilentlyContinue
|
||||
Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
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
|
||||
Write-Host "--- Windows Update Settings Reset to Default ---" -ForegroundColor Green
|
||||
Write-Host "===================================================" -ForegroundColor Green
|
||||
|
||||
@@ -8,7 +8,17 @@ function Invoke-WPFUpdatesdisable {
|
||||
Disabling Windows Update is not recommended. This is only for advanced users who know what they are doing.
|
||||
|
||||
#>
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$confirmation = Show-WinUtilMessage `
|
||||
-Message "Disabling Windows Update stops update services, disables scheduled tasks, and clears downloaded update files. Security updates will not be installed until defaults are restored. Continue?" `
|
||||
-Title "Disable Windows Update?" `
|
||||
-Button "YesNo" `
|
||||
-Icon "Warning"
|
||||
|
||||
if ($confirmation -ne "Yes") {
|
||||
Write-WinUtilLog -Component "Updates" -Message "Windows Update disable workflow cancelled."
|
||||
return
|
||||
}
|
||||
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update settings."
|
||||
|
||||
Write-Host "Configuring registry settings..." -ForegroundColor Yellow
|
||||
@@ -21,24 +31,14 @@ function Invoke-WPFUpdatesdisable {
|
||||
New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Force
|
||||
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
|
||||
foreach ($serviceName in @("BITS", "wuauserv", "UsoSvc")) {
|
||||
Write-Host "Stopping and disabling $serviceName service."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Stopping and disabling $serviceName service."
|
||||
Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue
|
||||
Set-Service -Name $serviceName -StartupType Disabled
|
||||
}
|
||||
|
||||
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
|
||||
Remove-Item -Path "C:\Windows\SoftwareDistribution\*" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Host "Cleared SoftwareDistribution folder."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Cleared SoftwareDistribution folder."
|
||||
|
||||
@@ -54,11 +54,11 @@ function Invoke-WPFUpdatesdisable {
|
||||
'\Microsoft\WindowsUpdate\*'
|
||||
|
||||
foreach ($Task in $Tasks) {
|
||||
Get-ScheduledTask -TaskPath $Task | Disable-ScheduledTask -ErrorAction SilentlyContinue
|
||||
Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "=================================" -ForegroundColor Green
|
||||
Write-Host "--- Updates Are Disabled ---" -ForegroundColor Green
|
||||
Write-Host "--- Windows Update Is Disabled ---" -ForegroundColor Green
|
||||
Write-Host "=================================" -ForegroundColor Green
|
||||
|
||||
Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow
|
||||
|
||||
@@ -6,10 +6,9 @@ function Invoke-WPFUpdatessecurity {
|
||||
|
||||
.DESCRIPTION
|
||||
1. Disables driver offering through Windows Update
|
||||
2. Disables Windows Update automatic restart
|
||||
3. Sets Windows Update to Semi-Annual Channel (Targeted)
|
||||
4. Defers feature updates for 365 days
|
||||
5. Defers quality updates for 4 days
|
||||
2. Defers feature updates for 365 days
|
||||
3. Defers quality updates for 4 days
|
||||
4. Prevents automatic restarts while a user is signed in
|
||||
|
||||
#>
|
||||
|
||||
@@ -17,6 +16,32 @@ function Invoke-WPFUpdatessecurity {
|
||||
Write-WinUtilLog -Component "Updates" -Message "Applying recommended Windows Update settings."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling driver offering through Windows Update."
|
||||
|
||||
$windowsUpdatePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
|
||||
$automaticUpdatePolicyPath = Join-Path $windowsUpdatePolicyPath "AU"
|
||||
|
||||
Write-Host "Restoring Windows Update availability..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update services and scheduled tasks before applying recommended settings."
|
||||
|
||||
Remove-ItemProperty -Path $automaticUpdatePolicyPath -Name "NoAutoUpdate" -ErrorAction SilentlyContinue
|
||||
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -ErrorAction SilentlyContinue
|
||||
|
||||
Set-Service -Name BITS -StartupType Manual
|
||||
Set-Service -Name wuauserv -StartupType Manual
|
||||
Set-Service -Name UsoSvc -StartupType Automatic
|
||||
Start-Service -Name UsoSvc
|
||||
|
||||
$Tasks =
|
||||
'\Microsoft\Windows\InstallService\*',
|
||||
'\Microsoft\Windows\UpdateOrchestrator\*',
|
||||
'\Microsoft\Windows\UpdateAssistant\*',
|
||||
'\Microsoft\Windows\WaaSMedic\*',
|
||||
'\Microsoft\Windows\WindowsUpdate\*',
|
||||
'\Microsoft\WindowsUpdate\*'
|
||||
|
||||
foreach ($Task in $Tasks) {
|
||||
Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -26,24 +51,30 @@ function Invoke-WPFUpdatessecurity {
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DontSearchWindowsUpdate" -Type DWord -Value 1
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DriverUpdateWizardWuSearchEnabled" -Type DWord -Value 0
|
||||
|
||||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Force
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1
|
||||
New-Item -Path $windowsUpdatePolicyPath -Force
|
||||
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1
|
||||
|
||||
Write-Host "Setting cumulative updates back by 1 year and security updates by 4 days..."
|
||||
Write-Host "Deferring feature updates by 365 days and quality 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
|
||||
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferFeatureUpdates" -Type DWord -Value 1
|
||||
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferFeatureUpdatesPeriodInDays" -Type DWord -Value 365
|
||||
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferQualityUpdates" -Type DWord -Value 1
|
||||
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4
|
||||
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "BranchReadinessLevel" -Type DWord -Value 20
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferFeatureUpdatesPeriodInDays" -Type DWord -Value 365
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4
|
||||
$legacySettingsPath = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings"
|
||||
foreach ($legacyValue in @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")) {
|
||||
Remove-ItemProperty -Path $legacySettingsPath -Name $legacyValue -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host "Disabling Windows Update automatic restart..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update automatic restart while users are logged in."
|
||||
Write-Host "Preventing automatic restarts while users are signed in..."
|
||||
Write-WinUtilLog -Component "Updates" -Message "Configuring scheduled automatic updates without restarting while users are signed 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
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUPowerManagement" -Type DWord -Value 0
|
||||
New-Item -Path $automaticUpdatePolicyPath -Force
|
||||
# NoAutoRebootWithLoggedOnUsers only applies when automatic updates use option 4.
|
||||
Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "AUOptions" -Type DWord -Value 4
|
||||
Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "NoAutoRebootWithLoggedOnUsers" -Type DWord -Value 1
|
||||
Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "AUPowerManagement" -Type DWord -Value 0
|
||||
|
||||
Write-Host "================================="
|
||||
Write-Host "-- Updates Set to Recommended ---"
|
||||
|
||||
@@ -39,13 +39,13 @@ function Invoke-WPFtweaksbutton {
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" }
|
||||
}
|
||||
|
||||
Set-WinUtilProgressBar -Label "Creating restore point" -Percent 0
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Creating restore point" -Percent 0
|
||||
Write-WinUtilLog -Component "Tweaks" -Message "Creating restore point before applying selected tweaks."
|
||||
Invoke-WinUtilTweaks $restorePointTweak
|
||||
$completedSteps = 1
|
||||
|
||||
if ($tweaksToRun.Count -eq 0 -and $dnsProvider -eq "Default") {
|
||||
Set-WinUtilProgressBar -Label "Tweaks finished" -Percent 100
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100
|
||||
$sync.ProcessRunning = $false
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
Write-Host "================================="
|
||||
@@ -75,13 +75,13 @@ function Invoke-WPFtweaksbutton {
|
||||
}
|
||||
|
||||
for ($i = 0; $i -lt $tweaks.Count; $i++) {
|
||||
Set-WinUtilProgressBar -Label "Applying $($tweaks[$i])" -Percent ($completedSteps / $totalSteps * 100)
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Applying $($tweaks[$i]) ($($completedSteps + 1)/$totalSteps)" -Percent ($completedSteps / $totalSteps * 100)
|
||||
Invoke-WinUtilTweaks $tweaks[$i]
|
||||
$completedSteps++
|
||||
$progress = $completedSteps / $totalSteps
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value $progress }
|
||||
}
|
||||
Set-WinUtilProgressBar -Label "Tweaks finished" -Percent 100
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100
|
||||
$sync.ProcessRunning = $false
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
Write-Host "================================="
|
||||
|
||||
@@ -33,12 +33,12 @@ function Invoke-WPFundoall {
|
||||
|
||||
|
||||
for ($i = 0; $i -lt $tweaks.Count; $i++) {
|
||||
Set-WinUtilProgressBar -Label "Undoing $($tweaks[$i])" -Percent ($i / $tweaks.Count * 100)
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undoing $($tweaks[$i]) ($($i + 1)/$($tweaks.Count))" -Percent ($i / $tweaks.Count * 100)
|
||||
Invoke-WinUtiltweaks $tweaks[$i] -undo $true
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($i/$tweaks.Count) }
|
||||
}
|
||||
|
||||
Set-WinUtilProgressBar -Label "Undo Tweaks Finished" -Percent 100
|
||||
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undo Tweaks Finished" -Percent 100
|
||||
$sync.ProcessRunning = $false
|
||||
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
|
||||
Write-Host "=================================="
|
||||
|
||||
+394
-6
@@ -1,12 +1,16 @@
|
||||
#===========================================================================
|
||||
# Tests - AppX Removal
|
||||
# Tests - AppX Management
|
||||
#===========================================================================
|
||||
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
. (Join-Path $script:repoRoot "functions\private\Get-WinUtilInstalledAPPX.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Install-WinUtilAPPX.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Remove-WinUtilAPPX.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Remove-WinUtilProvisionedAPPX.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFAppxInstall.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFAppxRemoval.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1")
|
||||
|
||||
$tokens = $null
|
||||
$parseErrors = $null
|
||||
@@ -20,6 +24,16 @@ BeforeAll {
|
||||
}, $true)
|
||||
$script:provisionedRemovalScriptBlock = $ps5CommandAssignment.Right.Expression.ScriptBlock.GetScriptBlock()
|
||||
|
||||
$installSourcePath = Join-Path $script:repoRoot "functions\private\Install-WinUtilAPPX.ps1"
|
||||
$installSourceAst = [System.Management.Automation.Language.Parser]::ParseFile($installSourcePath, [ref]$tokens, [ref]$parseErrors)
|
||||
$installCommandAssignment = $installSourceAst.Find({
|
||||
param($node)
|
||||
$node -is [System.Management.Automation.Language.AssignmentStatementAst] -and
|
||||
$node.Left -is [System.Management.Automation.Language.VariableExpressionAst] -and
|
||||
$node.Left.VariablePath.UserPath -eq "ps5Command"
|
||||
}, $true)
|
||||
$script:appxInstallScriptBlock = $installCommandAssignment.Right.Expression.ScriptBlock.GetScriptBlock()
|
||||
|
||||
function Write-WinUtilLog {
|
||||
param($Message, $Level, $Component)
|
||||
}
|
||||
@@ -29,8 +43,22 @@ BeforeAll {
|
||||
function Invoke-WPFRunspace {
|
||||
param($ArgumentList, $ParameterList, [scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Invoke-WPFUIThread {
|
||||
param([scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
function powershell.exe { }
|
||||
function Get-AppxPackage {
|
||||
param($Name, [switch]$AllUsers)
|
||||
param($Name, [switch]$AllUsers, $ErrorAction)
|
||||
}
|
||||
function Add-AppxPackage {
|
||||
param($Register, [switch]$DisableDevelopmentMode, $ErrorAction)
|
||||
}
|
||||
function Install-WinUtilWinget { }
|
||||
function Install-WinUtilProgramWinget {
|
||||
param($Action, $Programs)
|
||||
}
|
||||
function Remove-AppxPackage {
|
||||
param(
|
||||
@@ -73,6 +101,161 @@ BeforeAll {
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get-WinUtilInstalledAPPX" {
|
||||
BeforeEach {
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock powershell.exe {
|
||||
$global:LASTEXITCODE = 0
|
||||
@("Example.One", "Example.Two")
|
||||
}
|
||||
}
|
||||
|
||||
It "queries installed package names through Windows PowerShell" {
|
||||
$result = Get-WinUtilInstalledAPPX
|
||||
|
||||
$result | Should -Be @("Example.One", "Example.Two")
|
||||
Should -Invoke -CommandName powershell.exe -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 0 -Exactly
|
||||
}
|
||||
|
||||
It "logs query failures and returns no package names" {
|
||||
Mock powershell.exe {
|
||||
$global:LASTEXITCODE = 1
|
||||
"AppX query failed"
|
||||
}
|
||||
|
||||
$result = @(Get-WinUtilInstalledAPPX)
|
||||
|
||||
$result | Should -HaveCount 0
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Level -eq "ERROR" -and
|
||||
$Component -eq "AppX" -and
|
||||
$Message -eq "Failed to get installed AppX packages: AppX query failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Install-WinUtilAPPX" {
|
||||
BeforeEach {
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Install-WinUtilWinget { }
|
||||
Mock Install-WinUtilProgramWinget { }
|
||||
Mock powershell.exe {
|
||||
$global:LASTEXITCODE = 0
|
||||
"C:\Program Files\WindowsApps\Example.Package\AppxManifest.xml"
|
||||
}
|
||||
}
|
||||
|
||||
It "uses a local manifest without contacting the Microsoft Store" {
|
||||
Install-WinUtilAPPX -Name "Example.Package" -StoreId "9EXAMPLE1234"
|
||||
|
||||
Should -Invoke -CommandName Install-WinUtilWinget -Times 0 -Exactly
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 0 -Exactly
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Component -eq "AppX" -and
|
||||
$Message -like "Registered local AppX manifest for Example.Package*"
|
||||
}
|
||||
}
|
||||
|
||||
It "falls back to the Microsoft Store when no local manifest is available" {
|
||||
Mock powershell.exe { $global:LASTEXITCODE = 0 }
|
||||
|
||||
Install-WinUtilAPPX -Name "Example.Package" -StoreId "9EXAMPLE1234"
|
||||
|
||||
Should -Invoke -CommandName Install-WinUtilWinget -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter {
|
||||
$Action -eq "Install" -and $Programs.Count -eq 1 -and $Programs[0] -eq "msstore:9EXAMPLE1234"
|
||||
}
|
||||
}
|
||||
|
||||
It "logs local registration failures before using the Microsoft Store" {
|
||||
Mock powershell.exe {
|
||||
$global:LASTEXITCODE = 1
|
||||
"Registration failed"
|
||||
}
|
||||
|
||||
Install-WinUtilAPPX -Name "Example.Package" -StoreId "9EXAMPLE1234"
|
||||
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Level -eq "WARN" -and
|
||||
$Component -eq "AppX" -and
|
||||
$Message -eq "Local AppX registration failed for Example.Package: Registration failed"
|
||||
}
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly
|
||||
}
|
||||
|
||||
It "throws after logging an error when neither install method is available" {
|
||||
Mock powershell.exe { $global:LASTEXITCODE = 0 }
|
||||
|
||||
{ Install-WinUtilAPPX -Name "Example.Package" } |
|
||||
Should -Throw "Unable to install Example.Package because no local manifest or Microsoft Store ID is available."
|
||||
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 0 -Exactly
|
||||
Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
|
||||
$Level -eq "ERROR" -and
|
||||
$Component -eq "AppX" -and
|
||||
$Message -eq "Unable to install Example.Package because no local manifest or Microsoft Store ID is available."
|
||||
}
|
||||
}
|
||||
|
||||
It "registers an installed package manifest through Windows PowerShell" {
|
||||
Mock Get-AppxPackage {
|
||||
[pscustomobject]@{
|
||||
InstallLocation = "C:\Program Files\WindowsApps\Example.Package"
|
||||
Version = [version]"2.0.0.0"
|
||||
}
|
||||
}
|
||||
Mock Get-AppxProvisionedPackage { }
|
||||
Mock Test-Path { $LiteralPath -eq "C:\Program Files\WindowsApps\Example.Package\AppxManifest.xml" }
|
||||
Mock Add-AppxPackage { }
|
||||
|
||||
$result = & $script:appxInstallScriptBlock "Example.Package"
|
||||
|
||||
$result | Should -Be "C:\Program Files\WindowsApps\Example.Package\AppxManifest.xml"
|
||||
Should -Invoke -CommandName Get-AppxPackage -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Add-AppxPackage -Times 1 -Exactly
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Get installed AppX selection" {
|
||||
BeforeEach {
|
||||
$script:sync = [Hashtable]::Synchronized(@{
|
||||
ProcessRunning = $false
|
||||
configs = @{
|
||||
feature = @{}
|
||||
appxHashtable = @{
|
||||
WPFAppxExample = [pscustomobject]@{ PackageId = "Example.Package" }
|
||||
WPFAppxMissing = [pscustomobject]@{ PackageId = "Missing.Package" }
|
||||
}
|
||||
}
|
||||
WPFAppxExample = [pscustomobject]@{ IsChecked = $false }
|
||||
WPFAppxMissing = [pscustomobject]@{ IsChecked = $false }
|
||||
})
|
||||
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Get-WinUtilInstalledAPPX { @("Example.Package") }
|
||||
Mock Invoke-WPFAppxInstall { }
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "selects configured packages returned by the compatibility-safe query" {
|
||||
Invoke-WPFButton -Button "WPFGetInstalledAppx"
|
||||
|
||||
Should -Invoke -CommandName Get-WinUtilInstalledAPPX -Times 1 -Exactly
|
||||
$script:sync.WPFAppxExample.IsChecked | Should -BeTrue
|
||||
$script:sync.WPFAppxMissing.IsChecked | Should -BeFalse
|
||||
}
|
||||
|
||||
It "routes the install button to the AppX install workflow" {
|
||||
Invoke-WPFButton -Button "WPFInstallSelectedAppx"
|
||||
|
||||
Should -Invoke -CommandName Invoke-WPFAppxInstall -Times 1 -Exactly
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Remove-WinUtilAPPX" {
|
||||
BeforeEach {
|
||||
Mock Write-Host { }
|
||||
@@ -155,13 +338,123 @@ Describe "Remove-WinUtilProvisionedAPPX" {
|
||||
}
|
||||
}
|
||||
|
||||
It "handles child process failures before logging completion" {
|
||||
It "throws after logging child process failures" {
|
||||
$source = Get-Content -Path $provisionedSourcePath -Raw
|
||||
|
||||
$source | Should -Match '\$removalOutput = powershell\.exe .* 2>&1'
|
||||
$source | Should -Match 'if \(\$LASTEXITCODE -ne 0 -or \$null -ne \$removalOutput\)'
|
||||
$source | Should -Match 'Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX provisioned package removal failed:'
|
||||
$source | Should -Match '(?s)AppX provisioned package removal failed:.*return.*AppX provisioned package removal completed\.'
|
||||
$source | Should -Match 'Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message \$errorMessage'
|
||||
$source | Should -Match '(?s)AppX provisioned package removal failed:.*throw \$errorMessage.*AppX provisioned package removal completed\.'
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFAppxInstall" {
|
||||
BeforeEach {
|
||||
$script:sync = [Hashtable]::Synchronized(@{
|
||||
ProcessRunning = $false
|
||||
Form = [pscustomobject]@{ Dispatcher = [pscustomobject]@{} }
|
||||
selectedAppx = [System.Collections.Generic.List[string]]::new()
|
||||
configs = @{
|
||||
appxHashtable = @{
|
||||
WPFAppxExample = [pscustomobject]@{
|
||||
Content = "Example App"
|
||||
PackageId = "Example.Package"
|
||||
StoreId = "9EXAMPLE1234"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
$script:capturedAppxInstallScriptBlock = $null
|
||||
$script:capturedAppxInstallParameterList = $null
|
||||
$script:appxInstallProcessRunningAtLaunch = $null
|
||||
|
||||
Mock Show-WinUtilMessage { "OK" }
|
||||
Mock Write-Host { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Invoke-WPFUIThread { }
|
||||
Mock Install-WinUtilAPPX { }
|
||||
Mock Invoke-WPFRunspace {
|
||||
$script:appxInstallProcessRunningAtLaunch = $script:sync.ProcessRunning
|
||||
$script:capturedAppxInstallScriptBlock = $ScriptBlock
|
||||
$script:capturedAppxInstallParameterList = $ParameterList
|
||||
[pscustomobject]@{ MockHandle = $true }
|
||||
}
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedAppxInstallScriptBlock -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedAppxInstallParameterList -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name appxInstallProcessRunningAtLaunch -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "prompts and exits when no AppX packages are selected for install" {
|
||||
Invoke-WPFAppxInstall
|
||||
|
||||
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
|
||||
$Message -eq "No AppX Package selected" -and
|
||||
$Title -eq "Error" -and
|
||||
$Button -eq "OK" -and
|
||||
$Icon -eq "Error"
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFRunspace -Times 0 -Exactly
|
||||
}
|
||||
|
||||
It "prevents overlapping AppX install operations" {
|
||||
$script:sync.ProcessRunning = $true
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
|
||||
Invoke-WPFAppxInstall
|
||||
|
||||
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
|
||||
$Message -eq "An AppX process is currently running." -and
|
||||
$Title -eq "WinUtil" -and
|
||||
$Button -eq "OK" -and
|
||||
$Icon -eq "Warning"
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFRunspace -Times 0 -Exactly
|
||||
}
|
||||
|
||||
It "installs selected AppX packages with their Store IDs" {
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
|
||||
Invoke-WPFAppxInstall
|
||||
$script:appxInstallProcessRunningAtLaunch | Should -BeTrue
|
||||
& $script:capturedAppxInstallScriptBlock -selected @("WPFAppxExample") -apps $script:sync.configs.appxHashtable
|
||||
|
||||
$script:capturedAppxInstallParameterList[0][1][0] | Should -Be "WPFAppxExample"
|
||||
Should -Invoke -CommandName Install-WinUtilAPPX -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "Example.Package" -and $StoreId -eq "9EXAMPLE1234"
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Installing Example App (1/1)" -and $Percent -eq 0
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Installed Example App (1/1)" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "AppX install finished" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "shows failure feedback and clears ProcessRunning when install fails" {
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
Mock Install-WinUtilAPPX { throw "Install failed" }
|
||||
|
||||
Invoke-WPFAppxInstall
|
||||
& $script:capturedAppxInstallScriptBlock -selected @("WPFAppxExample") -apps $script:sync.configs.appxHashtable
|
||||
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "AppX install failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +469,11 @@ Describe "Invoke-WPFAppxRemoval entrypoint" {
|
||||
})
|
||||
$script:capturedAppxScriptBlock = $null
|
||||
$script:capturedAppxParameterList = $null
|
||||
$script:appxRemovalProcessRunningAtLaunch = $null
|
||||
|
||||
Mock Show-WinUtilMessage { "OK" }
|
||||
Mock Invoke-WPFRunspace {
|
||||
$script:appxRemovalProcessRunningAtLaunch = $script:sync.ProcessRunning
|
||||
$script:capturedAppxScriptBlock = $ScriptBlock
|
||||
$script:capturedAppxParameterList = $ParameterList
|
||||
[pscustomobject]@{ MockHandle = $true }
|
||||
@@ -189,6 +484,7 @@ Describe "Invoke-WPFAppxRemoval entrypoint" {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedAppxScriptBlock -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedAppxParameterList -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name appxRemovalProcessRunningAtLaunch -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "prompts and exits when no AppX packages are selected" {
|
||||
@@ -203,6 +499,21 @@ Describe "Invoke-WPFAppxRemoval entrypoint" {
|
||||
Should -Invoke -CommandName Invoke-WPFRunspace -Times 0 -Exactly
|
||||
}
|
||||
|
||||
It "prevents overlapping AppX removal operations" {
|
||||
$script:sync.ProcessRunning = $true
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
|
||||
Invoke-WPFAppxRemoval
|
||||
|
||||
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
|
||||
$Message -eq "An AppX process is currently running." -and
|
||||
$Title -eq "WinUtil" -and
|
||||
$Button -eq "OK" -and
|
||||
$Icon -eq "Warning"
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFRunspace -Times 0 -Exactly
|
||||
}
|
||||
|
||||
It "passes selected AppX keys and app metadata to the removal runspace" {
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
$script:sync.configs.appxHashtable["WPFAppxExample"] = [pscustomobject]@{
|
||||
@@ -211,7 +522,10 @@ Describe "Invoke-WPFAppxRemoval entrypoint" {
|
||||
}
|
||||
|
||||
Invoke-WPFAppxRemoval
|
||||
$script:sync.selectedAppx.Add("WPFAppxChangedAfterLaunch")
|
||||
|
||||
$script:appxRemovalProcessRunningAtLaunch | Should -BeTrue
|
||||
$script:capturedAppxParameterList[0][1] | Should -HaveCount 1
|
||||
Should -Invoke -CommandName Show-WinUtilMessage -Times 0 -Exactly
|
||||
Should -Invoke -CommandName Invoke-WPFRunspace -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock -is [scriptblock] -and
|
||||
@@ -228,6 +542,7 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
|
||||
BeforeEach {
|
||||
$script:sync = [Hashtable]::Synchronized(@{
|
||||
ProcessRunning = $false
|
||||
Form = [pscustomobject]@{ Dispatcher = [pscustomobject]@{} }
|
||||
selectedAppx = [System.Collections.Generic.List[string]]::new()
|
||||
configs = @{
|
||||
appxHashtable = @{}
|
||||
@@ -260,6 +575,8 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
|
||||
Mock Show-WinUtilMessage { "OK" }
|
||||
Mock Write-Host { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Invoke-WPFUIThread { }
|
||||
Mock Stop-Process { }
|
||||
Mock Set-ItemProperty { }
|
||||
Mock Get-AppxPackage {
|
||||
@@ -301,6 +618,77 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
|
||||
Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 1 -Exactly -ParameterFilter {
|
||||
$PackageList.Count -eq 1 -and $PackageList[0] -eq "Example.Package"
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Removing Example App (1/1)" -and $Percent -eq 0
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Removed Example App (1/1)" -and $Percent -eq 90
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "AppX removal finished" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "shows failure feedback and clears ProcessRunning when removal fails" {
|
||||
$selected = @("WPFAppxExample")
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
$script:sync.configs.appxHashtable = $script:apps
|
||||
Mock Remove-WinUtilAPPX { throw "Removal failed" }
|
||||
|
||||
Invoke-WPFAppxRemoval
|
||||
& $script:capturedAppxScriptBlock -selected $selected -apps $script:apps
|
||||
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "AppX removal failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
|
||||
}
|
||||
Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 0 -Exactly
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "removes packages without UI progress during headless autorun" {
|
||||
$selected = @("WPFAppxExample")
|
||||
$script:sync.Remove("Form")
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
$script:sync.configs.appxHashtable = $script:apps
|
||||
|
||||
Invoke-WPFAppxRemoval
|
||||
& $script:capturedAppxScriptBlock -selected $selected -apps $script:apps
|
||||
|
||||
Should -Invoke -CommandName Remove-AppxPackage -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 0 -Exactly
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 0 -Exactly
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "shows failure feedback when provisioned package removal fails" {
|
||||
$selected = @("WPFAppxExample")
|
||||
$script:sync.selectedAppx.Add("WPFAppxExample")
|
||||
$script:sync.configs.appxHashtable = $script:apps
|
||||
Mock Remove-WinUtilProvisionedAPPX { throw "Provisioned removal failed" }
|
||||
|
||||
Invoke-WPFAppxRemoval
|
||||
& $script:capturedAppxScriptBlock -selected $selected -apps $script:apps
|
||||
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "AppX removal failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 0 -Exactly -ParameterFilter {
|
||||
$Label -eq "AppX removal finished"
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 0 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
|
||||
@@ -326,6 +326,11 @@ Describe "UI-rendered config entries" {
|
||||
foreach ($missingField in (Get-WinUtilMissingRequiredFields -EntryName $entry.Name -Entry $entry.Value -RequiredFields $requiredFields)) {
|
||||
$invalidEntries.Add($missingField)
|
||||
}
|
||||
|
||||
if ((Test-WinUtilHasProperty -Object $entry.Value -Name "StoreId") -and
|
||||
$entry.Value.StoreId -notmatch '^(?:[A-Z0-9]{12}|XP[A-Z0-9]{12})$') {
|
||||
$invalidEntries.Add("$($entry.Name) has invalid Microsoft Store ID '$($entry.Value.StoreId)'")
|
||||
}
|
||||
}
|
||||
|
||||
if ($invalidEntries.Count -gt 0) {
|
||||
|
||||
@@ -21,6 +21,7 @@ Describe "Install app rendering startup contract" {
|
||||
$renderScript | Should -Match 'Dispatcher\.BeginInvoke'
|
||||
$renderScript | Should -Match 'Invoke-WinUtilInstallAppRenderNextBatch'
|
||||
$renderScript | Should -Match 'Initialize-InstallAppEntry'
|
||||
$renderScript | Should -Match 'Find-AppsByNameOrDescription -SearchString \$sync\.SearchBar\.Text -Category \$sync\.SearchBar\.Tag'
|
||||
$renderScript | Should -Match '\$sync\.InstallAppEntriesRendered = \$true'
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ Describe "Install app rendering startup contract" {
|
||||
}
|
||||
|
||||
function global:Find-AppsByNameOrDescription {
|
||||
param($SearchString)
|
||||
param($SearchString, $Category)
|
||||
throw "Search should not run for an empty search box in this test."
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,9 @@ BeforeAll {
|
||||
function Get-WinUtilSelectedPackages {
|
||||
param($PackageList, [string]$Preference)
|
||||
}
|
||||
function Show-WPFInstallAppBusy {
|
||||
param($text)
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
function Hide-WPFInstallAppBusy { }
|
||||
function Install-WinUtilWinget { }
|
||||
function Install-WinUtilChoco { }
|
||||
function Install-WinUtilProgramWinget {
|
||||
@@ -74,6 +73,9 @@ BeforeAll {
|
||||
preferences = [pscustomobject]@{
|
||||
packagemanager = "Winget"
|
||||
}
|
||||
Form = [pscustomobject]@{
|
||||
Dispatcher = [pscustomobject]@{}
|
||||
}
|
||||
configs = @{
|
||||
applicationsHashtable = $applications
|
||||
}
|
||||
@@ -187,8 +189,7 @@ Describe "Invoke-WPFInstall runspace body" {
|
||||
Mock Get-WinUtilSelectedPackages {
|
||||
New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc")
|
||||
}
|
||||
Mock Show-WPFInstallAppBusy { }
|
||||
Mock Hide-WPFInstallAppBusy { }
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Install-WinUtilWinget { }
|
||||
Mock Install-WinUtilChoco { }
|
||||
Mock Install-WinUtilProgramWinget { }
|
||||
@@ -212,8 +213,17 @@ Describe "Invoke-WPFInstall runspace body" {
|
||||
Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter {
|
||||
@($PackageList).Count -eq 1 -and $Preference -eq "Winget"
|
||||
}
|
||||
Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter {
|
||||
$text -eq "Installing apps..."
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Preparing app install (0/2)" -and $Percent -eq 0
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Installed Git.Git (1/2)" -and $Percent -eq 50
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Installed Chocolatey packages (2/2)" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "App install finished" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Install-WinUtilWinget -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter {
|
||||
@@ -223,21 +233,34 @@ Describe "Invoke-WPFInstall runspace body" {
|
||||
Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter {
|
||||
$Action -eq "Install" -and @($Programs)[0] -eq "vlc"
|
||||
}
|
||||
Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "hides the busy overlay, sets taskbar error state, and clears ProcessRunning on failure" {
|
||||
It "shows failure progress, sets taskbar error state, and clears ProcessRunning on failure" {
|
||||
Mock Install-WinUtilProgramWinget { throw "winget failed" }
|
||||
|
||||
Invoke-WPFInstall
|
||||
|
||||
& $script:capturedInstallScriptBlock -PackagesToInstall @($script:package) -ManagerPreference "Winget"
|
||||
|
||||
Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "App install failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
|
||||
}
|
||||
@@ -345,8 +368,7 @@ Describe "Invoke-WPFUnInstall runspace body" {
|
||||
Mock Get-WinUtilSelectedPackages {
|
||||
New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc")
|
||||
}
|
||||
Mock Show-WPFInstallAppBusy { }
|
||||
Mock Hide-WPFInstallAppBusy { }
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Install-WinUtilProgramWinget { }
|
||||
Mock Install-WinUtilProgramChoco { }
|
||||
Mock Invoke-WPFUIThread { }
|
||||
@@ -369,8 +391,17 @@ Describe "Invoke-WPFUnInstall runspace body" {
|
||||
Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter {
|
||||
@($PackageList).Count -eq 1 -and $Preference -eq "Winget"
|
||||
}
|
||||
Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter {
|
||||
$text -eq "Uninstalling apps..."
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Preparing app uninstall (0/2)" -and $Percent -eq 0
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Uninstalled Git.Git (1/2)" -and $Percent -eq 50
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Uninstalled Chocolatey packages (2/2)" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "App uninstall finished" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter {
|
||||
$Action -eq "Uninstall" -and @($Programs)[0] -eq "Git.Git"
|
||||
@@ -378,21 +409,34 @@ Describe "Invoke-WPFUnInstall runspace body" {
|
||||
Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter {
|
||||
$Action -eq "Uninstall" -and @($Programs)[0] -eq "vlc"
|
||||
}
|
||||
Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "hides the busy overlay, sets taskbar error state, and clears ProcessRunning on failure" {
|
||||
It "shows failure progress, sets taskbar error state, and clears ProcessRunning on failure" {
|
||||
Mock Install-WinUtilProgramWinget { throw "winget failed" }
|
||||
|
||||
Invoke-WPFUnInstall -PackagesToUninstall @($script:package)
|
||||
|
||||
& $script:capturedUninstallScriptBlock -PackagesToUninstall @($script:package) -ManagerPreference "Winget"
|
||||
|
||||
Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly
|
||||
Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "App uninstall failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*'
|
||||
}
|
||||
Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
|
||||
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
|
||||
}
|
||||
|
||||
@@ -69,6 +69,23 @@ Describe "Initialize-WinUtilTabContent" {
|
||||
$targetGridName -eq "appxpanel" -and $columncount -eq 2
|
||||
}
|
||||
}
|
||||
|
||||
It "checks for existing Win11ISO work when the tab is initialized" {
|
||||
Add-Type -AssemblyName WindowsBase
|
||||
$dispatcher = [pscustomobject]@{}
|
||||
$dispatcher | Add-Member -MemberType ScriptMethod -Name BeginInvoke -Value {
|
||||
param($priority, $action)
|
||||
$action.Invoke()
|
||||
}
|
||||
$script:sync.Form = [pscustomobject]@{ Dispatcher = $dispatcher }
|
||||
Mock Invoke-WinUtilISOCheckExistingWork { }
|
||||
|
||||
Initialize-WinUtilTabContent -TabName "Win11ISO"
|
||||
Initialize-WinUtilTabContent -TabName "Win11ISO"
|
||||
|
||||
Should -Invoke -CommandName Invoke-WinUtilISOCheckExistingWork -Times 1 -Exactly
|
||||
$script:sync.InitializedTabs["Win11ISO"] | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Startup lazy tab wiring" {
|
||||
@@ -96,4 +113,12 @@ Describe "Startup lazy tab wiring" {
|
||||
$rendererScript | Should -Match '\$sync\.Buttons\.Add\(\$button\.Name\)'
|
||||
$mainScript | Should -Match '\$sync\.Buttons -notcontains \$psitem'
|
||||
}
|
||||
|
||||
It "binds generated documentation links when lazy panels are rendered" {
|
||||
$rendererScript = Get-Content -Path (Join-Path $script:repoRoot "functions\public\Invoke-WPFUIElements.ps1") -Raw
|
||||
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
|
||||
|
||||
$rendererScript | Should -Match '(?s)if \(\$entryInfo\.Link\).*\$textBlock\.Add_MouseUp\(\{.*Start-Process \$Sender\.ToolTip -ErrorAction Stop'
|
||||
$mainScript | Should -Not -Match '\.Name\.EndsWith\("Link"\)'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
#===========================================================================
|
||||
# Tests - O&O ShutUp10++ Download Workflow
|
||||
#===========================================================================
|
||||
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
|
||||
. (Join-Path $script:repoRoot "functions\private\Save-WinUtilFile.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFOOSU.ps1")
|
||||
|
||||
function Invoke-WPFRunspace {
|
||||
param($ArgumentList, $ParameterList, [scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
function Show-WinUtilMessage {
|
||||
param($Message, $Title, $Button, $Icon)
|
||||
}
|
||||
function Write-WinUtilLog {
|
||||
param($Message, $Level, $Component)
|
||||
}
|
||||
|
||||
function script:New-WinUtilOOSUTestContext {
|
||||
param([bool]$ProcessRunning = $false)
|
||||
|
||||
$script:sync = [Hashtable]::Synchronized(@{
|
||||
ProcessRunning = $ProcessRunning
|
||||
winutildir = $TestDrive
|
||||
Form = [pscustomobject]@{
|
||||
Dispatcher = [pscustomobject]@{}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Save-WinUtilFile" {
|
||||
It "copies a download and reports its percentage" {
|
||||
$sourcePath = Join-Path $TestDrive "source.bin"
|
||||
$destinationPath = Join-Path $TestDrive "destination.bin"
|
||||
$sourceBytes = [byte[]](0..255)
|
||||
[System.IO.File]::WriteAllBytes($sourcePath, $sourceBytes)
|
||||
$reportedProgress = [System.Collections.Generic.List[int]]::new()
|
||||
|
||||
Save-WinUtilFile -Uri ([uri]$sourcePath) -DestinationPath $destinationPath -ProgressCallback {
|
||||
param($percent)
|
||||
$reportedProgress.Add($percent)
|
||||
}
|
||||
|
||||
[System.IO.File]::ReadAllBytes($destinationPath) | Should -Be $sourceBytes
|
||||
$reportedProgress[-1] | Should -Be 100
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFOOSU" {
|
||||
BeforeEach {
|
||||
New-WinUtilOOSUTestContext
|
||||
$script:capturedScriptBlock = $null
|
||||
$script:capturedParameterList = $null
|
||||
|
||||
Mock Invoke-WPFRunspace {
|
||||
$script:capturedScriptBlock = $ScriptBlock
|
||||
$script:capturedParameterList = $ParameterList
|
||||
[pscustomobject]@{ MockHandle = $true }
|
||||
}
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
Mock Show-WinUtilMessage { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Start-Process { }
|
||||
Mock Write-Error { }
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedScriptBlock -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name capturedParameterList -Scope Script -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "queues the download in a background runspace" {
|
||||
Invoke-WPFOOSU
|
||||
|
||||
$script:sync.ProcessRunning | Should -BeTrue
|
||||
Should -Invoke Invoke-WPFRunspace -Times 1 -Exactly
|
||||
$script:capturedParameterList[0][0] | Should -Be "downloadPath"
|
||||
$script:capturedParameterList[0][1] | Should -Be (Join-Path $TestDrive "ooshutup10.exe")
|
||||
}
|
||||
|
||||
It "does not start while another process is running" {
|
||||
New-WinUtilOOSUTestContext -ProcessRunning $true
|
||||
|
||||
Invoke-WPFOOSU
|
||||
|
||||
Should -Invoke Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
|
||||
$Message -eq "Another process is currently running." -and
|
||||
$Title -eq "WinUtil" -and
|
||||
$Button -eq "OK" -and
|
||||
$Icon -eq "Warning"
|
||||
}
|
||||
Should -Not -Invoke Invoke-WPFRunspace
|
||||
}
|
||||
|
||||
It "maps download progress to the window indicator and launches O&O ShutUp10++" {
|
||||
Mock Save-WinUtilFile {
|
||||
& $ProgressCallback 35
|
||||
& $ProgressCallback 100
|
||||
}
|
||||
|
||||
Invoke-WPFOOSU
|
||||
& $script:capturedScriptBlock -downloadPath $script:capturedParameterList[0][1]
|
||||
|
||||
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Downloading O&O ShutUp10++ (0%)" -and $Percent -eq 0
|
||||
}
|
||||
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "Downloading O&O ShutUp10++ (35%)" -and $Percent -eq 35
|
||||
}
|
||||
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "O&O ShutUp10++ launched" -and $Percent -eq 100
|
||||
}
|
||||
Should -Invoke Start-Process -Times 1 -Exactly -ParameterFilter {
|
||||
$FilePath -eq (Join-Path $TestDrive "ooshutup10.exe")
|
||||
}
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
|
||||
It "shows failure progress and clears the running state when the download fails" {
|
||||
Mock Save-WinUtilFile { throw "download failed" }
|
||||
|
||||
Invoke-WPFOOSU
|
||||
& $script:capturedScriptBlock -downloadPath $script:capturedParameterList[0][1]
|
||||
|
||||
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $true -and $Label -eq "O&O ShutUp10++ download failed" -and $Percent -eq 100
|
||||
}
|
||||
Should -Not -Invoke Start-Process
|
||||
Should -Invoke Write-Error -Times 1 -Exactly
|
||||
$script:sync.ProcessRunning | Should -BeFalse
|
||||
}
|
||||
}
|
||||
@@ -172,18 +172,27 @@ namespace Windows.Controls
|
||||
WPFInstallBrowser = [pscustomobject]@{
|
||||
Content = "Firefox"
|
||||
Description = "Fast private browser"
|
||||
Category = "Browsers"
|
||||
}
|
||||
WPFInstallMedia = [pscustomobject]@{
|
||||
Content = "VLC"
|
||||
Description = "Media player"
|
||||
Category = "Multimedia Tools"
|
||||
}
|
||||
WPFInstallLiteral = [pscustomobject]@{
|
||||
Content = "Tool [abc]"
|
||||
Description = "Literal wildcard sample"
|
||||
Category = "Utilities"
|
||||
}
|
||||
WPFInstallEditor = [pscustomobject]@{
|
||||
Content = "Code Editor"
|
||||
Description = "Text editing"
|
||||
Category = "Development"
|
||||
}
|
||||
WPFInstallPowerToys = [pscustomobject]@{
|
||||
Content = "PowerToys"
|
||||
Description = "A collection of system utilities"
|
||||
Category = "Microsoft Tools"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,6 +372,19 @@ Describe "Find-AppsByNameOrDescription" {
|
||||
$mediaItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
$category.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
}
|
||||
|
||||
It "filters category chips by exact application category" {
|
||||
$utilityItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral"
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($utilityItem, $powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -SearchString "Utilities" -Category "Utilities"
|
||||
|
||||
$utilityItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
$category.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Find-TweaksByNameOrDescription" {
|
||||
|
||||
@@ -31,8 +31,8 @@ BeforeAll {
|
||||
function Invoke-WPFUIThread {
|
||||
param([scriptblock]$ScriptBlock)
|
||||
}
|
||||
function Set-WinUtilProgressBar {
|
||||
param($Label, $Percent)
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
function Write-WinUtilLog {
|
||||
param($Message, $Level, $Component)
|
||||
@@ -184,7 +184,6 @@ Describe "Invoke-WPFtweaksbutton" {
|
||||
Mock Invoke-WPFRunspace { [pscustomobject]@{ MockHandle = $true } }
|
||||
Mock Invoke-WinUtilTweaks { }
|
||||
Mock Invoke-WPFUIThread { }
|
||||
Mock Set-WinUtilProgressBar { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Write-Host { }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
BeforeAll {
|
||||
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
|
||||
if (-not ("System.Windows.Controls.CheckBox" -as [type])) {
|
||||
if (-not ("Windows.Visibility" -as [type])) {
|
||||
Add-Type @"
|
||||
namespace Windows
|
||||
{
|
||||
@@ -15,7 +15,25 @@ namespace Windows
|
||||
Collapsed
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
if (-not ("Windows.WindowState" -as [type])) {
|
||||
Add-Type @"
|
||||
namespace Windows
|
||||
{
|
||||
public enum WindowState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized
|
||||
}
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
if (-not ("System.Windows.Controls.CheckBox" -as [type])) {
|
||||
Add-Type @"
|
||||
namespace System.Windows.Controls
|
||||
{
|
||||
public class CheckBox
|
||||
@@ -44,8 +62,13 @@ namespace System.Windows.Controls
|
||||
. (Join-Path $script:repoRoot "functions\private\Update-WinUtilSelections.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\private\Reset-WPFCheckBoxes.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFSelectedCheckboxesUpdate.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1")
|
||||
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFToggleAllCategories.ps1")
|
||||
|
||||
function Set-WinUtilTweaksProgressIndicator {
|
||||
param($Visible, $Label, $Percent)
|
||||
}
|
||||
|
||||
function script:New-WinUtilFakeCheckBox {
|
||||
param([bool]$IsChecked = $false)
|
||||
|
||||
@@ -272,3 +295,71 @@ Describe "Invoke-WPFToggleAllCategories" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFButton progress cleanup" {
|
||||
BeforeEach {
|
||||
New-WinUtilUiStateTestContext
|
||||
Mock Set-WinUtilTweaksProgressIndicator { }
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "clears completed progress on the next idle button click" {
|
||||
$script:sync.ProcessRunning = $false
|
||||
|
||||
Invoke-WPFButton -Button "WPFNoOp"
|
||||
|
||||
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
|
||||
$Visible -eq $false
|
||||
}
|
||||
}
|
||||
|
||||
It "leaves progress visible while a process is running" {
|
||||
$script:sync.ProcessRunning = $true
|
||||
|
||||
Invoke-WPFButton -Button "WPFNoOp"
|
||||
|
||||
Should -Not -Invoke Set-WinUtilTweaksProgressIndicator
|
||||
}
|
||||
|
||||
It "leaves progress visible while a Win11 ISO process is running" {
|
||||
$script:sync.ProcessRunning = $false
|
||||
$script:sync.Win11ISOProcessRunning = $true
|
||||
|
||||
Invoke-WPFButton -Button "WPFNoOp"
|
||||
|
||||
Should -Not -Invoke Set-WinUtilTweaksProgressIndicator
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFButton window state" {
|
||||
BeforeEach {
|
||||
New-WinUtilUiStateTestContext
|
||||
$script:sync.ProcessRunning = $true
|
||||
$script:sync.Form = [pscustomobject]@{
|
||||
WindowState = [Windows.WindowState]::Normal
|
||||
}
|
||||
}
|
||||
|
||||
AfterEach {
|
||||
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
|
||||
Remove-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
It "maximizes a normal window" {
|
||||
Invoke-WPFButton -Button "WPFMaximizeButton"
|
||||
|
||||
$script:sync.Form.WindowState | Should -Be ([Windows.WindowState]::Maximized)
|
||||
}
|
||||
|
||||
It "restores a maximized window" {
|
||||
$script:sync.Form.WindowState = [Windows.WindowState]::Maximized
|
||||
|
||||
Invoke-WPFButton -Button "WPFMaximizeButton"
|
||||
|
||||
$script:sync.Form.WindowState | Should -Be ([Windows.WindowState]::Normal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@ BeforeAll {
|
||||
function Write-WinUtilLog {
|
||||
param($Message, $Level, $Component)
|
||||
}
|
||||
function Show-WinUtilMessage {
|
||||
param($Message, $Title, $Button, $Icon)
|
||||
}
|
||||
function Get-ScheduledTask {
|
||||
param($TaskPath)
|
||||
param($TaskPath, $ErrorAction)
|
||||
}
|
||||
function Disable-ScheduledTask {
|
||||
param(
|
||||
@@ -51,6 +54,7 @@ Describe "Invoke-WPFUpdatesdisable" {
|
||||
BeforeEach {
|
||||
Mock Write-Host { }
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Show-WinUtilMessage { "Yes" }
|
||||
Mock New-Item { }
|
||||
Mock Set-ItemProperty { }
|
||||
Mock Set-Service { }
|
||||
@@ -88,25 +92,23 @@ Describe "Invoke-WPFUpdatesdisable" {
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 0
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
|
||||
$Name -eq "SettingsPageVisibility" -and
|
||||
$Value -eq "hide:windowsupdate"
|
||||
}
|
||||
}
|
||||
|
||||
It "disables update services and clears the SoftwareDistribution folder" {
|
||||
Invoke-WPFUpdatesdisable
|
||||
|
||||
foreach ($expectedServiceName in @("BITS", "wuauserv", "UsoSvc")) {
|
||||
$expected = $expectedServiceName
|
||||
Should -Invoke -CommandName Stop-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq $expected -and $Force -eq $true
|
||||
}
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "BITS" -and $StartupType -eq "Disabled"
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "wuauserv" -and $StartupType -eq "Disabled"
|
||||
}
|
||||
Should -Invoke -CommandName Stop-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "UsoSvc" -and $Force -eq $true
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "UsoSvc" -and $StartupType -eq "Disabled"
|
||||
}
|
||||
@@ -128,6 +130,21 @@ Describe "Invoke-WPFUpdatesdisable" {
|
||||
}
|
||||
Should -Invoke -CommandName Disable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
|
||||
}
|
||||
|
||||
It "requires confirmation before disabling updates" {
|
||||
Mock Show-WinUtilMessage { "No" }
|
||||
|
||||
Invoke-WPFUpdatesdisable
|
||||
|
||||
Should -Invoke Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
|
||||
$Title -eq "Disable Windows Update?" -and
|
||||
$Button -eq "YesNo" -and
|
||||
$Icon -eq "Warning"
|
||||
}
|
||||
Should -Not -Invoke Set-ItemProperty
|
||||
Should -Not -Invoke Set-Service
|
||||
Should -Not -Invoke Remove-Item
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Invoke-WPFUpdatesdefault" {
|
||||
@@ -136,6 +153,11 @@ Describe "Invoke-WPFUpdatesdefault" {
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock Remove-Item { }
|
||||
Mock Remove-ItemProperty { }
|
||||
Mock Get-ItemProperty {
|
||||
[pscustomobject]@{
|
||||
SettingsPageVisibility = "hide:windowsupdate"
|
||||
}
|
||||
}
|
||||
Mock Set-Service { }
|
||||
Mock Start-Service { }
|
||||
Mock Get-ScheduledTask {
|
||||
@@ -147,30 +169,49 @@ Describe "Invoke-WPFUpdatesdefault" {
|
||||
Mock secedit { }
|
||||
}
|
||||
|
||||
It "removes update policy registry paths and shows the Windows Update settings page" {
|
||||
It "removes only registry values managed by WinUtil" {
|
||||
Invoke-WPFUpdatesdefault
|
||||
|
||||
$expectedPaths = @(
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU",
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization",
|
||||
"HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
|
||||
$expectedRegistryValues = @(
|
||||
@("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", "NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement"),
|
||||
@("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "ExcludeWUDriversInQualityUpdate", "DeferFeatureUpdates", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdates", "DeferQualityUpdatesPeriodInDays"),
|
||||
@("HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings", "BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays"),
|
||||
@("HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata", "PreventDeviceMetadataFromNetwork"),
|
||||
@("HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching", "DontPromptForWindowsUpdate", "DontSearchWindowsUpdate", "DriverUpdateWizardWuSearchEnabled"),
|
||||
@("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config", "DODownloadMode")
|
||||
)
|
||||
|
||||
foreach ($expectedRegistryPath in $expectedPaths) {
|
||||
$expected = $expectedRegistryPath
|
||||
Should -Invoke -CommandName Remove-Item -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq $expected -and $Recurse -eq $true -and $Force -eq $true
|
||||
foreach ($expectedEntry in $expectedRegistryValues) {
|
||||
$expectedPath = $expectedEntry[0]
|
||||
foreach ($expectedName in $expectedEntry[1..($expectedEntry.Count - 1)]) {
|
||||
$valueName = $expectedName
|
||||
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq $expectedPath -and $Name -eq $valueName
|
||||
}
|
||||
}
|
||||
}
|
||||
Should -Not -Invoke Remove-Item
|
||||
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
|
||||
$Name -eq "SettingsPageVisibility"
|
||||
}
|
||||
}
|
||||
|
||||
It "preserves unrelated Settings page visibility policy" {
|
||||
Mock Get-ItemProperty {
|
||||
[pscustomobject]@{
|
||||
SettingsPageVisibility = "hide:privacy"
|
||||
}
|
||||
}
|
||||
|
||||
Invoke-WPFUpdatesdefault
|
||||
|
||||
Should -Not -Invoke Remove-ItemProperty -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
|
||||
$Name -eq "SettingsPageVisibility"
|
||||
}
|
||||
}
|
||||
|
||||
It "restores update service startup types" {
|
||||
Invoke-WPFUpdatesdefault
|
||||
|
||||
@@ -186,12 +227,12 @@ Describe "Invoke-WPFUpdatesdefault" {
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "UsoSvc" -and $StartupType -eq "Automatic"
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "WaaSMedicSvc" -and $StartupType -eq "Manual"
|
||||
Should -Not -Invoke Set-Service -ParameterFilter {
|
||||
$Name -eq "WaaSMedicSvc"
|
||||
}
|
||||
}
|
||||
|
||||
It "enables update scheduled task paths and resets local policy defaults" {
|
||||
It "enables update scheduled task paths without resetting unrelated local security policy" {
|
||||
Invoke-WPFUpdatesdefault
|
||||
|
||||
foreach ($expectedTaskPath in $script:updateTaskPaths) {
|
||||
@@ -201,13 +242,7 @@ Describe "Invoke-WPFUpdatesdefault" {
|
||||
}
|
||||
}
|
||||
Should -Invoke -CommandName Enable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
|
||||
Should -Invoke -CommandName secedit -Times 1 -Exactly -ParameterFilter {
|
||||
$Arguments[0] -eq "/configure" -and
|
||||
$Arguments[1] -eq "/cfg" -and
|
||||
$Arguments[2] -like "*\inf\defltbase.inf" -and
|
||||
$Arguments[3] -eq "/db" -and
|
||||
$Arguments[4] -eq "defltbase.sdb"
|
||||
}
|
||||
Should -Not -Invoke secedit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +252,47 @@ Describe "Invoke-WPFUpdatessecurity" {
|
||||
Mock Write-WinUtilLog { }
|
||||
Mock New-Item { }
|
||||
Mock Set-ItemProperty { }
|
||||
Mock Remove-ItemProperty { }
|
||||
Mock Set-Service { }
|
||||
Mock Start-Service { }
|
||||
Mock Get-ScheduledTask {
|
||||
[pscustomobject]@{
|
||||
TaskPath = $TaskPath
|
||||
}
|
||||
}
|
||||
Mock Enable-ScheduledTask { }
|
||||
}
|
||||
|
||||
It "restores update availability before applying recommended settings" {
|
||||
Invoke-WPFUpdatessecurity
|
||||
|
||||
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
|
||||
$Name -eq "NoAutoUpdate"
|
||||
}
|
||||
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -and
|
||||
$Name -eq "DODownloadMode"
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "BITS" -and $StartupType -eq "Manual"
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "wuauserv" -and $StartupType -eq "Manual"
|
||||
}
|
||||
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "UsoSvc" -and $StartupType -eq "Automatic"
|
||||
}
|
||||
Should -Invoke -CommandName Start-Service -Times 1 -Exactly -ParameterFilter {
|
||||
$Name -eq "UsoSvc"
|
||||
}
|
||||
foreach ($expectedTaskPath in $script:updateTaskPaths) {
|
||||
$expected = $expectedTaskPath
|
||||
Should -Invoke -CommandName Get-ScheduledTask -Times 1 -Exactly -ParameterFilter {
|
||||
$TaskPath -eq $expected
|
||||
}
|
||||
}
|
||||
Should -Invoke -CommandName Enable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
|
||||
}
|
||||
|
||||
It "disables driver metadata and Windows Update driver search" {
|
||||
@@ -261,23 +337,35 @@ Describe "Invoke-WPFUpdatessecurity" {
|
||||
Invoke-WPFUpdatessecurity
|
||||
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
|
||||
$Name -eq "BranchReadinessLevel" -and
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
|
||||
$Name -eq "DeferFeatureUpdates" -and
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 20
|
||||
$Value -eq 1
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
|
||||
$Name -eq "DeferFeatureUpdatesPeriodInDays" -and
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 365
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
|
||||
$Name -eq "DeferQualityUpdates" -and
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 1
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
|
||||
$Name -eq "DeferQualityUpdatesPeriodInDays" -and
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 4
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
|
||||
$Name -eq "AUOptions" -and
|
||||
$Type -eq "DWord" -and
|
||||
$Value -eq 4
|
||||
}
|
||||
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
|
||||
$Name -eq "NoAutoRebootWithLoggedOnUsers" -and
|
||||
@@ -291,4 +379,16 @@ Describe "Invoke-WPFUpdatessecurity" {
|
||||
$Value -eq 0
|
||||
}
|
||||
}
|
||||
|
||||
It "removes legacy WinUtil deferral values from the unsupported UX settings path" {
|
||||
Invoke-WPFUpdatessecurity
|
||||
|
||||
foreach ($expectedValueName in @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")) {
|
||||
$expected = $expectedValueName
|
||||
Should -Invoke Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
|
||||
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
|
||||
$Name -eq $expected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ Describe "Win11 Creator setup media" {
|
||||
BeforeAll {
|
||||
$script:repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$script:isoWorkflowPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISO.ps1"
|
||||
$script:isoUsbWorkflowPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISOUSB.ps1"
|
||||
$script:isoScriptPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISOScript.ps1"
|
||||
$script:autoUnattendPath = Join-Path $script:repoRoot "tools\autounattend.xml"
|
||||
|
||||
@@ -36,6 +37,9 @@ Describe "Win11 Creator setup media" {
|
||||
}
|
||||
|
||||
$script:modifyFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOModify"
|
||||
$script:cleanAndResetFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOCleanAndReset"
|
||||
$script:exportFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOExport"
|
||||
$script:writeUsbFunction = Get-WinUtilFunctionText -Path $script:isoUsbWorkflowPath -FunctionName "Invoke-WinUtilISOWriteUSB"
|
||||
$script:editionIdFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Get-WinUtilEditionIdFromName"
|
||||
$script:addDriversFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Add-DriversToImage"
|
||||
$script:answerFileChildElementFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Get-WinUtilISOScriptChildElement"
|
||||
@@ -81,6 +85,18 @@ Describe "Win11 Creator setup media" {
|
||||
$script:modifyFunction | Should -Not -Match ([regex]::Escape("Reusing existing temp directory"))
|
||||
}
|
||||
|
||||
It "tracks every background ISO workflow with the shared busy state" {
|
||||
foreach ($functionText in @(
|
||||
$script:modifyFunction,
|
||||
$script:cleanAndResetFunction,
|
||||
$script:exportFunction,
|
||||
$script:writeUsbFunction
|
||||
)) {
|
||||
$functionText | Should -Match ([regex]::Escape('$sync["Win11ISOProcessRunning"] = $true'))
|
||||
$functionText | Should -Match ([regex]::Escape('$sync["Win11ISOProcessRunning"] = $false'))
|
||||
}
|
||||
}
|
||||
|
||||
It "mounts the copied image file that was verified from the ISO" {
|
||||
foreach ($expectedText in @(
|
||||
'$sourceImageFileName = Split-Path $wimPath -Leaf',
|
||||
|
||||
+126
-7
@@ -135,12 +135,23 @@ Describe "XAML document" {
|
||||
"WPFTab4BT",
|
||||
"WPFTab5BT",
|
||||
"SearchBar",
|
||||
"SearchBarIcon",
|
||||
"SearchBarClearButton",
|
||||
"WPFSearchChips",
|
||||
"WPFSearchChipAll",
|
||||
"WPFSearchChipBrowsers",
|
||||
"WPFSearchChipCommunications",
|
||||
"WPFSearchChipDevelopment",
|
||||
"WPFSearchChipGames",
|
||||
"WPFSearchChipMicrosoftTools",
|
||||
"WPFSearchChipMultimediaTools",
|
||||
"WPFSearchChipProTools",
|
||||
"WPFSearchChipSelfhostedTools",
|
||||
"WPFSearchChipUtilities",
|
||||
"appscategory",
|
||||
"appspanel",
|
||||
"tweakspanel",
|
||||
"featurespanel",
|
||||
"updatespanel",
|
||||
"appxpanel",
|
||||
"WPFstandard",
|
||||
"WPFminimal",
|
||||
@@ -158,6 +169,7 @@ Describe "XAML document" {
|
||||
"WPFSelectAllAppx",
|
||||
"WPFClearAppxSelection",
|
||||
"WPFBackToTweaks",
|
||||
"WPFInstallSelectedAppx",
|
||||
"WPFRemoveSelectedAppx"
|
||||
)
|
||||
|
||||
@@ -167,6 +179,23 @@ Describe "XAML document" {
|
||||
}
|
||||
}
|
||||
|
||||
It "presents the three Updates profiles with accurate action labels" {
|
||||
$updatesTab = $script:xaml.SelectSingleNode('//*[local-name()="TabItem"][@Name="WPFTab4"]')
|
||||
$profileGrid = $updatesTab.SelectSingleNode('.//*[local-name()="UniformGrid"]')
|
||||
$expectedButtons = @{
|
||||
WPFUpdatessecurity = "Apply Recommended"
|
||||
WPFUpdatesdefault = "Restore Defaults"
|
||||
WPFUpdatesdisable = "Disable Updates"
|
||||
}
|
||||
|
||||
$profileGrid.GetAttribute("Columns") | Should -Be "3"
|
||||
foreach ($buttonName in $expectedButtons.Keys) {
|
||||
$button = $updatesTab.SelectSingleNode(".//*[local-name()='Button'][@Name='$buttonName']")
|
||||
$button.GetAttribute("Content") | Should -Be $expectedButtons[$buttonName]
|
||||
}
|
||||
$updatesTab.SelectSingleNode('.//*[@Name="updatespanel"]') | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It "contains Win11 Creator controls used by the ISO workflow" {
|
||||
$xamlNames = @(Get-WinUtilXamlRuntimeNamedControls | ForEach-Object { $_.Name })
|
||||
$requiredControls = @(
|
||||
@@ -240,11 +269,106 @@ Describe "XAML document" {
|
||||
$openButton.GetAttribute("Content").Trim() | Should -Be "AppX Removal"
|
||||
$openAppxIndex | Should -Be ($getInstalledIndex + 1)
|
||||
$appxTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFBackToTweaks"]') | Should -Not -BeNullOrEmpty
|
||||
$appxTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFInstallSelectedAppx"]') | Should -Not -BeNullOrEmpty
|
||||
$appxTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFRemoveSelectedAppx"]') | Should -Not -BeNullOrEmpty
|
||||
$buttonSource | Should -Match '"WPFAppxRemoval"\s*\{Invoke-WPFTab "WPFTab6BT"\}'
|
||||
$buttonSource | Should -Match '"WPFBackToTweaks"\s*\{Invoke-WPFTab "WPFTab2BT"\}'
|
||||
$buttonSource | Should -Match '"WPFInstallSelectedAppx"\s*\{Invoke-WPFAppxInstall\}'
|
||||
$tabSource | Should -Match '\$sync\.\$tabNav\.Items\[\$tabNumber\]\.IsSelected = \$true'
|
||||
}
|
||||
|
||||
It "centers top bar controls vertically" {
|
||||
$navPanel = $script:xaml.SelectSingleNode('//*[local-name()="StackPanel"][@Name="NavDockPanel"]')
|
||||
$minimizeButton = $script:xaml.SelectSingleNode('//*[local-name()="Button"][@Name="WPFMinimizeButton"]')
|
||||
$actionPanel = $minimizeButton.ParentNode
|
||||
$topBarButtonNames = @(
|
||||
"ThemeButton",
|
||||
"FontScalingButton",
|
||||
"SettingsButton",
|
||||
"WPFMinimizeButton",
|
||||
"WPFMaximizeButton",
|
||||
"WPFCloseButton"
|
||||
)
|
||||
|
||||
$navPanel.GetAttribute("VerticalAlignment") | Should -Be "Center"
|
||||
$actionPanel.GetAttribute("VerticalAlignment") | Should -Be "Center"
|
||||
|
||||
foreach ($buttonName in $topBarButtonNames) {
|
||||
$button = $script:xaml.SelectSingleNode("//*[local-name()='Button'][@Name='$buttonName']")
|
||||
$button.GetAttribute("VerticalAlignment") | Should -Be "Center"
|
||||
}
|
||||
}
|
||||
|
||||
It "keeps the responsive search controls within the available screen width" {
|
||||
$window = $script:xaml.DocumentElement
|
||||
$searchBar = $script:xaml.SelectSingleNode('//*[local-name()="TextBox"][@Name="SearchBar"]')
|
||||
$searchBorder = $searchBar.ParentNode.ParentNode
|
||||
$mainScript = Get-Content -Path $script:mainScriptPath -Raw
|
||||
|
||||
$window.GetAttribute("MinWidth") | Should -Be "800"
|
||||
$searchBorder.GetAttribute("Width") | Should -BeNullOrEmpty
|
||||
$searchBorder.GetAttribute("HorizontalAlignment") | Should -Be "Stretch"
|
||||
$searchBar.GetAttribute("Width") | Should -BeNullOrEmpty
|
||||
$searchBar.GetAttribute("HorizontalAlignment") | Should -Be "Stretch"
|
||||
$mainScript | Should -Match '\$sync\.Form\.MinWidth = "1150"'
|
||||
$mainScript | Should -Match '\$sync\.Form\.MinWidth = \[Math\]::Min\(\[double\]\$sync\.Form\.MinWidth, \[double\]\$screenWidth\)'
|
||||
}
|
||||
|
||||
It "shows only one search action glyph at a time" {
|
||||
$searchIcon = $script:xaml.SelectSingleNode('//*[local-name()="TextBlock"][@Name="SearchBarIcon"]')
|
||||
$clearButton = $script:xaml.SelectSingleNode('//*[local-name()="Button"][@Name="SearchBarClearButton"]')
|
||||
$mainScript = Get-Content -Path $script:mainScriptPath -Raw
|
||||
|
||||
$searchIcon | Should -Not -BeNullOrEmpty
|
||||
$clearButton | Should -Not -BeNullOrEmpty
|
||||
$mainScript | Should -Match '\$sync\.SearchBarClearButton\.Visibility = "Visible"\s+\$sync\.SearchBarIcon\.Visibility = "Collapsed"'
|
||||
$mainScript | Should -Match '\$sync\.SearchBarClearButton\.Visibility = "Collapsed"\s+\$sync\.SearchBarIcon\.Visibility = "Visible"'
|
||||
}
|
||||
|
||||
It "scopes toggle button styles without leaking into combo boxes" {
|
||||
$resources = $script:xaml.SelectSingleNode('//*[local-name()="Window.Resources"]')
|
||||
$implicitToggleStyles = @($resources.SelectNodes('./*[local-name()="Style"][@TargetType="ToggleButton" or @TargetType="{x:Type ToggleButton}"][not(@x:Key)]', $script:xamlNamespace))
|
||||
$tabStyle = $resources.SelectSingleNode('./*[local-name()="Style"][@x:Key="TabToggleButton"]', $script:xamlNamespace)
|
||||
$comboToggleStyle = $resources.SelectSingleNode('./*[local-name()="Style"][@x:Key="ComboBoxToggleButtonStyle"]', $script:xamlNamespace)
|
||||
$comboStyle = $resources.SelectSingleNode('./*[local-name()="Style"][@TargetType="ComboBox"]')
|
||||
$comboToggle = $comboStyle.SelectSingleNode('.//*[local-name()="ToggleButton"][@Name="ToggleButton"]')
|
||||
$comboItemStyle = $resources.SelectSingleNode('./*[local-name()="Style"][@TargetType="ComboBoxItem"]')
|
||||
$navButtons = @($script:xaml.SelectNodes('//*[local-name()="StackPanel"][@Name="NavDockPanel"]/*[local-name()="ToggleButton"]'))
|
||||
|
||||
$implicitToggleStyles | Should -BeNullOrEmpty
|
||||
$tabStyle | Should -Not -BeNullOrEmpty
|
||||
$comboToggleStyle | Should -Not -BeNullOrEmpty
|
||||
$comboToggle.GetAttribute("Style") | Should -Be "{StaticResource ComboBoxToggleButtonStyle}"
|
||||
$comboItemStyle | Should -Not -BeNullOrEmpty
|
||||
$navButtons.Count | Should -Be 5
|
||||
foreach ($navButton in $navButtons) {
|
||||
$navButton.GetAttribute("Style") | Should -Be "{StaticResource TabToggleButton}"
|
||||
}
|
||||
}
|
||||
|
||||
It "uses state-aware maximize and restore icons" {
|
||||
$themes = Get-WinUtilConfigObject -Name "themes"
|
||||
$minimizeButton = $script:xaml.SelectSingleNode('//*[local-name()="Button"][@Name="WPFMinimizeButton"]')
|
||||
$maximizeButton = $script:xaml.SelectSingleNode('//*[local-name()="Button"][@Name="WPFMaximizeButton"]')
|
||||
$closeButton = $script:xaml.SelectSingleNode('//*[local-name()="Button"][@Name="WPFCloseButton"]')
|
||||
$maximizeStyle = $maximizeButton.SelectSingleNode('./*[local-name()="Button.Style"]/*[local-name()="Style"]')
|
||||
$maximizeIcon = $maximizeStyle.SelectSingleNode('./*[local-name()="Setter"][@Property="Content"]')
|
||||
$maximizedTrigger = $maximizeStyle.SelectSingleNode('./*[local-name()="Style.Triggers"]/*[local-name()="DataTrigger"][@Value="Maximized"]')
|
||||
$restoreIcon = $maximizedTrigger.SelectSingleNode('./*[local-name()="Setter"][@Property="Content"]')
|
||||
|
||||
foreach ($button in @($minimizeButton, $maximizeButton, $closeButton)) {
|
||||
$button.GetAttribute("FontFamily") | Should -Be "Segoe MDL2 Assets"
|
||||
$button.GetAttribute("FontSize") | Should -Be "{DynamicResource CloseIconFontSize}"
|
||||
$button.GetAttribute("Margin") | Should -BeIn @("0", "0,0,0,0")
|
||||
}
|
||||
|
||||
$minimizeButton.GetAttribute("Content") | Should -Be ([string][char]0xE921)
|
||||
$maximizeIcon.GetAttribute("Value") | Should -Be ([string][char]0xE922)
|
||||
$restoreIcon.GetAttribute("Value") | Should -Be ([string][char]0xE923)
|
||||
$closeButton.GetAttribute("Content") | Should -Be ([string][char]0xE8BB)
|
||||
$maximizeStyle.GetAttribute("BasedOn") | Should -Be "{StaticResource HoverButtonStyle}"
|
||||
[int]$themes.shared.CloseIconFontSize | Should -BeLessThan ([int]$themes.shared.SettingsIconFontSize)
|
||||
}
|
||||
}
|
||||
|
||||
Describe "XAML and sync wiring" {
|
||||
@@ -324,20 +448,15 @@ Describe "XAML and sync wiring" {
|
||||
"InitializedTabs",
|
||||
"RenderedAssetCache",
|
||||
"ToggleStatusCache",
|
||||
"InstallAppAreaBorder",
|
||||
"InstallAppAreaScrollViewer",
|
||||
"InstallAppAreaOverlay",
|
||||
"InstallAppAreaOverlayText",
|
||||
"InstallAppRenderQueue",
|
||||
"InstallAppEntriesRendered",
|
||||
"ProgressBar",
|
||||
"progressBarTextBlock",
|
||||
"FontScaleFactor",
|
||||
"Win11ISOImageInfo",
|
||||
"Win11ISODriveLetter",
|
||||
"Win11ISOWimPath",
|
||||
"Win11ISOImagePath",
|
||||
"Win11ISOModifying",
|
||||
"Win11ISOProcessRunning",
|
||||
"Win11ISOWorkDir",
|
||||
"Win11ISOContentsDir",
|
||||
"Win11ISOUSBDisks"
|
||||
|
||||
+27
-14
@@ -128,9 +128,6 @@ Invoke-WinutilThemeChange -theme $sync.preferences.theme
|
||||
$sync.InitializedTabs = @{}
|
||||
Initialize-WinUtilTabContent -TabName "Install"
|
||||
|
||||
# Future implementation: Add Windows Version to updates panel
|
||||
#Invoke-WPFUIElements -configVariable $sync.configs.updates -targetGridName "updatespanel" -columncount 1
|
||||
|
||||
#===========================================================================
|
||||
# Store Form Objects In PowerShell
|
||||
#===========================================================================
|
||||
@@ -171,15 +168,6 @@ $sync.keys | ForEach-Object {
|
||||
}
|
||||
}
|
||||
|
||||
if ($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "TextBlock") {
|
||||
if ($sync["$psitem"].Name.EndsWith("Link")) {
|
||||
$sync["$psitem"].Add_MouseUp({
|
||||
[System.Object]$Sender = $args[0]
|
||||
Start-Process $Sender.ToolTip -ErrorAction Stop
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,6 +273,7 @@ $sync["Form"].Add_ContentRendered({
|
||||
# Extract screen width and height for the primary monitor
|
||||
$screenWidth = $primaryScreen.Bounds.Width
|
||||
$screenHeight = $primaryScreen.Bounds.Height
|
||||
$sync.Form.MinWidth = [Math]::Min([double]$sync.Form.MinWidth, [double]$screenWidth)
|
||||
|
||||
# Compare with the primary monitor size
|
||||
if ($sync.Form.ActualWidth -gt $screenWidth -or $sync.Form.ActualHeight -gt $screenHeight) {
|
||||
@@ -340,7 +329,7 @@ $searchBarTimer.add_Tick({
|
||||
$searchBarTimer.Stop()
|
||||
switch ($sync.currentTab) {
|
||||
"Install" {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
|
||||
}
|
||||
"Tweaks" {
|
||||
Find-TweaksByNameOrDescription -SearchString $sync.SearchBar.Text
|
||||
@@ -351,21 +340,45 @@ $searchBarTimer.add_Tick({
|
||||
}
|
||||
})
|
||||
$sync["SearchBar"].Add_TextChanged({
|
||||
if ($sync.SearchBar.Tag -ne $sync.SearchBar.Text) {
|
||||
$sync.SearchBar.Tag = $null
|
||||
}
|
||||
|
||||
if ($sync.SearchBar.Text -ne "") {
|
||||
$sync.SearchBarClearButton.Visibility = "Visible"
|
||||
$sync.SearchBarIcon.Visibility = "Collapsed"
|
||||
} else {
|
||||
$sync.SearchBarClearButton.Visibility = "Collapsed"
|
||||
$sync.SearchBarIcon.Visibility = "Visible"
|
||||
}
|
||||
|
||||
# Category chip handlers apply their filter immediately.
|
||||
if ($sync.SearchBar.Tag -eq $sync.SearchBar.Text) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($searchBarTimer.IsEnabled) {
|
||||
$searchBarTimer.Stop()
|
||||
}
|
||||
$searchBarTimer.Start()
|
||||
})
|
||||
|
||||
# Quick Category Search Chips
|
||||
$sync["WPFSearchChipAll"].Add_Click({ Set-WinUtilAppCategoryFilter })
|
||||
$sync["WPFSearchChipBrowsers"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Browsers" })
|
||||
$sync["WPFSearchChipCommunications"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Communications" })
|
||||
$sync["WPFSearchChipDevelopment"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Development" })
|
||||
$sync["WPFSearchChipGames"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Games" })
|
||||
$sync["WPFSearchChipMicrosoftTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Microsoft Tools" })
|
||||
$sync["WPFSearchChipMultimediaTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Multimedia Tools" })
|
||||
$sync["WPFSearchChipProTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Pro Tools" })
|
||||
$sync["WPFSearchChipSelfhostedTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Selfhosted Tools" })
|
||||
$sync["WPFSearchChipUtilities"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Utilities" })
|
||||
|
||||
$sync["Form"].Add_Loaded({
|
||||
param($e)
|
||||
$null = $e
|
||||
$sync.Form.MinWidth = "1000"
|
||||
$sync.Form.MinWidth = "1150"
|
||||
$sync["Form"].MaxWidth = [Double]::PositiveInfinity
|
||||
$sync["Form"].MaxHeight = [Double]::PositiveInfinity
|
||||
})
|
||||
|
||||
@@ -62,6 +62,7 @@ $sync.configs = @{}
|
||||
$sync.Buttons = [System.Collections.Generic.List[PSObject]]::new()
|
||||
$sync.preferences = @{}
|
||||
$sync.ProcessRunning = $false
|
||||
$sync.Win11ISOProcessRunning = $false
|
||||
$sync.selectedAppx = [System.Collections.Generic.List[string]]::new()
|
||||
$sync.selectedApps = [System.Collections.Generic.List[string]]::new()
|
||||
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
+276
-114
@@ -248,6 +248,17 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="ComboBoxToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="{TemplateBinding Background}" BorderThickness="0">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}" />
|
||||
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}" />
|
||||
@@ -262,6 +273,7 @@
|
||||
CornerRadius="{DynamicResource ButtonCornerRadius}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<ToggleButton Name="ToggleButton"
|
||||
Style="{StaticResource ComboBoxToggleButtonStyle}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
@@ -309,6 +321,27 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundColor}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundColor}"/>
|
||||
<Setter Property="Padding" Value="6,3"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}" Background="Transparent"
|
||||
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ComboBoxItem}}"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundMouseoverColor}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundSelectedColor}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style TargetType="Label">
|
||||
<Setter Property="Foreground" Value="{DynamicResource LabelboxForegroundColor}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource LabelBackgroundColor}"/>
|
||||
@@ -321,8 +354,7 @@
|
||||
<Setter Property="Foreground" Value="{DynamicResource LabelboxForegroundColor}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource LabelBackgroundColor}"/>
|
||||
</Style>
|
||||
<!-- Toggle button template x:Key="TabToggleButton" -->
|
||||
<Style TargetType="{x:Type ToggleButton}">
|
||||
<Style x:Key="TabToggleButton" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Margin" Value="{DynamicResource ButtonMargin}"/>
|
||||
<Setter Property="Content" Value=""/>
|
||||
<Setter Property="FontFamily" Value="{DynamicResource FontFamily}"/>
|
||||
@@ -918,12 +950,27 @@
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="RoundedProgressBarStyle" TargetType="ProgressBar">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ProgressBar">
|
||||
<Border CornerRadius="4" Background="{DynamicResource MainBackgroundColor}" BorderBrush="{DynamicResource MainForegroundColor}" BorderThickness="1">
|
||||
<Grid ClipToBounds="True">
|
||||
<Border Name="PART_Track" CornerRadius="4" Background="Transparent"/>
|
||||
<Border Name="PART_Indicator" CornerRadius="4" Background="{DynamicResource ProgressBarForegroundColor}" HorizontalAlignment="Left"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource MainBackgroundColor}" ShowGridLines="False" Name="WPFMainGrid" Width="Auto" Height="Auto" HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -940,10 +987,10 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Navigation Buttons Panel -->
|
||||
<StackPanel Name="NavDockPanel" Orientation="Horizontal" Grid.Column="0" Margin="5,5,10,5">
|
||||
<StackPanel Name="NavDockPanel" Orientation="Horizontal" Grid.Column="0" VerticalAlignment="Center" Margin="5,5,10,5">
|
||||
<StackPanel Name="NavLogoPanel" Orientation="Horizontal" HorizontalAlignment="Left" Background="{DynamicResource MainBackgroundColor}" SnapsToDevicePixels="True" Margin="10,0,20,0">
|
||||
</StackPanel>
|
||||
<ToggleButton Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
<ToggleButton Style="{StaticResource TabToggleButton}" Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
Background="{DynamicResource ButtonInstallBackgroundColor}" Foreground="white" FontWeight="Bold" Name="WPFTab1BT">
|
||||
<ToggleButton.Content>
|
||||
<TextBlock FontSize="{DynamicResource TabButtonFontSize}" Background="Transparent" Foreground="{DynamicResource ButtonInstallForegroundColor}" >
|
||||
@@ -951,7 +998,7 @@
|
||||
</TextBlock>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<ToggleButton Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
<ToggleButton Style="{StaticResource TabToggleButton}" Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
Background="{DynamicResource ButtonTweaksBackgroundColor}" Foreground="{DynamicResource ButtonTweaksForegroundColor}" FontWeight="Bold" Name="WPFTab2BT">
|
||||
<ToggleButton.Content>
|
||||
<TextBlock FontSize="{DynamicResource TabButtonFontSize}" Background="Transparent" Foreground="{DynamicResource ButtonTweaksForegroundColor}">
|
||||
@@ -959,7 +1006,7 @@
|
||||
</TextBlock>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<ToggleButton Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
<ToggleButton Style="{StaticResource TabToggleButton}" Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
Background="{DynamicResource ButtonConfigBackgroundColor}" Foreground="{DynamicResource ButtonConfigForegroundColor}" FontWeight="Bold" Name="WPFTab3BT">
|
||||
<ToggleButton.Content>
|
||||
<TextBlock FontSize="{DynamicResource TabButtonFontSize}" Background="Transparent" Foreground="{DynamicResource ButtonConfigForegroundColor}">
|
||||
@@ -967,7 +1014,7 @@
|
||||
</TextBlock>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<ToggleButton Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
<ToggleButton Style="{StaticResource TabToggleButton}" Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="{DynamicResource TabButtonWidth}"
|
||||
Background="{DynamicResource ButtonUpdatesBackgroundColor}" Foreground="{DynamicResource ButtonUpdatesForegroundColor}" FontWeight="Bold" Name="WPFTab4BT">
|
||||
<ToggleButton.Content>
|
||||
<TextBlock FontSize="{DynamicResource TabButtonFontSize}" Background="Transparent" Foreground="{DynamicResource ButtonUpdatesForegroundColor}">
|
||||
@@ -975,7 +1022,7 @@
|
||||
</TextBlock>
|
||||
</ToggleButton.Content>
|
||||
</ToggleButton>
|
||||
<ToggleButton Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="Auto" MinWidth="{DynamicResource TabButtonWidth}"
|
||||
<ToggleButton Style="{StaticResource TabToggleButton}" Margin="0,0,5,0" Height="{DynamicResource TabButtonHeight}" Width="Auto" MinWidth="{DynamicResource TabButtonWidth}"
|
||||
Background="{DynamicResource ButtonWin11ISOBackgroundColor}" Foreground="{DynamicResource ButtonWin11ISOForegroundColor}" FontWeight="Bold" Name="WPFTab5BT">
|
||||
<ToggleButton.Content>
|
||||
<TextBlock FontSize="{DynamicResource TabButtonFontSize}" Background="Transparent" Foreground="{DynamicResource ButtonWin11ISOForegroundColor}">
|
||||
@@ -992,13 +1039,12 @@
|
||||
<ColumnDefinition Width="Auto"/><!-- Buttons area -->
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0" Margin="5,0,0,0" Width="{DynamicResource SearchBarWidth}" Height="{DynamicResource SearchBarHeight}" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||
<Border Grid.Column="0" Margin="5,0,10,0" MinWidth="120" Height="{DynamicResource SearchBarHeight}" VerticalAlignment="Center" HorizontalAlignment="Stretch">
|
||||
<Grid>
|
||||
<TextBox
|
||||
Width="{DynamicResource SearchBarWidth}"
|
||||
Height="{DynamicResource SearchBarHeight}"
|
||||
FontSize="{DynamicResource SearchBarTextBoxFontSize}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
||||
BorderThickness="1"
|
||||
Name="SearchBar"
|
||||
Foreground="{DynamicResource MainForegroundColor}" Background="{DynamicResource MainBackgroundColor}"
|
||||
@@ -1006,6 +1052,7 @@
|
||||
ToolTip="Press Ctrl-F and type app name to filter application list below. Press Esc to reset the filter">
|
||||
</TextBox>
|
||||
<TextBlock
|
||||
Name="SearchBarIcon"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Foreground="{DynamicResource ButtonBackgroundSelectedColor}"
|
||||
@@ -1015,14 +1062,14 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
<Button Grid.Column="0"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Right"
|
||||
Name="SearchBarClearButton"
|
||||
Style="{StaticResource SearchBarClearButtonStyle}"
|
||||
Margin="213,0,0,0" Visibility="Collapsed">
|
||||
Margin="0,0,20,0" Visibility="Collapsed">
|
||||
</Button>
|
||||
|
||||
<!-- Buttons Container -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top" Margin="5,5,5,5">
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,5,5,5">
|
||||
<Button Name="ThemeButton"
|
||||
Style="{StaticResource HoverButtonStyle}"
|
||||
BorderBrush="Transparent"
|
||||
@@ -1030,7 +1077,7 @@
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource SettingsIconFontSize}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,2,0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Content="N/A"
|
||||
@@ -1068,7 +1115,7 @@
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource SettingsIconFontSize}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,2,0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Content=""
|
||||
@@ -1136,7 +1183,7 @@
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource SettingsIconFontSize}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,2,0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Content=""/>
|
||||
@@ -1165,24 +1212,62 @@
|
||||
</Popup>
|
||||
|
||||
<Button
|
||||
Content="−" BorderThickness="0"
|
||||
BorderBrush="Transparent"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,0,0,0"
|
||||
FontFamily="{DynamicResource FontFamily}"
|
||||
Foreground="{DynamicResource MainForegroundColor}" FontSize="{DynamicResource CloseIconFontSize}" Name="WPFMinimizeButton" />
|
||||
Content=""
|
||||
Style="{StaticResource HoverButtonStyle}"
|
||||
BorderThickness="0"
|
||||
BorderBrush="Transparent"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource CloseIconFontSize}"
|
||||
ToolTip="Minimize"
|
||||
AutomationProperties.Name="Minimize"
|
||||
Name="WPFMinimizeButton" />
|
||||
<Button
|
||||
BorderThickness="0"
|
||||
BorderBrush="Transparent"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0,0,0,0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource CloseIconFontSize}"
|
||||
Name="WPFMaximizeButton">
|
||||
<Button.Style>
|
||||
<Style TargetType="Button" BasedOn="{StaticResource HoverButtonStyle}">
|
||||
<Setter Property="Content" Value=""/>
|
||||
<Setter Property="ToolTip" Value="Maximize"/>
|
||||
<Setter Property="AutomationProperties.Name" Value="Maximize"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding WindowState, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Value="Maximized">
|
||||
<Setter Property="Content" Value=""/>
|
||||
<Setter Property="ToolTip" Value="Restore"/>
|
||||
<Setter Property="AutomationProperties.Name" Value="Restore"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
Content="×" BorderThickness="0"
|
||||
BorderBrush="Transparent"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Margin="0,0,0,0"
|
||||
FontFamily="{DynamicResource FontFamily}"
|
||||
Foreground="{DynamicResource MainForegroundColor}" FontSize="{DynamicResource CloseIconFontSize}" Name="WPFCloseButton" />
|
||||
Content=""
|
||||
Style="{StaticResource HoverButtonStyle}"
|
||||
BorderThickness="0"
|
||||
BorderBrush="Transparent"
|
||||
Background="{DynamicResource MainBackgroundColor}"
|
||||
Width="{DynamicResource IconButtonSize}" Height="{DynamicResource IconButtonSize}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Foreground="{DynamicResource MainForegroundColor}"
|
||||
FontSize="{DynamicResource CloseIconFontSize}"
|
||||
ToolTip="Close"
|
||||
AutomationProperties.Name="Close"
|
||||
Name="WPFCloseButton" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -1190,8 +1275,26 @@
|
||||
<TabControl Name="WPFTabNav" Background="Transparent" Width="Auto" Height="Auto" BorderBrush="Transparent" BorderThickness="0" Grid.Row="2" Grid.Column="0" Padding="-1">
|
||||
<TabItem Header="Install" Visibility="Collapsed" Name="WPFTab1">
|
||||
<Grid Background="Transparent" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="0" Margin="{DynamicResource TabContentMargin}">
|
||||
<!-- Quick Category Search Chips -->
|
||||
<WrapPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5" Name="WPFSearchChips">
|
||||
<Button Name="WPFSearchChipAll" Content="All" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipBrowsers" Content="Browsers" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipCommunications" Content="Communications" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipDevelopment" Content="Development" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipGames" Content="Games" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipMicrosoftTools" Content="Microsoft Tools" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipMultimediaTools" Content="Multimedia Tools" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipProTools" Content="Pro Tools" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipSelfhostedTools" Content="Selfhosted Tools" Width="Auto" Height="Auto" Margin="2"/>
|
||||
<Button Name="WPFSearchChipUtilities" Content="Utilities" Width="Auto" Height="Auto" Margin="2"/>
|
||||
</WrapPanel>
|
||||
|
||||
<Grid Grid.Row="1" Margin="{DynamicResource TabContentMargin}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
@@ -1263,99 +1366,148 @@
|
||||
</TabItem>
|
||||
<TabItem Header="Updates" Visibility="Collapsed" Name="WPFTab4">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="{DynamicResource TabContentMargin}">
|
||||
<Grid Background="Transparent" MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ScrollViewer}}">
|
||||
<Grid Background="Transparent" MaxWidth="1250" HorizontalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/> <!-- Row for the 3 columns -->
|
||||
<RowDefinition Height="Auto"/> <!-- Row for Windows Version -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Three columns container -->
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="10,10,10,14">
|
||||
<TextBlock Text="Windows Update Profiles"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Choose how Windows receives updates. Each profile replaces the Windows Update settings managed by WinUtil."
|
||||
Margin="0,6,0,0"
|
||||
FontSize="13"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Default Settings -->
|
||||
<Border Grid.Column="0" Style="{StaticResource BorderStyle}">
|
||||
<StackPanel>
|
||||
<Button Name="WPFUpdatesdefault"
|
||||
FontSize="{DynamicResource ConfigTabButtonFontSize}"
|
||||
Content="Default Settings"
|
||||
Margin="10,5"
|
||||
Padding="10"/>
|
||||
<TextBlock Margin="10"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}">
|
||||
<Run FontWeight="Bold">Default Windows Update Configuration</Run>
|
||||
<LineBreak/>
|
||||
- No modifications to Windows defaults
|
||||
<LineBreak/>
|
||||
- Removes any custom update settings
|
||||
<LineBreak/><LineBreak/>
|
||||
<Run FontStyle="Italic" FontSize="11">Note: This resets your Windows Update settings to default out of the box settings. It removes ANY policy or customization that has been done to Windows Update.</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Security Settings -->
|
||||
<Border Grid.Column="1" Style="{StaticResource BorderStyle}">
|
||||
<StackPanel>
|
||||
<UniformGrid Grid.Row="1" Columns="3">
|
||||
<Border Style="{StaticResource BorderStyle}"
|
||||
BorderBrush="{DynamicResource ProgressBarForegroundColor}"
|
||||
BorderThickness="2"
|
||||
Padding="16"
|
||||
MinHeight="300">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,14">
|
||||
<TextBlock Text="Recommended"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Balanced security and stability"
|
||||
Margin="0,4,0,0"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1">
|
||||
<TextBlock Text="- Defers feature updates for 365 days" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Defers quality updates for 4 days" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Excludes drivers from quality updates" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Prevents automatic restarts while a user is signed in" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Available on Windows Pro, Enterprise, and Education editions."
|
||||
FontSize="11"
|
||||
FontStyle="Italic"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</StackPanel>
|
||||
<Button Name="WPFUpdatessecurity"
|
||||
Grid.Row="2"
|
||||
Content="Apply Recommended"
|
||||
FontSize="{DynamicResource ConfigTabButtonFontSize}"
|
||||
Content="Security Settings"
|
||||
Margin="10,5"
|
||||
Margin="0,16,0,0"
|
||||
Padding="10"/>
|
||||
<TextBlock Margin="10"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}">
|
||||
<Run FontWeight="Bold">Balanced Security Configuration</Run>
|
||||
<LineBreak/>
|
||||
- Feature updates delayed by 365 days
|
||||
<LineBreak/>
|
||||
- Security updates installed after 4 days
|
||||
<LineBreak/>
|
||||
- Prevents Windows Update from installing drivers
|
||||
<LineBreak/><LineBreak/>
|
||||
<Run FontWeight="SemiBold">Feature Updates:</Run> New features and potential bugs
|
||||
<LineBreak/>
|
||||
<Run FontWeight="SemiBold">Security Updates:</Run> Critical security patches
|
||||
<LineBreak/><LineBreak/>
|
||||
<Run FontStyle="Italic" FontSize="11">Note: This only applies to Pro systems that can use group policy.</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Disable Updates -->
|
||||
<Border Grid.Column="2" Style="{StaticResource BorderStyle}">
|
||||
<StackPanel>
|
||||
<Border Style="{StaticResource BorderStyle}" Padding="16" MinHeight="300">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,14">
|
||||
<TextBlock Text="Windows Default"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Return control to Windows"
|
||||
Margin="0,4,0,0"
|
||||
FontSize="13"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1">
|
||||
<TextBlock Text="- Removes Windows Update policies applied by WinUtil" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Restores update service startup settings" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Re-enables update scheduled tasks" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Use this to undo the Recommended or Disable profile."
|
||||
FontSize="11"
|
||||
FontStyle="Italic"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</StackPanel>
|
||||
<Button Name="WPFUpdatesdefault"
|
||||
Grid.Row="2"
|
||||
Content="Restore Defaults"
|
||||
FontSize="{DynamicResource ConfigTabButtonFontSize}"
|
||||
Margin="0,16,0,0"
|
||||
Padding="10"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource BorderStyle}" Padding="16" MinHeight="300">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Margin="0,0,0,14">
|
||||
<TextBlock Text="Disable Updates"
|
||||
FontSize="20"
|
||||
FontWeight="Bold"
|
||||
Foreground="Red"/>
|
||||
<TextBlock Text="Advanced use only"
|
||||
Margin="0,4,0,0"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="Red"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1">
|
||||
<TextBlock Text="- Disables automatic update policy" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Stops update services and scheduled tasks" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="- Clears downloaded update files" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
<TextBlock Text="Security updates will not be installed while this profile is active."
|
||||
FontSize="11"
|
||||
FontStyle="Italic"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="Red"/>
|
||||
</StackPanel>
|
||||
<Button Name="WPFUpdatesdisable"
|
||||
Grid.Row="2"
|
||||
Content="Disable Updates"
|
||||
FontSize="{DynamicResource ConfigTabButtonFontSize}"
|
||||
Content="Disable All Updates"
|
||||
Foreground="Red"
|
||||
Margin="10,5"
|
||||
Margin="0,16,0,0"
|
||||
Padding="10"/>
|
||||
<TextBlock Margin="10"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="{DynamicResource MainForegroundColor}">
|
||||
<Run FontWeight="Bold" Foreground="Red">!! Not Recommended !!</Run>
|
||||
<LineBreak/>
|
||||
- Disables ALL Windows Updates
|
||||
<LineBreak/>
|
||||
- Increases security risks
|
||||
<LineBreak/>
|
||||
- Only use for isolated systems
|
||||
<LineBreak/><LineBreak/>
|
||||
<Run FontStyle="Italic" FontSize="11">Warning: Your system will be vulnerable without security updates.</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UniformGrid>
|
||||
|
||||
<!-- Future Implementation: Add Windows Version to updates panel -->
|
||||
<Grid Name="updatespanel" Grid.Row="1" Background="Transparent">
|
||||
</Grid>
|
||||
<Border Grid.Row="2" Style="{StaticResource BorderStyle}" Margin="8,14,8,8" Padding="12">
|
||||
<TextBlock Text="Changes apply system-wide. Restart Windows after switching profiles. Use Restore Defaults to undo WinUtil update policies."
|
||||
TextWrapping="Wrap"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource MainForegroundColor}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
@@ -1700,8 +1852,9 @@
|
||||
<Border Grid.Row="2" Style="{StaticResource BorderStyle}" Margin="5,15,5,5">
|
||||
<StackPanel Background="{DynamicResource MainBackgroundColor}" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock Padding="10" TextWrapping="Wrap" Foreground="{DynamicResource MainForegroundColor}">
|
||||
Note: Select the pre-installed Windows AppX packages you wish to remove and click 'Remove Selected'.
|
||||
<LineBreak/>These packages are removed for the current user and all new user profiles.
|
||||
Note: Select the Windows AppX packages you wish to install or remove.
|
||||
<LineBreak/>Install Selected registers a local manifest when available, then falls back to the Microsoft Store.
|
||||
<LineBreak/>Remove Selected removes packages for the current user and all new user profiles.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
@@ -1711,11 +1864,20 @@
|
||||
<Border Grid.Row="1" Background="{DynamicResource MainBackgroundColor}" BorderBrush="{DynamicResource BorderColor}" BorderThickness="1" CornerRadius="5" HorizontalAlignment="Stretch" Padding="10">
|
||||
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<Button Name="WPFBackToTweaks" Content="Back to Tweaks" Margin="5" Width="{DynamicResource ButtonWidth}" Height="{DynamicResource ButtonHeight}"/>
|
||||
<Button Name="WPFInstallSelectedAppx" Content="Install Selected" Margin="5" Width="{DynamicResource ButtonWidth}" Height="{DynamicResource ButtonHeight}"/>
|
||||
<Button Name="WPFRemoveSelectedAppx" Content="Remove Selected" Margin="5" Width="{DynamicResource ButtonWidth}" Height="{DynamicResource ButtonHeight}"/>
|
||||
</WrapPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
|
||||
<!-- Window-level progress indicator - visible regardless of active tab -->
|
||||
<Border Name="WPFTweaksProgressBar" Grid.Row="3" Background="{DynamicResource MainBackgroundColor}" Visibility="Collapsed" Padding="10,6">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Name="WPFTweaksProgressLabel" Text="" Foreground="{DynamicResource MainForegroundColor}" FontSize="13" Background="Transparent" Margin="0,0,0,4"/>
|
||||
<ProgressBar Name="WPFTweaksProgressValue" Height="6" Minimum="0" Maximum="100" Value="0" Style="{StaticResource RoundedProgressBarStyle}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
Reference in New Issue
Block a user