winutil/scripts/main.ps1
Chris Titus 771c268130
Test 2023 12 20 (#1337)
* Update winutil.ps1

* [Fix]: MicroWin finds but fails to use system oscdimg (#1298)

* Update winutil.ps1 (#1295)

* [Fix]: MicroWin finds but fails to use system oscdimg

* Compiled change b740693

---------

Co-authored-by: Chris Titus <contact@christitus.com>

* Fix wrong hash verification for oscdimg (#1301)

* Update winutil.ps1 (#1295)

* Fixed wrong hash verification for oscdimg

---------

Co-authored-by: Chris Titus <contact@christitus.com>

* Fix Winget package for Chromium and add Ungoogled-Chromium (#1306)

* Fix Chocolatey package for Chromium, add Ungoogled-Chromium

The Chocolatey package for Chromium was previously the one for Ungoogled-Chromium instead, I fixed it and added Ungoogled-Chromium as a separate package instead.

* Add ungoogled-chromium to inputXML

* Making Install section fully data driven (#1326)

Co-authored-by: KonTy <KonTy@github.com>

* Update README for India blocking (#1336)

* Fixed a couple of MicroWin issues (#1313)

* Update winutil.ps1 (#1295)

* Update MicroWin-Helper.ps1

Fix AppX package issue

* Update winutil.ps1

Fixed AppX package removal issue

* Added error detection to AppX removal

Adds error detection to the AppX package removal function in case the process of getting applications fails

This change might fix issue #1324

* Force DISM commands to be in English

Fixes an issue where MicroWin processing would fail on hosts with languages other than English.

This is because, by default, DISM uses the system language. By passing the /English flag, we're forcing DISM to be in English

---------

Co-authored-by: Chris Titus <contact@christitus.com>

* fix app display to be alphabetical

---------

Co-authored-by: kian yamamoto <kianjyamamoto@gmail.com>
Co-authored-by: Júlio C. Oliveira <braxkan@gmail.com>
Co-authored-by: AlbydS <119180144+AlbydST@users.noreply.github.com>
Co-authored-by: KonTy <9524513+KonTy@users.noreply.github.com>
Co-authored-by: KonTy <KonTy@github.com>
Co-authored-by: Munkk <152475628+munkk01@users.noreply.github.com>
Co-authored-by: CodingWonders <101426328+CodingWonders@users.noreply.github.com>
2024-01-02 15:45:06 -06:00

377 lines
13 KiB
PowerShell

# SPDX-License-Identifier: MIT
# Set the maximum number of threads for the RunspacePool to the number of threads on the machine
$maxthreads = [int]$env:NUMBER_OF_PROCESSORS
# Create a new session state for parsing variables into our runspace
$hashVars = New-object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList 'sync',$sync,$Null
$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
# Add the variable to the session state
$InitialSessionState.Variables.Add($hashVars)
# Get every private function and add them to the session state
$functions = Get-ChildItem function:\ | Where-Object {$_.name -like "*winutil*" -or $_.name -like "*WPF*"}
foreach ($function in $functions){
$functionDefinition = Get-Content function:\$($function.name)
$functionEntry = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $($function.name), $functionDefinition
$initialSessionState.Commands.Add($functionEntry)
}
# Create the runspace pool
$sync.runspace = [runspacefactory]::CreateRunspacePool(
1, # Minimum thread count
$maxthreads, # Maximum thread count
$InitialSessionState, # Initial session state
$Host # Machine to create runspaces on
)
# Open the RunspacePool instance
$sync.runspace.Open()
# Create classes for different exceptions
class WingetFailedInstall : Exception {
[string] $additionalData
WingetFailedInstall($Message) : base($Message) {}
}
class ChocoFailedInstall : Exception {
[string] $additionalData
ChocoFailedInstall($Message) : base($Message) {}
}
class GenericException : Exception {
[string] $additionalData
GenericException($Message) : base($Message) {}
}
$inputXML = $inputXML -replace 'mc:Ignorable="d"', '' -replace "x:N", 'N' -replace '^<Win.*', '<Window'
$organizedData = @{}
# Iterate through JSON data and organize by panel and category
foreach ($appName in $sync.configs.applications.PSObject.Properties.Name) {
$appInfo = $sync.configs.applications.$appName
# Create an object for the application
$appObject = [PSCustomObject]@{
Name = $appName
Category = $appInfo.Category
Content = $appInfo.Content
Choco = $appInfo.choco
Winget = $appInfo.winget
Panel = $appInfo.panel
}
if (-not $organizedData.ContainsKey($appInfo.panel)) {
$organizedData[$appInfo.panel] = @{}
}
if (-not $organizedData[$appInfo.panel].ContainsKey($appInfo.Category)) {
$organizedData[$appInfo.panel][$appInfo.Category] = @{}
}
# Store application data in a sub-array under the category
$organizedData[$appInfo.panel][$appInfo.Category][$appName] = $appObject
}
# Iterate through organizedData by panel, category, and application
foreach ($panel in $organizedData.Keys) {
foreach ($category in $organizedData[$panel].Keys) {
$blockXml += "<Label Content=""$($category)"" FontSize=""16""/>`n"
$sortedApps = $organizedData[$panel][$category].Keys | Sort-Object
foreach ($appName in $sortedApps) {
$appInfo = $organizedData[$panel][$category][$appName]
$blockXml += "<CheckBox Name=""$appName"" Content=""$($appInfo.Content)""/>`n"
}
}
$inputXML = $inputXML -replace "{{InstallPanel$panel}}", $blockXml
$blockXml = ""
}
if ((Get-WinUtilToggleStatus WPFToggleDarkMode) -eq $True) {
$ctttheme = 'Matrix'
}
else {
$ctttheme = 'Classic'
}
$inputXML = Set-WinUtilUITheme -inputXML $inputXML -themeName $ctttheme
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$XAML = $inputXML
# Read the XAML file
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
try { $sync["Form"] = [Windows.Markup.XamlReader]::Load( $reader ) }
catch [System.Management.Automation.MethodInvocationException] {
Write-Warning "We ran into a problem with the XAML code. Check the syntax for this control..."
Write-Host $error[0].Exception.Message -ForegroundColor Red
If ($error[0].Exception.Message -like "*button*") {
write-warning "Ensure your &lt;button in the `$inputXML does NOT have a Click=ButtonClick property. PS can't handle this`n`n`n`n"
}
}
catch {
Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."
}
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$xaml.SelectNodes("//*[@Name]") | ForEach-Object {$sync["$("$($psitem.Name)")"] = $sync["Form"].FindName($psitem.Name)}
$sync.keys | ForEach-Object {
if($sync.$psitem){
if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "Button"){
$sync["$psitem"].Add_Click({
[System.Object]$Sender = $args[0]
Invoke-WPFButton $Sender.name
})
}
}
}
$sync.keys | ForEach-Object {
if($sync.$psitem){
if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "ToggleButton"){
$sync["$psitem"].Add_Click({
[System.Object]$Sender = $args[0]
Invoke-WPFButton $Sender.name
})
}
}
}
$sync.keys | ForEach-Object {
if($sync.$psitem){
if(
$($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "CheckBox" `
-and $sync["$psitem"].Name -like "WPFToggle*"
){
$sync["$psitem"].IsChecked = Get-WinUtilToggleStatus $sync["$psitem"].Name
$sync["$psitem"].Add_Click({
[System.Object]$Sender = $args[0]
Invoke-WPFToggle $Sender.name
})
}
}
}
#===========================================================================
# Setup background config
#===========================================================================
# Load computer information in the background
Invoke-WPFRunspace -ScriptBlock {
$sync.ConfigLoaded = $False
$sync.ComputerInfo = Get-ComputerInfo
$sync.ConfigLoaded = $True
} | Out-Null
#===========================================================================
# Setup and Show the Form
#===========================================================================
# Print the logo
Invoke-WPFFormVariables
# Check if Chocolatey is installed
Install-WinUtilChoco
# Set the titlebar
$sync["Form"].title = $sync["Form"].title + " " + $sync.version
# Set the commands that will run when the form is closed
$sync["Form"].Add_Closing({
$sync.runspace.Dispose()
$sync.runspace.Close()
[System.GC]::Collect()
})
# add some shortcuts for people that don't like clicking
$commonKeyEvents = {
if ($sync.ProcessRunning -eq $true) {
return
}
# Escape removes focus from the searchbox that way all shortcuts will start workinf again
if ($_.Key -eq "Escape") {
#if ($sync.CheckboxFilter.IsFocused)
{
$sync.CheckboxFilter.SelectAll()
$sync.CheckboxFilter.Text = ""
$sync.CheckboxFilter.Focus()
return
}
}
# don't ask, I know what I'm doing, just go...
if (($_.Key -eq "Q" -and $_.KeyboardDevice.Modifiers -eq "Ctrl"))
{
$this.Close()
}
# $ret = [System.Windows.Forms.MessageBox]::Show("Are you sure you want to Exit?", "Winutil", [System.Windows.Forms.MessageBoxButtons]::YesNo,
# [System.Windows.Forms.MessageBoxIcon]::Question, [System.Windows.Forms.MessageBoxDefaultButton]::Button2)
# switch ($ret) {
# "Yes" {
# $this.Close()
# }
# "No" {
# return
# }
# }
if ($_.KeyboardDevice.Modifiers -eq "Alt") {
# this is an example how to handle shortcuts per tab
# Alt-I on the MicroWin tab (4) would press GetIso Button
# NOTE: All per tab shortcuts have to be handled *before* regular tab keys
# if ($_.SystemKey -eq "I") {
# $TabNav = Get-WinUtilVariables | Where-Object {$psitem -like "WPFTabNav"}
# if ($sync.$TabNav.Items[4].IsSelected -eq $true) {
# Invoke-WPFButton "WPFGetIso"
# break
# }
# }
if ($_.SystemKey -eq "I") {
Invoke-WPFButton "WPFTab1BT"
}
if ($_.SystemKey -eq "T") {
Invoke-WPFButton "WPFTab2BT"
}
if ($_.SystemKey -eq "C") {
Invoke-WPFButton "WPFTab3BT"
}
if ($_.SystemKey -eq "U") {
Invoke-WPFButton "WPFTab4BT"
}
if ($_.SystemKey -eq "M") {
Invoke-WPFButton "WPFTab5BT"
}
}
# shortcut for the filter box
if ($_.Key -eq "F" -and $_.KeyboardDevice.Modifiers -eq "Ctrl") {
if ($sync.CheckboxFilter.Text -eq "Ctrl-F to filter") {
$sync.CheckboxFilter.SelectAll()
$sync.CheckboxFilter.Text = ""
}
$sync.CheckboxFilter.Focus()
}
}
$sync["Form"].Add_PreViewKeyDown($commonKeyEvents)
# adding some left mouse window move on drag capability
$sync["Form"].Add_MouseLeftButtonDown({
$sync["Form"].DragMove()
})
$sync["Form"].Add_MouseDoubleClick({
if ($sync["Form"].WindowState -eq [Windows.WindowState]::Normal)
{
$sync["Form"].WindowState = [Windows.WindowState]::Maximized;
}
else
{
$sync["Form"].WindowState = [Windows.WindowState]::Normal;
}
})
# setting window icon to make it look more professional
$sync["Form"].Add_Loaded({
$downloadUrl = "https://christitus.com/images/logo-full.png"
$destinationPath = Join-Path $env:TEMP "cttlogo.png"
# Check if the file already exists
if (-not (Test-Path $destinationPath)) {
# File does not exist, download it
$wc = New-Object System.Net.WebClient
$wc.DownloadFile($downloadUrl, $destinationPath)
Write-Host "File downloaded to: $destinationPath"
} else {
Write-Output "File already exists at: $destinationPath"
}
$sync["Form"].Icon = $destinationPath
Try {
[Void][Window]
} Catch {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Window {
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowWindow(IntPtr handle, int state);
}
public struct RECT {
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
"@
}
$processId = [System.Diagnostics.Process]::GetCurrentProcess().Id
$windowHandle = (Get-Process -Id $processId).MainWindowHandle
$rect = New-Object RECT
[Void][Window]::GetWindowRect($windowHandle,[ref]$rect)
# only snap upper edge don't move left to right, in case people have multimon setup
$x = $rect.Left
$y = 0
$width = $rect.Right - $rect.Left
$height = $rect.Bottom - $rect.Top
# Move the window to that position...
[Void][Window]::MoveWindow($windowHandle, $x, $y, $width, $height, $True)
Invoke-WPFTab "WPFTab1BT"
$sync["Form"].Focus()
})
$sync["CheckboxFilter"].Add_TextChanged({
#Write-host $sync.CheckboxFilter.Text
$filter = Get-WinUtilVariables -Type Checkbox
$CheckBoxes = $sync.GetEnumerator() | Where-Object {$psitem.Key -in $filter}
$textToSearch = $sync.CheckboxFilter.Text
Foreach ($CheckBox in $CheckBoxes) {
#Write-Host "$($sync.CheckboxFilter.Text)"
if ($CheckBox -eq $null -or $CheckBox.Value -eq $null -or $CheckBox.Value.Content -eq $null) {
continue
}
if ($CheckBox.Value.Content.ToLower().Contains($textToSearch)) {
$CheckBox.Value.Visibility = "Visible"
}
else {
$CheckBox.Value.Visibility = "Collapsed"
}
}
})
# show current windowsd Product ID
#Write-Host "Your Windows Product Key: $((Get-WmiObject -query 'select * from SoftwareLicensingService').OA3xOriginalProductKey)"
$sync["Form"].ShowDialog() | out-null
Stop-Transcript