################################################################################################################ ### ### ### WARNING: This file is automatically generated DO NOT modify this file directly as it will be overwritten ### ### ### ################################################################################################################ <# .NOTES Author : Chris Titus @christitustech Runspace Author: @DeveloperDurp GitHub : https://github.com/ChrisTitusTech Version : 24.07.25 #> param ( [switch]$Debug, [string]$Config, [switch]$Run ) # Set DebugPreference based on the -Debug switch if ($Debug) { $DebugPreference = "Continue" } if ($Config) { $PARAM_CONFIG = $Config } $PARAM_RUN = $false # Handle the -Run switch if ($Run) { Write-Host "Running config file tasks..." $PARAM_RUN = $true } if (!(Test-Path -Path $ENV:TEMP)) { New-Item -ItemType Directory -Force -Path $ENV:TEMP } Start-Transcript $ENV:TEMP\Winutil.log -Append # Load DLLs Add-Type -AssemblyName PresentationFramework Add-Type -AssemblyName System.Windows.Forms # Variable to sync between runspaces $sync = [Hashtable]::Synchronized(@{}) $sync.PSScriptRoot = $PSScriptRoot $sync.version = "24.07.25" $sync.configs = @{} $sync.ProcessRunning = $false # If script isn't running as admin, show error message and quit If (([Security.Principal.WindowsIdentity]::GetCurrent()).Owner.Value -ne "S-1-5-32-544") { Write-Host "===========================================" -Foregroundcolor Red Write-Host "-- Scripts must be run as Administrator ---" -Foregroundcolor Red Write-Host "-- Right-Click Start -> Terminal(Admin) ---" -Foregroundcolor Red Write-Host "===========================================" -Foregroundcolor Red break } # Set PowerShell window title $Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Admin)" clear-host function ConvertTo-Icon { <# .DESCRIPTION This function will convert BMP, GIF, EXIF, JPG, PNG and TIFF to ICO file .PARAMETER bitmapPath The file path to bitmap image to make '.ico' file out of. Supported file types according to Microsoft Documentation is the following: BMP, GIF, EXIF, JPG, PNG and TIFF. .PARAMETER iconPath The file path to write the new '.ico' resource. .PARAMETER overrideIconFile An optional boolean Parameter that makes the function overrides the Icon File Path if the file exists. Defaults to $true. .EXAMPLE try { ConvertTo-Icon -bitmapPath "$env:TEMP\cttlogo.png" -iconPath "$env:TEMP\cttlogo.ico" } catch [System.IO.FileNotFoundException] { # Handle the thrown exception here... } This Example makes a '.ico' file at "$env:TEMP\cttlogo.ico" File Path using the bitmap file found in "$env:TEMP\cttlogo.png", the function overrides the '.ico' File if it's found. this function will throw a FileNotFound Exception at the event of not finding the provided bitmap File Path. .EXAMPLE try { ConvertTo-Icon "$env:TEMP\cttlogo.png" "$env:TEMP\cttlogo.ico" } catch [System.IO.FileNotFoundException] { # Handle the thrown exception here... } This Example is the same as Example 1, but uses Positional Parameters instead. .EXAMPLE if (Test-Path "$env:TEMP\cttlogo.png") { ConvertTo-Icon -bitmapPath "$env:TEMP\cttlogo.png" -iconPath "$env:TEMP\cttlogo.ico" } This Example is same as Example 1, but checks if the bitmap File exists before calling 'ConvertTo-Icon' Function. This's the recommended way of using this function, as it doesn't require any try-catch blocks. .EXAMPLE try { ConvertTo-Icon -bitmapPath "$env:TEMP\cttlogo.png" -iconPath "$env:TEMP\cttlogo.ico" -overrideIconFile $false } catch [System.IO.FileNotFoundException] { # Handle the thrown exception here... } This Example make use of '-overrideIconFile' Optional Parameter, the default for this paramter is $true. By doing '-overrideIconFile $false', the 'ConvertTo-Icon' function will raise an exception that needs to be catched throw a 'catch' Code Block, otherwise it'll crash the running PowerShell instance/process. #> param( [Parameter(Mandatory=$true, position=0)] [string]$bitmapPath, [Parameter(Mandatory=$true, position=1)] [string]$iconPath, [Parameter(position=2)] [bool]$overrideIconFile = $true ) Add-Type -AssemblyName System.Drawing if (Test-Path $bitmapPath) { if ((Test-Path $iconPath) -AND ($overrideIconFile -eq $false)) { Write-Host "[ConvertTo-Icon] Icon File is found at '$iconPath', and the 'overrideIconFile' Parameter is set to '$overrideIconFile'. Skipping the bitmap to icon convertion..." -ForegroundColor Yellow return } # Load bitmap file into memory, and make an Icon version out of it $b = [System.Drawing.Bitmap]::FromFile($bitmapPath) $icon = [System.Drawing.Icon]::FromHandle($b.GetHicon()) # Create the folder for the new icon file if it doesn't exists $iconFolder = (New-Object System.IO.FileInfo($iconPath)).Directory.FullName [System.IO.Directory]::CreateDirectory($iconFolder) | Out-Null # Write the Icon File and do some cleaning-up $file = New-Object System.IO.FileStream($iconPath, 'OpenOrCreate') $icon.Save($file) $file.Close() $icon.Dispose() } else { throw [System.IO.FileNotFoundException] "[ConvertTo-Icon] The provided bitmap File Path is not found at '$bitmapPath'." } } function Copy-Files { <# .DESCRIPTION This function will make all modifications to the registry .EXAMPLE Set-WinUtilRegistry -Name "PublishUserActivities" -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\System" -Type "DWord" -Value "0" #> param ( [string] $Path, [string] $Destination, [switch] $Recurse = $false, [switch] $Force = $false ) try { $files = Get-ChildItem -Path $path -Recurse:$recurse Write-Host "Copy $($files.Count)(s) from $path to $destination" foreach($file in $files) { $status = "Copy files {0} on {1}: {2}" -f $counter, $files.Count, $file.Name Write-Progress -Activity "Copy Windows files" -Status $status -PercentComplete ($counter++/$files.count*100) $restpath = $file.FullName -Replace $path, '' if($file.PSIsContainer -eq $true) { Write-Debug "Creating $($destination + $restpath)" New-Item ($destination+$restpath) -Force:$force -Type Directory -ErrorAction SilentlyContinue } else { Write-Debug "Copy from $($file.FullName) to $($destination+$restpath)" Copy-Item $file.FullName ($destination+$restpath) -ErrorAction SilentlyContinue -Force:$force Set-ItemProperty -Path ($destination+$restpath) -Name IsReadOnly -Value $false } } Write-Progress -Activity "Copy Windows files" -Status "Ready" -Completed } Catch{ Write-Warning "Unable to Copy all the files due to unhandled exception" Write-Warning $psitem.Exception.StackTrace } } function Get-LocalizedYesNo { <# .SYNOPSIS This function runs choice.exe and captures its output to extract yes no in a localized Windows .DESCRIPTION The function retrieves the output of the command 'cmd /c "choice nul"' and converts the default output for Yes and No in the localized format, such as "Yes=, No=". .EXAMPLE $yesNoArray = Get-LocalizedYesNo Write-Host "Yes=$($yesNoArray[0]), No=$($yesNoArray[1])" #> # Run choice and capture its options as output # The output shows the options for Yes and No as "[Y,N]?" in the (partitially) localized format. # eg. English: [Y,N]? # Dutch: [Y,N]? # German: [J,N]? # French: [O,N]? # Spanish: [S,N]? # Italian: [S,N]? # Russian: [Y,N]? $line = cmd /c "choice nul" $charactersArray = @() $regexPattern = '([a-zA-Z])' $charactersArray = [regex]::Matches($line, $regexPattern) | ForEach-Object { $_.Groups[1].Value } Write-Debug "According to takeown.exe local Yes is $charactersArray[0]" # Return the array of characters return $charactersArray } function Get-Oscdimg { <# .DESCRIPTION This function will download oscdimg file from github Release folders and put it into env:temp folder .EXAMPLE Get-Oscdimg #> param( [Parameter(Mandatory=$true)] [string]$oscdimgPath ) $oscdimgPath = "$env:TEMP\oscdimg.exe" $downloadUrl = "https://github.com/ChrisTitusTech/winutil/raw/main/releases/oscdimg.exe" Invoke-RestMethod -Uri $downloadUrl -OutFile $oscdimgPath $hashResult = Get-FileHash -Path $oscdimgPath -Algorithm SHA256 $sha256Hash = $hashResult.Hash Write-Host "[INFO] oscdimg.exe SHA-256 Hash: $sha256Hash" $expectedHash = "AB9E161049D293B544961BFDF2D61244ADE79376D6423DF4F60BF9B147D3C78D" # Replace with the actual expected hash if ($sha256Hash -eq $expectedHash) { Write-Host "Hashes match. File is verified." } else { Write-Host "Hashes do not match. File may be corrupted or tampered with." } } function Get-TabXaml { <# .SYNOPSIS Generates XAML for a tab in the WinUtil GUI This function is used to generate the XAML for the applications tab in the WinUtil GUI It takes the tabname and the number of columns to display the applications in as input and returns the XAML for the tab as output .PARAMETER tabname The name of the tab to generate XAML for Note: the 'tabname' parameter must equal one of the json files found in $sync.configs variable Otherwise, it'll throw an exception .PARAMETER columncount The number of columns to display the applications in, default is 0 .OUTPUTS The XAML for the tab .EXAMPLE Get-TabXaml "applications" 3 #> param( [Parameter(Mandatory, position=0)] [string]$tabname, [Parameter(position=1)] [ValidateRange(0,10)] # 10 panels as max number is more then enough [int]$columncount = 0 ) # Validate tabname if ($sync.configs.$tabname -eq $null) { throw "Invalid parameter passed, can't find '$tabname' in '`$sync.configs' variable, please double check any calls to 'Get-TabXaml' function." } $organizedData = @{} # Iterate through JSON data and organize by panel and category foreach ($appName in $sync.configs.$tabname.PSObject.Properties.Name) { $appInfo = $sync.configs.$tabname.$appName # Create an object for the application $appObject = [PSCustomObject]@{ Name = $appName Category = $appInfo.Category Content = $appInfo.Content Choco = $appInfo.choco Winget = $appInfo.winget Panel = if ($columncount -gt 0 ) { "0" } else {$appInfo.panel} Link = $appInfo.link Description = $appInfo.description # Type is (Checkbox,Toggle,Button,Combobox ) (Default is Checkbox) Type = $appInfo.type ComboItems = $appInfo.ComboItems # Checked is the property to set startup checked status of checkbox (Default is false) Checked = $appInfo.Checked ButtonWidth = $appInfo.ButtonWidth } if (-not $organizedData.ContainsKey($appObject.panel)) { $organizedData[$appObject.panel] = @{} } if (-not $organizedData[$appObject.panel].ContainsKey($appObject.Category)) { $organizedData[$appObject.panel][$appObject.Category] = @{} } # Store application data in a sub-array under the category # Add Order property to keep the original order of tweaks and features $organizedData[$appObject.panel][$appInfo.Category]["$($appInfo.order)$appName"] = $appObject } # Same tab amount in last line of 'inputXML.xaml' file # TODO: Get the base repeat (amount) of tabs from last line (or even lines) # so it can dynamicly react to whatever is before this generated XML string. # .. may be solve this even before calling this function, and pass the result as a parameter? $tab_repeat = 7 $spaces_per_tab = 4 # The convenction used across the code base $tab_as_spaces = $(" " * $spaces_per_tab) $precal_indent = $($tab_as_spaces * $tab_repeat) $precal_indent_p1 = $($tab_as_spaces * ($tab_repeat + 1)) $precal_indent_p2 = $($tab_as_spaces * ($tab_repeat + 2)) $precal_indent_m1 = $($tab_as_spaces * ($tab_repeat - 1)) $precal_indent_m2 = $($tab_as_spaces * ($tab_repeat - 2)) # Calculate the needed number of panels $panelcount = 0 $paneltotal = $organizedData.Keys.Count if ($columncount -gt 0) { $appcount = $sync.configs.$tabname.PSObject.Properties.Name.count + $organizedData["0"].Keys.count $maxcount = [Math]::Round( $appcount / $columncount + 0.5) $paneltotal = $columncount } # add ColumnDefinitions to evenly draw colums $blockXml = "" $blockXml += $("`r`n" + " " * ($spaces_per_tab * $tab_repeat) + "") * $paneltotal $blockXml += $("`r`n" + " " * ($spaces_per_tab * ($tab_repeat - 1))) + "" + "`r`n" # Iterate through 'organizedData' by panel, category, and application $count = 0 foreach ($panel in ($organizedData.Keys | Sort-Object)) { $blockXml += $precal_indent_m1 + "" + "`r`n" $blockXml += $precal_indent + "" + "`r`n" $panelcount++ foreach ($category in ($organizedData[$panel].Keys | Sort-Object)) { $count++ if ($columncount -gt 0) { $panelcount2 = [Int](($count)/$maxcount-0.5) if ($panelcount -eq $panelcount2 ) { $blockXml += $precal_indent_p2 + "" + "`r`n" $blockXml += $precal_indent_p1 + "" + "`r`n" $blockXml += $precal_indent_p1 + "" + "`r`n" $blockXml += $precal_indent_p2 + "" + "`r`n" $panelcount++ } } # Dot-source the Get-WPFObjectName function . .\functions\private\Get-WPFObjectName $categorycontent = $($category -replace '^.__', '') $categoryname = Get-WPFObjectName -type "Label" -name $categorycontent $blockXml += $("`r`n" + " " * ($spaces_per_tab * $tab_repeat)) + "" + "`r`n" $blockXml += $precal_indent_m2 + "" + "`r`n" $blockXml += $precal_indent_m2 + "" + "`r`n" $blockXml += $precal_indent_m1 + "" + "`r`n" $panelcount++ } } $appInfo = $organizedData[$panel][$category][$appName] switch ($appInfo.Type) { "Toggle" { $blockXml += $precal_indent_m1 + "" + "`r`n" $blockXml += $precal_indent + "" + "`r`n" $blockXml += $precal_indent + "" + "`r`n" } "Combobox" { $blockXml += $precal_indent_m1 + "" + "`r`n" $blockXml += $precal_indent + "" + "`r`n" } "Button" { if ($appInfo.ButtonWidth -ne $null) { $ButtonWidthStr = "Width=""$($appInfo.ButtonWidth)""" } $blockXml += $precal_indent + " Choose Windows SKU Choose Windows features you want to remove from the ISO