mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2024-11-14 14:45:52 -06:00
6b73f71876
* Update Documentation for 'Get-TabXaml' Function * Add More checks for Passed Parameters for 'Get-TabXaml' Function & A One Line change * Make 'Get-TabXaml' Function do Proper Indentation to the Generated XML String & Add more Theming Options * Re-formatting for a few lines in 'Get-TabXaml' Function * Improve UI a bit * Fix Buttons for Config Tab * Add optional 'ButtonWidth' Field to Config Json Files that use 'Button' Type * Simple Code Formatting in 'inputXML.xaml' File * Make naming More Clear to the Developers Rename the name of an element from 'CheckboxFilterClear' to 'SearchBarClearButton' and renamed style from 'ClearButtonStyle' to 'SearchBarClearButtonStyle' * Rename 'FontFamilyHeading' to 'HeaderFontFamily' to match conventions & Make new Theming Property called 'SearchBarClearButtonFontSize' * Remove Un-necessary 'ToString' Convertion in 'Get-TabXaml' Function * Make naming More Clear to the Developers again Rename the name of an element from 'CheckboxFilter' to 'SearchBar'.. although after some realization.. the naming might make sense at first, because it filters only the checkboxes.. but CheckBoxFilter is less "Intutive" when saying it compared to "SearchBar".. _This's my own opinion, and it can reverted easily with git if needed._ * Remove Un-necessary Attributes in 'Get-TabXaml' Private Function * Improve UI a bit * Improve UI a bit * Re-order some tweaks & Update their Content Fields to be more descriptive * Remove the changing 'Off' & 'On' TextBlock next to Toggle Button/Checkbox Removed it as it only takes up space in the right side of the Toggle Button/Checkbox, as well as making it difficult to correctly align it when change from/to 'On' & 'Off' Text, * Some changes to the Generated Toggle Button/Checkbox in 'Get-TabXaml' Private Function Increase the Side Margins of Toggle Button/Checkbox, as well as Change its side to be on the Left hand-side of the Label, rather then the Right hand-side.
510 lines
17 KiB
PowerShell
510 lines
17 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{$_.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'
|
|
|
|
if ((Get-WinUtilToggleStatus WPFToggleDarkMode) -eq $True) {
|
|
if (Invoke-WinUtilGPU -eq $True) {
|
|
$ctttheme = 'Matrix'
|
|
}
|
|
else {
|
|
$ctttheme = 'Dark'
|
|
}
|
|
}
|
|
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 <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 "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
|
|
})
|
|
}
|
|
|
|
if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "ToggleButton"){
|
|
$sync["$psitem"].Add_Click({
|
|
[System.Object]$Sender = $args[0]
|
|
Invoke-WPFButton $Sender.name
|
|
})
|
|
}
|
|
|
|
if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "Button"){
|
|
$sync["$psitem"].Add_Click({
|
|
[System.Object]$Sender = $args[0]
|
|
Invoke-WPFButton $Sender.name
|
|
})
|
|
}
|
|
|
|
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
|
|
Write-Debug "Opening: $($Sender.ToolTip)"
|
|
})
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#===========================================================================
|
|
# Setup background config
|
|
#===========================================================================
|
|
|
|
# Load computer information in the background
|
|
Invoke-WPFRunspace -ScriptBlock {
|
|
try{
|
|
$oldProgressPreference = $ProgressPreference
|
|
$ProgressPreference = "SilentlyContinue"
|
|
$sync.ConfigLoaded = $False
|
|
$sync.ComputerInfo = Get-ComputerInfo
|
|
$sync.ConfigLoaded = $True
|
|
}
|
|
finally{
|
|
$ProgressPreference = "Continue"
|
|
}
|
|
|
|
} | Out-Null
|
|
|
|
#===========================================================================
|
|
# Setup and Show the Form
|
|
#===========================================================================
|
|
|
|
# Print the logo
|
|
Invoke-WPFFormVariables
|
|
|
|
# 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()
|
|
})
|
|
|
|
# Attach the event handler to the Click event
|
|
$sync.SearchBarClearButton.Add_Click({
|
|
$sync.SearchBar.Text = ""
|
|
$sync.SearchBarClearButton.Visibility = "Collapsed"
|
|
})
|
|
|
|
# add some shortcuts for people that don't like clicking
|
|
$commonKeyEvents = {
|
|
if ($sync.ProcessRunning -eq $true) {
|
|
return
|
|
}
|
|
|
|
if ($_.Key -eq "Escape")
|
|
{
|
|
$sync.SearchBar.SelectAll()
|
|
$sync.SearchBar.Text = ""
|
|
$sync.SearchBarClearButton.Visibility = "Collapsed"
|
|
return
|
|
}
|
|
|
|
# don't ask, I know what I'm doing, just go...
|
|
if (($_.Key -eq "Q" -and $_.KeyboardDevice.Modifiers -eq "Ctrl"))
|
|
{
|
|
$this.Close()
|
|
}
|
|
if ($_.KeyboardDevice.Modifiers -eq "Alt") {
|
|
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"
|
|
}
|
|
if ($_.SystemKey -eq "P") {
|
|
Write-Host "Your Windows Product Key: $((Get-WmiObject -query 'select * from SoftwareLicensingService').OA3xOriginalProductKey)"
|
|
}
|
|
}
|
|
# shortcut for the filter box
|
|
if ($_.Key -eq "F" -and $_.KeyboardDevice.Modifiers -eq "Ctrl") {
|
|
if ($sync.SearchBar.Text -eq "Ctrl-F to filter") {
|
|
$sync.SearchBar.SelectAll()
|
|
$sync.SearchBar.Text = ""
|
|
}
|
|
$sync.SearchBar.Focus()
|
|
}
|
|
}
|
|
|
|
$sync["Form"].Add_PreViewKeyDown($commonKeyEvents)
|
|
|
|
$sync["Form"].Add_MouseLeftButtonDown({
|
|
if ($sync["SettingsPopup"].IsOpen) {
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
}
|
|
$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;
|
|
}
|
|
})
|
|
|
|
$sync["Form"].Add_Deactivated({
|
|
Write-Debug "WinUtil lost focus"
|
|
if ($sync["SettingsPopup"].IsOpen) {
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
}
|
|
})
|
|
|
|
$sync["Form"].Add_ContentRendered({
|
|
|
|
try {
|
|
[void][Window]
|
|
} catch {
|
|
Add-Type @"
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
public class Window {
|
|
[DllImport("user32.dll")]
|
|
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
|
|
|
[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")]
|
|
public static extern int GetSystemMetrics(int nIndex);
|
|
};
|
|
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
|
|
}
|
|
"@
|
|
}
|
|
|
|
foreach ($proc in (Get-Process).where{ $_.MainWindowTitle -and $_.MainWindowTitle -like "*titus*" }) {
|
|
# Check if the process's MainWindowHandle is valid
|
|
if ($proc.MainWindowHandle -ne [System.IntPtr]::Zero) {
|
|
Write-Debug "MainWindowHandle: $($proc.Id) $($proc.MainWindowTitle) $($proc.MainWindowHandle)"
|
|
$windowHandle = $proc.MainWindowHandle
|
|
} else {
|
|
Write-Warning "Process found, but no MainWindowHandle: $($proc.Id) $($proc.MainWindowTitle)"
|
|
|
|
}
|
|
}
|
|
|
|
|
|
# Using a TaskbarItem Overlay until someone figures out how to replace the icon correctly
|
|
|
|
# URL of the image
|
|
$imageUrl = "https://christitus.com/images/logo-full.png"
|
|
|
|
# Download the image
|
|
$imagePath = "$env:TEMP\logo-full.png"
|
|
Invoke-WebRequest -Uri $imageUrl -OutFile $imagePath
|
|
|
|
# Read the image file as a byte array
|
|
$imageBytes = [System.IO.File]::ReadAllBytes($imagePath)
|
|
|
|
# Convert the byte array to a Base64 string
|
|
$base64String = [System.Convert]::ToBase64String($imageBytes)
|
|
|
|
# Create a streaming image by streaming the base64 string to a bitmap streamsource
|
|
$bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
|
|
$bitmap.BeginInit()
|
|
$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64String)
|
|
$bitmap.EndInit()
|
|
$bitmap.Freeze()
|
|
|
|
# Ensure TaskbarItemInfo is created if not already
|
|
if (-not $sync["Form"].TaskbarItemInfo) {
|
|
$sync["Form"].TaskbarItemInfo = New-Object System.Windows.Shell.TaskbarItemInfo
|
|
}
|
|
|
|
# Set the overlay icon for the taskbar
|
|
$sync["Form"].TaskbarItemInfo.Overlay = $bitmap
|
|
|
|
|
|
$rect = New-Object RECT
|
|
[Window]::GetWindowRect($windowHandle, [ref]$rect)
|
|
$width = $rect.Right - $rect.Left
|
|
$height = $rect.Bottom - $rect.Top
|
|
|
|
Write-Debug "UpperLeft:$($rect.Left),$($rect.Top) LowerBottom:$($rect.Right),$($rect.Bottom). Width:$($width) Height:$($height)"
|
|
|
|
# Load the Windows Forms assembly
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
$primaryScreen = [System.Windows.Forms.Screen]::PrimaryScreen
|
|
# Check if the primary screen is found
|
|
if ($primaryScreen) {
|
|
# Extract screen width and height for the primary monitor
|
|
$screenWidth = $primaryScreen.Bounds.Width
|
|
$screenHeight = $primaryScreen.Bounds.Height
|
|
|
|
# Print the screen size
|
|
Write-Debug "Primary Monitor Width: $screenWidth pixels"
|
|
Write-Debug "Primary Monitor Height: $screenHeight pixels"
|
|
|
|
# Compare with the primary monitor size
|
|
if ($width -gt $screenWidth -or $height -gt $screenHeight) {
|
|
Write-Debug "The specified width and/or height is greater than the primary monitor size."
|
|
[void][Window]::MoveWindow($windowHandle, 0, 0, $screenWidth, $screenHeight, $True)
|
|
} else {
|
|
Write-Debug "The specified width and height are within the primary monitor size limits."
|
|
}
|
|
} else {
|
|
Write-Debug "Unable to retrieve information about the primary monitor."
|
|
}
|
|
|
|
Invoke-WPFTab "WPFTab1BT"
|
|
$sync["Form"].Focus()
|
|
|
|
# maybe this is not the best place to load and execute config file?
|
|
# maybe community can help?
|
|
if ($PARAM_CONFIG){
|
|
Invoke-WPFImpex -type "import" -Config $PARAM_CONFIG
|
|
if ($PARAM_RUN){
|
|
while ($sync.ProcessRunning) {
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
Start-Sleep -Seconds 5
|
|
|
|
Write-Host "Applying tweaks..."
|
|
Invoke-WPFtweaksbutton
|
|
while ($sync.ProcessRunning) {
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
Start-Sleep -Seconds 5
|
|
|
|
Write-Host "Installing features..."
|
|
Invoke-WPFFeatureInstall
|
|
while ($sync.ProcessRunning) {
|
|
Start-Sleep -Seconds 5
|
|
}
|
|
|
|
Start-Sleep -Seconds 5
|
|
Write-Host "Installing applications..."
|
|
while ($sync.ProcessRunning) {
|
|
Start-Sleep -Seconds 1
|
|
}
|
|
Invoke-WPFInstall
|
|
Start-Sleep -Seconds 5
|
|
|
|
Write-Host "Done."
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
# Load Checkboxes and Labels outside of the Filter fuction only once on startup for performance reasons
|
|
$filter = Get-WinUtilVariables -Type CheckBox
|
|
$CheckBoxes = ($sync.GetEnumerator()).where{ $psitem.Key -in $filter }
|
|
|
|
$filter = Get-WinUtilVariables -Type Label
|
|
$labels = @{}
|
|
($sync.GetEnumerator()).where{$PSItem.Key -in $filter} | ForEach-Object {$labels[$_.Key] = $_.Value}
|
|
|
|
$allCategories = $checkBoxes.Name | ForEach-Object {$sync.configs.applications.$_} | Select-Object -Unique -ExpandProperty category
|
|
|
|
$sync["SearchBar"].Add_TextChanged({
|
|
|
|
if ($sync.SearchBar.Text -ne "") {
|
|
$sync.SearchBarClearButton.Visibility = "Visible"
|
|
}
|
|
else {
|
|
$sync.SearchBarClearButton.Visibility = "Collapsed"
|
|
}
|
|
|
|
$activeApplications = @()
|
|
|
|
foreach ($CheckBox in $CheckBoxes) {
|
|
# Check if the checkbox is null or if it doesn't have content
|
|
if ($CheckBox -eq $null -or $CheckBox.Value -eq $null -or $CheckBox.Value.Content -eq $null) {
|
|
continue
|
|
}
|
|
|
|
$textToSearch = $sync.SearchBar.Text.ToLower()
|
|
$checkBoxName = $CheckBox.Key
|
|
$textBlockName = $checkBoxName + "Link"
|
|
|
|
# Retrieve the corresponding text block based on the generated name
|
|
$textBlock = $sync[$textBlockName]
|
|
|
|
if ($CheckBox.Value.Content.ToLower().Contains($textToSearch)) {
|
|
$CheckBox.Value.Visibility = "Visible"
|
|
$activeApplications += $sync.configs.applications.$checkboxName
|
|
# Set the corresponding text block visibility
|
|
if ($textBlock -ne $null) {
|
|
$textBlock.Visibility = "Visible"
|
|
}
|
|
}
|
|
else {
|
|
$CheckBox.Value.Visibility = "Collapsed"
|
|
# Set the corresponding text block visibility
|
|
if ($textBlock -ne $null) {
|
|
$textBlock.Visibility = "Collapsed"
|
|
}
|
|
}
|
|
}
|
|
$activeCategories = $activeApplications | Select-Object -ExpandProperty category -Unique
|
|
|
|
foreach ($category in $activeCategories){
|
|
$label = $labels[$(Get-WPFObjectName -type "Label" -name $category)]
|
|
$label.Visibility = "Visible"
|
|
}
|
|
if ($activeCategories){
|
|
$inactiveCategories = Compare-Object -ReferenceObject $allCategories -DifferenceObject $activeCategories -PassThru
|
|
}
|
|
else{
|
|
$inactiveCategories = $allCategories
|
|
}
|
|
foreach ($category in $inactiveCategories){
|
|
$label = $labels[$(Get-WPFObjectName -type "Label" -name $category)]
|
|
$label.Visibility = "Collapsed"}
|
|
})
|
|
|
|
# Define event handler for button click
|
|
$sync["SettingsButton"].Add_Click({
|
|
Write-Debug "SettingsButton clicked"
|
|
if ($sync["SettingsPopup"].IsOpen) {
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
}
|
|
else {
|
|
$sync["SettingsPopup"].IsOpen = $true
|
|
}
|
|
$_.Handled = $false
|
|
})
|
|
|
|
# Define event handlers for menu items
|
|
$sync["ImportMenuItem"].Add_Click({
|
|
# Handle Import menu item click
|
|
Write-Debug "Import clicked"
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
Invoke-WPFImpex -type "import"
|
|
$_.Handled = $false
|
|
})
|
|
|
|
$sync["ExportMenuItem"].Add_Click({
|
|
# Handle Export menu item click
|
|
Write-Debug "Export clicked"
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
Invoke-WPFImpex -type "export"
|
|
$_.Handled = $false
|
|
})
|
|
|
|
$sync["AboutMenuItem"].Add_Click({
|
|
# Handle Export menu item click
|
|
Write-Debug "About clicked"
|
|
$sync["SettingsPopup"].IsOpen = $false
|
|
$authorInfo = @"
|
|
Author : <a href="https://github.com/ChrisTitusTech">@christitustech</a>
|
|
Runspace : <a href="https://github.com/DeveloperDurp">@DeveloperDurp</a>
|
|
MicroWin : <a href="https://github.com/KonTy">@KonTy</a>
|
|
GitHub : <a href="https://github.com/ChrisTitusTech/winutil">ChrisTitusTech/winutil</a>
|
|
Version : <a href="https://github.com/ChrisTitusTech/winutil/releases/tag/$($sync.version)">$($sync.version)</a>
|
|
"@
|
|
$FontSize = $sync.configs.themes.$ctttheme.CustomDialogFontSize
|
|
$HeaderFontSize = $sync.configs.themes.$ctttheme.CustomDialogFontSizeHeader
|
|
$IconSize = $sync.configs.themes.$ctttheme.CustomDialogIconSize
|
|
$Width = $sync.configs.themes.$ctttheme.CustomDialogWidth
|
|
$Height = $sync.configs.themes.$ctttheme.CustomDialogHeight
|
|
Show-CustomDialog -Message $authorInfo -Width $Width -Height $Height -FontSize $FontSize -HeaderFontSize $HeaderFontSize -IconSize $IconSize
|
|
})
|
|
$sync["Form"].ShowDialog() | out-null
|
|
Stop-Transcript
|