diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c7ddb76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +config/* diff +config/applications.json diff +*.json diff \ No newline at end of file diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index e7dd572..6e64cdf 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -19,22 +19,22 @@ jobs: failOnInfos: false test: runs-on: windows-latest + steps: - - name: Check out repository code - uses: actions/checkout@v3 - with: - ref: ${{ github.head_ref }} - - name: pester_tests - id: pester_tests - uses: zyborg/pester-tests-report@v1 - with: - include_paths: pester - github_token: ${{ secrets.GITHUB_TOKEN }} - tests_fail_step: true - skip_check_run: true - - name: dump test results - shell: pwsh - run: | - Write-Host 'Total Tests Executed...: ${{ steps.pester_tests.outputs.total_count }}' - Write-Host 'Total Tests PASSED.....: ${{ steps.pester_tests.outputs.passed_count }}' - Write-Host 'Total Tests FAILED.....: ${{ steps.pester_tests.outputs.failed_count }}' \ No newline at end of file + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install Pester + run: | + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process + Install-Module -Name Pester -Force -AllowClobber + shell: pwsh + + - name: Run Pester tests + run: | + Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process + Invoke-Pester -Path 'pester/*.Tests.ps1' -EnableExit + + shell: pwsh + env: + TEMP: ${{ runner.temp }} \ No newline at end of file diff --git a/config/applications.json b/config/applications.json index 52edb8f..e4a2aaa 100755 Binary files a/config/applications.json and b/config/applications.json differ diff --git a/config/themes.json b/config/themes.json index 7fa31da..1055827 100644 --- a/config/themes.json +++ b/config/themes.json @@ -1,38 +1,12 @@ { - "Classic": { - "ComboBoxBackgroundColor": "#777777", - "LabelboxForegroundColor": "#000000", - "MainForegroundColor": "#000000", - "MainBackgroundColor": "#777777", - "LabelBackgroundColor": "#777777", - "ComboBoxForegroundColor": "#000000", - "ButtonInstallBackgroundColor": "#222222", - "ButtonTweaksBackgroundColor": "#333333", - "ButtonConfigBackgroundColor": "#444444", - "ButtonUpdatesBackgroundColor": "#555555", - "ButtonInstallForegroundColor": "#FFFFFF", - "ButtonTweaksForegroundColor": "#FFFFFF", - "ButtonConfigForegroundColor": "#FFFFFF", - "ButtonUpdatesForegroundColor": "#FFFFFF", - "ButtonBackgroundColor": "#CACACA", - "ButtonBackgroundPressedColor": "#FFFFFF", - "ButtonBackgroundMouseoverColor": "#A55A64", - "ButtonBackgroundSelectedColor": "#BADFFF", - "ButtonForegroundColor": "#000000", - "ButtonBorderThickness": "0", - "ButtonMargin": "0,3,0,3", - "ButtonCornerRadius": "0", - "ToggleButtonHeight": "40", - "BorderColor": "#000000", - "BorderOpacity": "0.2", - "ShadowPulse": "Forever" - }, "Classic": { "ComboBoxBackgroundColor": "#FFFFFF", "LabelboxForegroundColor": "#000000", "MainForegroundColor": "#000000", "MainBackgroundColor": "#FFFFFF", "LabelBackgroundColor": "#FAFAFA", + "LinkForegroundColor": "#000000", + "LinkHoverForegroundColor": "#000000", "GroupBorderBackgroundColor": "#000000", "ComboBoxForegroundColor": "#000000", "ButtonInstallBackgroundColor": "#FFFFFF", @@ -63,6 +37,8 @@ "MainForegroundColor": "#9CCC65", "MainBackgroundColor": "#000000", "LabelBackgroundColor": "#000000", + "LinkForegroundColor": "#add8e6", + "LinkHoverForegroundColor": "#FFFFFF", "ComboBoxForegroundColor": "#FFEE58", "ButtonInstallBackgroundColor": "#222222", "ButtonTweaksBackgroundColor": "#333333", diff --git a/config/tweaks.json b/config/tweaks.json index a61597c..ead26d6 100644 --- a/config/tweaks.json +++ b/config/tweaks.json @@ -2141,6 +2141,43 @@ " ] }, + "WPFEssTweaksRestorePoint": { + "InvokeScript": [ + " + # Check if the user has administrative privileges + if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Host \"Please run this script as an administrator.\" + return + } + + # Check if System Restore is enabled for the main drive + try { + # Try getting restore points to check if System Restore is enabled + Enable-ComputerRestore -Drive \"$env:SystemDrive\" + } catch { + Write-Host \"An error occurred while enabling System Restore: $_\" + } + + # Check if the SystemRestorePointCreationFrequency value exists + $exists = Get-ItemProperty -path \"HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRestore\" -Name \"SystemRestorePointCreationFrequency\" -ErrorAction SilentlyContinue + if($null -eq $exists){ + write-host 'Changing system to allow multiple restore points per day' + Set-ItemProperty -Path \"HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRestore\" -Name \"SystemRestorePointCreationFrequency\" -Value \"0\" -Type DWord -Force -ErrorAction Stop | Out-Null + } + + # Get all the restore points for the current day + $existingRestorePoints = Get-ComputerRestorePoint | Where-Object { $_.CreationTime.Date -eq (Get-Date).Date } + + # Check if there is already a restore point created today + if ($existingRestorePoints.Count -eq 0) { + $description = \"System Restore Point created by WinUtil\" + + Checkpoint-Computer -Description $description -RestorePointType \"MODIFY_SETTINGS\" + Write-Host -ForegroundColor Green \"System Restore Point Created Successfully\" + } + " + ] + }, "WPFEssTweaksOO": { "InvokeScript": [ "curl.exe -s \"https://raw.githubusercontent.com/ChrisTitusTech/winutil/main/ooshutup10_winutil_settings.cfg\" -o $ENV:temp\\ooshutup10.cfg diff --git a/functions/private/Get-LocalizedYesNo.ps1 b/functions/private/Get-LocalizedYesNo.ps1 index fc6973f..db7c046 100644 --- a/functions/private/Get-LocalizedYesNo.ps1 +++ b/functions/private/Get-LocalizedYesNo.ps1 @@ -1,4 +1,39 @@ 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-LocalizedYesNoTakeown { <# .SYNOPSIS This function runs takeown.exe and captures its output to extract yes no in a localized Windows diff --git a/functions/private/Get-WinUtilVariables.ps1 b/functions/private/Get-WinUtilVariables.ps1 index f7cffc3..d19a3e9 100644 --- a/functions/private/Get-WinUtilVariables.ps1 +++ b/functions/private/Get-WinUtilVariables.ps1 @@ -1,30 +1,30 @@ function Get-WinUtilVariables { <# - .SYNOPSIS Gets every form object of the provided type .OUTPUTS List containing every object that matches the provided type - #> param ( [Parameter()] - [ValidateSet("CheckBox", "Button", "ToggleButton")] - [string]$Type + [string[]]$Type ) - $keys = $sync.keys | Where-Object {$psitem -like "WPF*"} + $keys = $sync.keys | Where-Object { $_ -like "WPF*" } - if($type){ + if ($Type) { $output = $keys | ForEach-Object { - Try{ - if ($sync["$psitem"].GetType() -like "*$type*"){ + Try { + $objType = $sync["$psitem"].GetType().Name + if ($Type -contains $objType) { Write-Output $psitem } } - Catch{<#I am here so errors don't get outputted for a couple variables that don't have the .GetType() attribute#>} + Catch { + <#I am here so errors don't get outputted for a couple variables that don't have the .GetType() attribute#> + } } return $output } diff --git a/functions/private/MicroWin-Helper.ps1 b/functions/private/Invoke-MicroWin-Helper.ps1 similarity index 98% rename from functions/private/MicroWin-Helper.ps1 rename to functions/private/Invoke-MicroWin-Helper.ps1 index 5219f1b..7abc493 100644 --- a/functions/private/MicroWin-Helper.ps1 +++ b/functions/private/Invoke-MicroWin-Helper.ps1 @@ -1,3 +1,20 @@ +function Invoke-MicroWin-Helper { +<# + + .SYNOPSIS + checking unit tests + + .PARAMETER Name + no parameters + + .EXAMPLE + placeholder + +#> + +} + + function Remove-Features([switch] $dumpFeatures = $false, [switch] $keepDefender = $false) { <# @@ -430,7 +447,7 @@ function New-FirstRun { function Stop-UnnecessaryServices { - $servicesAuto = @" + $servicesToExclude = @( "AudioSrv", "AudioEndpointBuilder", "BFE", @@ -493,12 +510,12 @@ function New-FirstRun { "vm3dservice", "webthreatdefusersvc_dc2a4", "wscsvc" -"@ +) - $allServices = Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $servicesAuto -NotContains $_.Name} - foreach($service in $allServices) + $runningServices = Get-Service | Where-Object { $servicesToExclude -notcontains $_.Name } + foreach($service in $runningServices) { - Stop-Service -Name $service.Name -PassThru + Stop-Service -Name $service.Name -PassThru Set-Service $service.Name -StartupType Manual "Stopping service $($service.Name)" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber } diff --git a/functions/private/Set-WinUtilRestorePoint.ps1 b/functions/private/Set-WinUtilRestorePoint.ps1 deleted file mode 100644 index 79618a1..0000000 --- a/functions/private/Set-WinUtilRestorePoint.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -function Set-WinUtilRestorePoint { - <# - - .SYNOPSIS - Creates a Restore Point - - #> - - # Check if the user has administrative privileges - if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Host "Please run this script as an administrator." - return - } - - # Check if System Restore is enabled for the main drive - try { - # Try getting restore points to check if System Restore is enabled - Enable-ComputerRestore -Drive "$env:SystemDrive" - } catch { - Write-Host "An error occurred while enabling System Restore: $_" - } - - # Check if the SystemRestorePointCreationFrequency value exists - $exists = Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -name "SystemRestorePointCreationFrequency" -ErrorAction SilentlyContinue - if($null -eq $exists){ - write-host 'Changing system to allow multiple restore points per day' - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -Name "SystemRestorePointCreationFrequency" -Value "0" -Type DWord -Force -ErrorAction Stop | Out-Null - } - - # Get all the restore points for the current day - $existingRestorePoints = Get-ComputerRestorePoint | Where-Object { $_.CreationTime.Date -eq (Get-Date).Date } - - # Check if there is already a restore point created today - if ($existingRestorePoints.Count -eq 0) { - $description = "System Restore Point created by WinUtil" - - Checkpoint-Computer -Description $description -RestorePointType "MODIFY_SETTINGS" - Write-Host -ForegroundColor Green "System Restore Point Created Successfully" - } -} diff --git a/functions/public/Invoke-WPFButton.ps1 b/functions/public/Invoke-WPFButton.ps1 index dc7890f..a0f1ab2 100644 --- a/functions/public/Invoke-WPFButton.ps1 +++ b/functions/public/Invoke-WPFButton.ps1 @@ -55,6 +55,6 @@ function Invoke-WPFButton { "WPFGetInstalledTweaks" {Invoke-WPFGetInstalled -CheckBox "tweaks"} "WPFGetIso" {Invoke-WPFGetIso} "WPFMicrowin" {Invoke-WPFMicrowin} - "WPFCloseButton" {Invoke-CloseButton} + "WPFCloseButton" {Invoke-WPFCloseButton} } } \ No newline at end of file diff --git a/functions/public/Invoke-WPFCloseButton.ps1 b/functions/public/Invoke-WPFCloseButton.ps1 index 2b0efc9..8a784fc 100644 --- a/functions/public/Invoke-WPFCloseButton.ps1 +++ b/functions/public/Invoke-WPFCloseButton.ps1 @@ -1,4 +1,4 @@ -function Invoke-CloseButton { +function Invoke-WPFCloseButton { <# diff --git a/functions/public/Invoke-WPFGetIso.ps1 b/functions/public/Invoke-WPFGetIso.ps1 index 8c819a2..af6f3e6 100644 --- a/functions/public/Invoke-WPFGetIso.ps1 +++ b/functions/public/Invoke-WPFGetIso.ps1 @@ -80,11 +80,19 @@ function Invoke-WPFGetIso { return } - Write-Host "Mounting Iso. Please wait." - $mountedISO = Mount-DiskImage -PassThru "$filePath" - Write-Host "Done mounting Iso $mountedISO" - $driveLetter = (Get-Volume -DiskImage $mountedISO).DriveLetter - Write-Host "Iso mounted to '$driveLetter'" + try { + Write-Host "Mounting Iso. Please wait." + $mountedISO = Mount-DiskImage -PassThru "$filePath" + Write-Host "Done mounting Iso $mountedISO" + $driveLetter = (Get-Volume -DiskImage $mountedISO).DriveLetter + Write-Host "Iso mounted to '$driveLetter'" + } catch { + # @ChrisTitusTech please copy this wiki and change the link below to your copy of the wiki + Write-Error "Failed to mount the image. Error: $($_.Exception.Message)" + Write-Error "This is NOT winutil's problem, your ISO might be corrupt, or there is a problem on the system" + Write-Error "Please refer to this wiki for more details https://github.com/KonTy/winutil/wiki/Error-in-Winutil-MicroWin-during-ISO-mounting" + return + } # storing off values in hidden fields for further steps # there is probably a better way of doing this, I don't have time to figure this out $sync.MicrowinIsoDrive.Text = $driveLetter diff --git a/functions/public/Invoke-WPFMicrowin.ps1 b/functions/public/Invoke-WPFMicrowin.ps1 index de4da7f..cc947aa 100644 --- a/functions/public/Invoke-WPFMicrowin.ps1 +++ b/functions/public/Invoke-WPFMicrowin.ps1 @@ -10,7 +10,40 @@ function Invoke-WPFMicrowin { return } - + # Define the constants for Windows API +Add-Type @" +using System; +using System.Runtime.InteropServices; + +public class PowerManagement { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); + + [FlagsAttribute] + public enum EXECUTION_STATE : uint { + ES_SYSTEM_REQUIRED = 0x00000001, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_CONTINUOUS = 0x80000000, + } +} +"@ + + # Prevent the machine from sleeping + [PowerManagement]::SetThreadExecutionState([PowerManagement]::EXECUTION_STATE::ES_CONTINUOUS -bor [PowerManagement]::EXECUTION_STATE::ES_SYSTEM_REQUIRED -bor [PowerManagement]::EXECUTION_STATE::ES_DISPLAY_REQUIRED) + + # Ask the user where to save the file + $SaveDialog = New-Object System.Windows.Forms.SaveFileDialog + $SaveDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop') + $SaveDialog.Filter = "ISO images (*.iso)|*.iso" + $SaveDialog.ShowDialog() | Out-Null + + if ($SaveDialog.FileName -eq "") { + Write-Host "No file name for the target image was specified" + return + } + + Write-Host "Target ISO location: $($SaveDialog.FileName)" + $index = $sync.MicrowinWindowsFlavors.SelectedValue.Split(":")[0].Trim() Write-Host "Index chosen: '$index' from $($sync.MicrowinWindowsFlavors.SelectedValue)" @@ -107,14 +140,6 @@ function Invoke-WPFMicrowin { Remove-FileOrDirectory -pathToDelete "$($scratchDir)\Windows\SystemApps" -mask "*ParentalControls*" -Directory Write-Host "Removal complete!" - # *************************** Automation black *************************** - # this doesn't work for some reason, this script is not being run at the end of the install - # if someone knows how to fix this, feel free to modify - New-Item -ItemType Directory -Force -Path $scratchDir\Windows\Setup\Scripts\ - "wmic cpu get Name > C:\windows\cpu.txt" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - "wmic bios get serialnumber > C:\windows\SerialNumber.txt" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - "devmgmt.msc /s" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - Write-Host "Create unattend.xml" New-Unattend Write-Host "Done Create unattend.xml" @@ -255,7 +280,7 @@ function Invoke-WPFMicrowin { if (-not (Test-Path -Path "$mountDir\sources\install.wim")) { - Write-Error "Somethig went wrong and '$mountDir\sources\install.wim' doesn't exist. Please report this bug to the devs" + Write-Error "Something went wrong and '$mountDir\sources\install.wim' doesn't exist. Please report this bug to the devs" return } Write-Host "Windows image completed. Continuing with boot.wim." @@ -323,13 +348,23 @@ function Invoke-WPFMicrowin { Write-Host "[INFO] Using oscdimg.exe from: $oscdimgPath" #& oscdimg.exe -m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso - Start-Process -FilePath $oscdimgPath -ArgumentList "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso" -NoNewWindow -Wait + #Start-Process -FilePath $oscdimgPath -ArgumentList "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso" -NoNewWindow -Wait + #Start-Process -FilePath $oscdimgPath -ArgumentList '-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir `"$($SaveDialog.FileName)`"' -NoNewWindow -Wait + $oscdimgProc = New-Object System.Diagnostics.Process + $oscdimgProc.StartInfo.FileName = $oscdimgPath + $oscdimgProc.StartInfo.Arguments = "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir `"$($SaveDialog.FileName)`"" + $oscdimgProc.StartInfo.CreateNoWindow = $True + $oscdimgProc.StartInfo.WindowStyle = "Hidden" + $oscdimgProc.StartInfo.UseShellExecute = $False + $oscdimgProc.Start() + $oscdimgProc.WaitForExit() if ($copyToUSB) { - Write-Host "Copying microwin.iso to the USB drive" - Copy-ToUSB("$env:temp\microwin.iso") - Write-Host "Done Copying microwin.iso to USB drive!" + Write-Host "Copying target ISO to the USB drive" + #Copy-ToUSB("$env:temp\microwin.iso") + Copy-ToUSB("$($SaveDialog.FileName)") + if ($?) { Write-Host "Done Copying target ISO to USB drive!" } else { Write-Host "ISO copy failed." } } Write-Host " _____ " @@ -344,18 +379,20 @@ function Invoke-WPFMicrowin { Write-Host "`n`nPerforming Cleanup..." Remove-Item -Recurse -Force "$($scratchDir)" Remove-Item -Recurse -Force "$($mountDir)" - $msg = "Done. ISO image is located here: $env:temp\microwin.iso" + #$msg = "Done. ISO image is located here: $env:temp\microwin.iso" + $msg = "Done. ISO image is located here: $($SaveDialog.FileName)" Write-Host $msg [System.Windows.MessageBox]::Show($msg, "Winutil", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) } else { Write-Host "ISO creation failed. The "$($mountDir)" directory has not been removed." } - $sync.MicrowinOptionsPanel.Visibility = 'Collapsed' - $sync.MicrowinFinalIsoLocation.Text = "$env:temp\microwin.iso" - + #$sync.MicrowinFinalIsoLocation.Text = "$env:temp\microwin.iso" + $sync.MicrowinFinalIsoLocation.Text = "$($SaveDialog.FileName)" + # Allow the machine to sleep again (optional) + [PowerManagement]::SetThreadExecutionState(0) $sync.ProcessRunning = $false } } \ No newline at end of file diff --git a/functions/public/Invoke-WPFtweaksbutton.ps1 b/functions/public/Invoke-WPFtweaksbutton.ps1 index a037653..90d7b00 100644 --- a/functions/public/Invoke-WPFtweaksbutton.ps1 +++ b/functions/public/Invoke-WPFtweaksbutton.ps1 @@ -27,10 +27,16 @@ function Invoke-WPFtweaksbutton { $sync.ProcessRunning = $true - Set-WinUtilRestorePoint + # Executes first if selected + if ("WPFEssTweaksRestorePoint" -in $Tweaks) { + Invoke-WinUtilTweaks "WPFEssTweaksRestorePoint" + } - Foreach ($tweak in $tweaks){ - Invoke-WinUtilTweaks $tweak + # Execute other selected tweaks + foreach ($tweak in $tweaks) { + if ($tweak -ne "WPFEssTweaksRestorePoint") { + Invoke-WinUtilTweaks $tweak + } } $sync.ProcessRunning = $false diff --git a/logs/test b/logs/test new file mode 100644 index 0000000..e69de29 diff --git a/pester/configs.Tests.ps1 b/pester/configs.Tests.ps1 index 06d560c..fee341e 100644 --- a/pester/configs.Tests.ps1 +++ b/pester/configs.Tests.ps1 @@ -14,7 +14,12 @@ Describe "Config Files" -ForEach @( name = "applications" config = $('{ "winget": "value", - "choco": "value" + "choco": "value", + "category": "value", + "panel": "value", + "content": "value", + "description": "value", + "link": "value" }' | ConvertFrom-Json) }, @{ diff --git a/pester/functions.Tests.ps1 b/pester/functions.Tests.ps1 index 1ec1e28..d02a31a 100644 --- a/pester/functions.Tests.ps1 +++ b/pester/functions.Tests.ps1 @@ -2,13 +2,35 @@ # Tests - Functions #=========================================================================== +# Get all .ps1 files in the functions folder +$ps1Files = Get-ChildItem -Path ./functions -Filter *.ps1 + +# Loop through each file +foreach ($file in $ps1Files) { + # Define the test name + $testName = "Syntax check for $($file.Name)" + + # Define the test script + $testScript = { + # Import the script + . $file.FullName + + # Check if any errors occurred + $scriptError = $error[0] + $scriptError | Should -Be $null + } + + # Add the test to the Pester test suite + Describe $testName $testScript +} + Describe "Functions"{ Get-ChildItem .\functions -Recurse -File | ForEach-Object { context "$($psitem.BaseName)" { BeforeEach -Scriptblock { - . $fullname + . $psitem.FullName } It "Imports with no errors" -TestCases @{ diff --git a/pester/winutil.Tests.ps1 b/pester/winutil.Tests.ps1 deleted file mode 100644 index 1077c1f..0000000 --- a/pester/winutil.Tests.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -# Load Variables needed for testing - -./Compile.ps1 - -$script = Get-Content .\winutil.ps1 -# Remove the part of the script that shows the form, leaving only the variable and function declarations -$script[0..($script.count - 21)] | Out-File .\pester.ps1 - - -BeforeAll { - # Execute the truncated script, bringing the variabes into the current scope - . .\pester.ps1 -} - -Describe "GUI" { - Context "XML" { - It "Imports with no errors" { - $inputXML | should -Not -BeNullOrEmpty - } - } - - Context "Form" { - It "Imports with no errors" { - $sync.Form | should -Not -BeNullOrEmpty - } - } -} diff --git a/screen-install.png b/screen-install.png index eef3c81..69839eb 100755 Binary files a/screen-install.png and b/screen-install.png differ diff --git a/scripts/main.ps1 b/scripts/main.ps1 index b7e8801..5a91b2d 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -66,6 +66,8 @@ foreach ($appName in $sync.configs.applications.PSObject.Properties.Name) { Choco = $appInfo.choco Winget = $appInfo.winget Panel = $appInfo.panel + Link = $appInfo.link + Description = $appInfo.description } if (-not $organizedData.ContainsKey($appInfo.panel)) { @@ -87,8 +89,14 @@ foreach ($panel in $organizedData.Keys) { $sortedApps = $organizedData[$panel][$category].Keys | Sort-Object foreach ($appName in $sortedApps) { $appInfo = $organizedData[$panel][$category][$appName] - - $blockXml += "`n" + if ($null -eq $appInfo.Link) + { + $blockXml += "`n" + } + else + { + $blockXml += "`n" + } } } @@ -131,32 +139,8 @@ $xaml.SelectNodes("//*[@Name]") | ForEach-Object {$sync["$("$($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*" - ){ + 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({ @@ -164,6 +148,31 @@ $sync.keys | ForEach-Object { 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-Host "Let's go: $($Sender.ToolTip)" + }) + } + + } } } @@ -197,21 +206,25 @@ $sync["Form"].Add_Closing({ [System.GC]::Collect() }) + +# Attach the event handler to the Click event +$sync.CheckboxFilterClear.Add_Click({ + $sync.CheckboxFilter.Text = "" + $sync.CheckboxFilterClear.Visibility = "Collapsed" +}) + # 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 - } + if ($_.Key -eq "Escape") + { + $sync.CheckboxFilter.SelectAll() + $sync.CheckboxFilter.Text = "" + $sync.CheckboxFilterClear.Visibility = "Collapsed" + return } # don't ask, I know what I'm doing, just go... @@ -219,31 +232,7 @@ $commonKeyEvents = { { $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" } @@ -259,6 +248,9 @@ $commonKeyEvents = { 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") { @@ -269,6 +261,7 @@ $commonKeyEvents = { $sync.CheckboxFilter.Focus() } } + $sync["Form"].Add_PreViewKeyDown($commonKeyEvents) # adding some left mouse window move on drag capability @@ -287,28 +280,18 @@ $sync["Form"].Add_MouseDoubleClick({ } }) +$sync["Form"].Add_ContentRendered({ - -# 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" + foreach ($proc in (Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like "*tit*" })) { + if ($proc.Id -ne [System.IntPtr]::Zero) { + Write-Debug "MainWindowHandle: $($proc.Id) $($proc.MainWindowTitle) $($proc.MainWindowHandle)" + $windowHandle = $proc.MainWindowHandle + } } - $sync["Form"].Icon = $destinationPath - Try { - [Void][Window] - } Catch { + try { + [void][Window] + } catch { Add-Type @" using System; using System.Runtime.InteropServices; @@ -320,8 +303,7 @@ $sync["Form"].Add_Loaded({ [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 static extern int GetSystemMetrics(int nIndex); } public struct RECT { public int Left; // x position of upper-left corner @@ -331,47 +313,84 @@ $sync["Form"].Add_Loaded({ } "@ } - - $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 + [void][Window]::GetWindowRect($windowHandle, [ref]$rect) $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) + + 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() }) $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 ($sync.CheckboxFilter.Text -ne "") { + $sync.CheckboxFilterClear.Visibility = "Visible" + } + else { + $sync.CheckboxFilterClear.Visibility = "Collapsed" + } + + $filter = Get-WinUtilVariables -Type CheckBox + $CheckBoxes = $sync.GetEnumerator() | Where-Object { $psitem.Key -in $filter } + + 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 } - if ($CheckBox.Value.Content.ToLower().Contains($textToSearch)) { - $CheckBox.Value.Visibility = "Visible" - } - else { + + $textToSearch = $sync.CheckboxFilter.Text + $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" + # 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" + } + } + } + }) -# 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 \ No newline at end of file diff --git a/scripts/start.ps1 b/scripts/start.ps1 index 7ae8eed..948591e 100644 --- a/scripts/start.ps1 +++ b/scripts/start.ps1 @@ -6,6 +6,10 @@ Version : #{replaceme} #> +if (!(Test-Path -Path $ENV:TEMP)) { + New-Item -ItemType Directory -Force -Path $ENV:TEMP +} + Start-Transcript $ENV:TEMP\Winutil.log -Append # Load DLLs diff --git a/winutil.ps1 b/winutil.ps1 index d1534ba..e9dea57 100644 --- a/winutil.ps1 +++ b/winutil.ps1 @@ -10,9 +10,13 @@ Author : Chris Titus @christitustech Runspace Author: @DeveloperDurp GitHub : https://github.com/ChrisTitusTech - Version : 24.01.03 + Version : 24.01.12 #> +if (!(Test-Path -Path $ENV:TEMP)) { + New-Item -ItemType Directory -Force -Path $ENV:TEMP +} + Start-Transcript $ENV:TEMP\Winutil.log -Append # Load DLLs @@ -22,7 +26,7 @@ Add-Type -AssemblyName System.Windows.Forms # Variable to sync between runspaces $sync = [Hashtable]::Synchronized(@{}) $sync.PSScriptRoot = $PSScriptRoot -$sync.version = "24.01.03" +$sync.version = "24.01.12" $sync.configs = @{} $sync.ProcessRunning = $false @@ -119,6 +123,41 @@ function Copy-Files { } } 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-LocalizedYesNoTakeown { <# .SYNOPSIS This function runs takeown.exe and captures its output to extract yes no in a localized Windows @@ -405,30 +444,30 @@ Function Get-WinUtilToggleStatus { function Get-WinUtilVariables { <# - .SYNOPSIS Gets every form object of the provided type .OUTPUTS List containing every object that matches the provided type - #> param ( [Parameter()] - [ValidateSet("CheckBox", "Button", "ToggleButton")] - [string]$Type + [string[]]$Type ) - $keys = $sync.keys | Where-Object {$psitem -like "WPF*"} + $keys = $sync.keys | Where-Object { $_ -like "WPF*" } - if($type){ + if ($Type) { $output = $keys | ForEach-Object { - Try{ - if ($sync["$psitem"].GetType() -like "*$type*"){ + Try { + $objType = $sync["$psitem"].GetType().Name + if ($Type -contains $objType) { Write-Output $psitem } } - Catch{<#I am here so errors don't get outputted for a couple variables that don't have the .GetType() attribute#>} + Catch { + <#I am here so errors don't get outputted for a couple variables that don't have the .GetType() attribute#> + } } return $output } @@ -557,6 +596,607 @@ function Install-WinUtilWinget { throw [WingetFailedInstall]::new('Failed to install') } } +function Invoke-MicroWin-Helper { +<# + + .SYNOPSIS + checking unit tests + + .PARAMETER Name + no parameters + + .EXAMPLE + placeholder + +#> + +} + + +function Remove-Features([switch] $dumpFeatures = $false, [switch] $keepDefender = $false) { +<# + + .SYNOPSIS + Removes certain features from ISO image + + .PARAMETER Name + dumpFeatures - Dumps all features found in the ISO into a file called allfeaturesdump.txt. This file can be examined and used to decide what to remove. + keepDefender - Should Defender be removed from the ISO? + + .EXAMPLE + Remove-Features -keepDefender:$false + +#> + $appxlist = dism /English /image:$scratchDir /Get-Features | Select-String -Pattern "Feature Name : " -CaseSensitive -SimpleMatch + $appxlist = $appxlist -split "Feature Name : " | Where-Object {$_} + if ($dumpFeatures) + { + $appxlist > allfeaturesdump.txt + } + + $appxlist = $appxlist | Where-Object { + $_ -NotLike "*Printing*" -AND + $_ -NotLike "*TelnetClient*" -AND + $_ -NotLike "*PowerShell*" -AND + $_ -NotLike "*NetFx*" + } + + if ($keepDefender) { $appxlist = $appxlist | Where-Object { $_ -NotLike "*Defender*" }} + + foreach($feature in $appxlist) + { + $status = "Removing feature $feature" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$appxlist.Count*100) + Write-Debug "Removing feature $feature" + # dism /image:$scratchDir /Disable-Feature /FeatureName:$feature /Remove /NoRestart > $null + } + Write-Progress -Activity "Removing features" -Status "Ready" -Completed +} + +function Remove-Packages +{ + $appxlist = dism /English /Image:$scratchDir /Get-Packages | Select-String -Pattern "Package Identity : " -CaseSensitive -SimpleMatch + $appxlist = $appxlist -split "Package Identity : " | Where-Object {$_} + + $appxlist = $appxlist | Where-Object { + $_ -NotLike "*ApplicationModel*" -AND + $_ -NotLike "*indows-Client-LanguagePack*" -AND + $_ -NotLike "*LanguageFeatures-Basic*" -AND + $_ -NotLike "*Package_for_ServicingStack*" -AND + $_ -NotLike "*.NET*" -AND + $_ -NotLike "*Store*" -AND + $_ -NotLike "*VCLibs*" -AND + $_ -NotLike "*AAD.BrokerPlugin", + $_ -NotLike "*LockApp*" -AND + $_ -NotLike "*Notepad*" -AND + $_ -NotLike "*immersivecontrolpanel*" -AND + $_ -NotLike "*ContentDeliveryManager*" -AND + $_ -NotLike "*PinningConfirMationDialog*" -AND + $_ -NotLike "*SecHealthUI*" -AND + $_ -NotLike "*SecureAssessmentBrowser*" -AND + $_ -NotLike "*PrintDialog*" -AND + $_ -NotLike "*AssignedAccessLockApp*" -AND + $_ -NotLike "*OOBENetworkConnectionFlow*" -AND + $_ -NotLike "*Apprep.ChxApp*" -AND + $_ -NotLike "*CBS*" -AND + $_ -NotLike "*OOBENetworkCaptivePortal*" -AND + $_ -NotLike "*PeopleExperienceHost*" -AND + $_ -NotLike "*ParentalControls*" -AND + $_ -NotLike "*Win32WebViewHost*" -AND + $_ -NotLike "*InputApp*" -AND + $_ -NotLike "*AccountsControl*" -AND + $_ -NotLike "*AsyncTextService*" -AND + $_ -NotLike "*CapturePicker*" -AND + $_ -NotLike "*CredDialogHost*" -AND + $_ -NotLike "*BioEnrollMent*" -AND + $_ -NotLike "*ShellExperienceHost*" -AND + $_ -NotLike "*DesktopAppInstaller*" -AND + $_ -NotLike "*WebMediaExtensions*" -AND + $_ -NotLike "*WMIC*" -AND + $_ -NotLike "*UI.XaML*" + } + + foreach ($appx in $appxlist) + { + $status = "Removing $appx" + Write-Progress -Activity "Removing Apps" -Status $status -PercentComplete ($counter++/$appxlist.Count*100) + dism /English /image:$scratchDir /Remove-Package /PackageName:$appx /NoRestart + } + Write-Progress -Activity "Removing Apps" -Status "Ready" -Completed +} + +function Remove-ProvisionedPackages([switch] $keepSecurity = $false) +{ +<# + + .SYNOPSIS + Removes AppX packages from a Windows image during MicroWin processing + + .PARAMETER Name + keepSecurity - Boolean that determines whether to keep "Microsoft.SecHealthUI" (Windows Security) in the Windows image + + .EXAMPLE + Remove-ProvisionedPackages -keepSecurity:$false + +#> + $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { + $_.PackageName -NotLike "*AppInstaller*" -AND + $_.PackageName -NotLike "*Store*" -and + $_.PackageName -NotLike "*dism*" -and + $_.PackageName -NotLike "*Foundation*" -and + $_.PackageName -NotLike "*FodMetadata*" -and + $_.PackageName -NotLike "*LanguageFeatures*" -and + $_.PackageName -NotLike "*Notepad*" -and + $_.PackageName -NotLike "*Printing*" -and + $_.PackageName -NotLike "*Wifi*" -and + $_.PackageName -NotLike "*Foundation*" + } + + if ($?) + { + if ($keepSecurity) { $appxProvisionedPackages = $appxProvisionedPackages | Where-Object { $_.PackageName -NotLike "*SecHealthUI*" }} + $counter = 0 + foreach ($appx in $appxProvisionedPackages) + { + $status = "Removing Provisioned $($appx.PackageName)" + Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) + dism /English /image:$scratchDir /Remove-ProvisionedAppxPackage /PackageName:$($appx.PackageName) /NoRestart + } + Write-Progress -Activity "Removing Provisioned Apps" -Status "Ready" -Completed + } + else + { + Write-Host "Could not get Provisioned App information. Skipping process..." + } +} + +function Copy-ToUSB([string] $fileToCopy) +{ + foreach ($volume in Get-Volume) { + if ($volume -and $volume.FileSystemLabel -ieq "ventoy") { + $destinationPath = "$($volume.DriveLetter):\" + #Copy-Item -Path $fileToCopy -Destination $destinationPath -Force + # Get the total size of the file + $totalSize = (Get-Item $fileToCopy).length + + Copy-Item -Path $fileToCopy -Destination $destinationPath -Verbose -Force -Recurse -Container -PassThru | + ForEach-Object { + # Calculate the percentage completed + $completed = ($_.BytesTransferred / $totalSize) * 100 + + # Display the progress bar + Write-Progress -Activity "Copying File" -Status "Progress" -PercentComplete $completed -CurrentOperation ("{0:N2} MB / {1:N2} MB" -f ($_.BytesTransferred / 1MB), ($totalSize / 1MB)) + } + + Write-Host "File copied to Ventoy drive $($volume.DriveLette)" + return + } + } + Write-Host "Ventoy USB Key is not inserted" +} + +function Remove-FileOrDirectory([string] $pathToDelete, [string] $mask = "", [switch] $Directory = $false) +{ + if(([string]::IsNullOrEmpty($pathToDelete))) { return } + if (-not (Test-Path -Path "$($pathToDelete)")) { return } + + $yesNo = Get-LocalizedYesNo + Write-Host "[INFO] In Your local takeown expects '$($yesNo[0])' as a Yes answer." + + # Specify the path to the directory + # $directoryPath = "$($scratchDir)\Windows\System32\LogFiles\WMI\RtBackup" + # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null + # icacls "$($directoryPath)" /q /c /t /reset > $null + # icacls $directoryPath /setowner "*S-1-5-32-544" + # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q + # Remove-Item -Path $directoryPath -Recurse -Force + + # # Grant full control to BUILTIN\Administrators using icacls + # $directoryPath = "$($scratchDir)\Windows\System32\WebThreatDefSvc" + # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null + # icacls "$($directoryPath)" /q /c /t /reset > $null + # icacls $directoryPath /setowner "*S-1-5-32-544" + # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q + # Remove-Item -Path $directoryPath -Recurse -Force + + $itemsToDelete = [System.Collections.ArrayList]::new() + + if ($mask -eq "") + { + Write-Debug "Adding $($pathToDelete) to array." + [void]$itemsToDelete.Add($pathToDelete) + } + else + { + Write-Debug "Adding $($pathToDelete) to array and mask is $($mask)" + if ($Directory) { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse -Directory } + else { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse } + } + + foreach($itemToDelete in $itemsToDelete) + { + $status = "Deleteing $($itemToDelete)" + Write-Progress -Activity "Removing Items" -Status $status -PercentComplete ($counter++/$itemsToDelete.Count*100) + + if (Test-Path -Path "$($itemToDelete)" -PathType Container) + { + $status = "Deleting directory: $($itemToDelete)" + + takeown /r /d $yesNo[0] /a /f "$($itemToDelete)" + icacls "$($itemToDelete)" /q /c /t /reset + icacls $itemToDelete /setowner "*S-1-5-32-544" + icacls $itemToDelete /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q + Remove-Item -Force -Recurse "$($itemToDelete)" + } + elseif (Test-Path -Path "$($itemToDelete)" -PathType Leaf) + { + $status = "Deleting file: $($itemToDelete)" + + takeown /a /f "$($itemToDelete)" + icacls "$($itemToDelete)" /q /c /t /reset + icacls "$($itemToDelete)" /setowner "*S-1-5-32-544" + icacls "$($itemToDelete)" /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q + Remove-Item -Force "$($itemToDelete)" + } + } + Write-Progress -Activity "Removing Items" -Status "Ready" -Completed +} + +function New-Unattend { + + # later if we wont to remove even more bloat EU requires MS to remove everything from English(world) + # Below is an example how to do it we probably should create a drop down with common locals + # + # + # + # + # en-US + # + # en-US + # en-US + # en-US + # en-US + # + # + + # + # + # + # en-US + # en-US + # en-US + # en-US + # + # + # using here string to embedd unattend + # + # 1 + # net user administrator /active:yes + # + + $unattend = @' + + + + + 0 + + + false + + + + + + + 1 + CMD /C echo LAU GG>C:\Windows\LogAuditUser.txt + StartMenu + + + + + + + + true + false + false + true + true + true + 3 + + + + 1 + cmd.exe /c echo 23>c:\windows\csup.txt + + + 2 + CMD /C echo GG>C:\Windows\LogOobeSystem.txt + + + 3 + powershell -ExecutionPolicy Bypass -File c:\windows\FirstStartup.ps1 + + + + + +'@ + $unattend | Out-File -FilePath "$env:temp\unattend.xml" -Force +} + +function New-CheckInstall { + + # using here string to embedd firstrun + $checkInstall = @' + @echo off + if exist "C:\windows\cpu.txt" ( + echo C:\windows\cpu.txt exists + ) else ( + echo C:\windows\cpu.txt does not exist + ) + if exist "C:\windows\SerialNumber.txt" ( + echo C:\windows\SerialNumber.txt exists + ) else ( + echo C:\windows\SerialNumber.txt does not exist + ) + if exist "C:\unattend.xml" ( + echo C:\unattend.xml exists + ) else ( + echo C:\unattend.xml does not exist + ) + if exist "C:\Windows\Setup\Scripts\SetupComplete.cmd" ( + echo C:\Windows\Setup\Scripts\SetupComplete.cmd exists + ) else ( + echo C:\Windows\Setup\Scripts\SetupComplete.cmd does not exist + ) + if exist "C:\Windows\Panther\unattend.xml" ( + echo C:\Windows\Panther\unattend.xml exists + ) else ( + echo C:\Windows\Panther\unattend.xml does not exist + ) + if exist "C:\Windows\System32\Sysprep\unattend.xml" ( + echo C:\Windows\System32\Sysprep\unattend.xml exists + ) else ( + echo C:\Windows\System32\Sysprep\unattend.xml does not exist + ) + if exist "C:\Windows\FirstStartup.ps1" ( + echo C:\Windows\FirstStartup.ps1 exists + ) else ( + echo C:\Windows\FirstStartup.ps1 does not exist + ) + if exist "C:\Windows\winutil.ps1" ( + echo C:\Windows\winutil.ps1 exists + ) else ( + echo C:\Windows\winutil.ps1 does not exist + ) + if exist "C:\Windows\LogSpecialize.txt" ( + echo C:\Windows\LogSpecialize.txt exists + ) else ( + echo C:\Windows\LogSpecialize.txt does not exist + ) + if exist "C:\Windows\LogAuditUser.txt" ( + echo C:\Windows\LogAuditUser.txt exists + ) else ( + echo C:\Windows\LogAuditUser.txt does not exist + ) + if exist "C:\Windows\LogOobeSystem.txt" ( + echo C:\Windows\LogOobeSystem.txt exists + ) else ( + echo C:\Windows\LogOobeSystem.txt does not exist + ) + if exist "c:\windows\csup.txt" ( + echo c:\windows\csup.txt exists + ) else ( + echo c:\windows\csup.txt does not exist + ) + if exist "c:\windows\LogFirstRun.txt" ( + echo c:\windows\LogFirstRun.txt exists + ) else ( + echo c:\windows\LogFirstRun.txt does not exist + ) +'@ + $checkInstall | Out-File -FilePath "$env:temp\checkinstall.cmd" -Force -Encoding Ascii +} + +function New-FirstRun { + + # using here string to embedd firstrun + $firstRun = @' + # Set the global error action preference to continue + $ErrorActionPreference = "Continue" + function Remove-RegistryValue + { + param ( + [Parameter(Mandatory = $true)] + [string]$RegistryPath, + + [Parameter(Mandatory = $true)] + [string]$ValueName + ) + + # Check if the registry path exists + if (Test-Path -Path $RegistryPath) + { + $registryValue = Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue + + # Check if the registry value exists + if ($registryValue) + { + # Remove the registry value + Remove-ItemProperty -Path $RegistryPath -Name $ValueName -Force + Write-Host "Registry value '$ValueName' removed from '$RegistryPath'." + } + else + { + Write-Host "Registry value '$ValueName' not found in '$RegistryPath'." + } + } + else + { + Write-Host "Registry path '$RegistryPath' not found." + } + } + + function Stop-UnnecessaryServices + { + $servicesToExclude = @( + "AudioSrv", + "AudioEndpointBuilder", + "BFE", + "BITS", + "BrokerInfrastructure", + "CDPSvc", + "CDPUserSvc_dc2a4", + "CoreMessagingRegistrar", + "CryptSvc", + "DPS", + "DcomLaunch", + "Dhcp", + "DispBrokerDesktopSvc", + "Dnscache", + "DoSvc", + "DusmSvc", + "EventLog", + "EventSystem", + "FontCache", + "LSM", + "LanmanServer", + "LanmanWorkstation", + "MapsBroker", + "MpsSvc", + "OneSyncSvc_dc2a4", + "Power", + "ProfSvc", + "RpcEptMapper", + "RpcSs", + "SCardSvr", + "SENS", + "SamSs", + "Schedule", + "SgrmBroker", + "ShellHWDetection", + "Spooler", + "SysMain", + "SystemEventsBroker", + "TextInputManagementService", + "Themes", + "TrkWks", + "UserManager", + "VGAuthService", + "VMTools", + "WSearch", + "Wcmsvc", + "WinDefend", + "Winmgmt", + "WlanSvc", + "WpnService", + "WpnUserService_dc2a4", + "cbdhsvc_dc2a4", + "edgeupdate", + "gpsvc", + "iphlpsvc", + "mpssvc", + "nsi", + "sppsvc", + "tiledatamodelsvc", + "vm3dservice", + "webthreatdefusersvc_dc2a4", + "wscsvc" +) + + $runningServices = Get-Service | Where-Object { $servicesToExclude -notcontains $_.Name } + foreach($service in $runningServices) + { + Stop-Service -Name $service.Name -PassThru + Set-Service $service.Name -StartupType Manual + "Stopping service $($service.Name)" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber + } + } + + "FirstStartup has worked" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber + + $Theme = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" + Set-ItemProperty -Path $Theme -Name AppsUseLightTheme -Value 1 + Set-ItemProperty -Path $Theme -Name SystemUsesLightTheme -Value 1 + + # figure this out later how to set updates to security only + #Import-Module -Name PSWindowsUpdate; + #Stop-Service -Name wuauserv + #Set-WUSettings -MicrosoftUpdateEnabled -AutoUpdateOption 'Never' + #Start-Service -Name wuauserv + + Stop-UnnecessaryServices + + $taskbarPath = "$env:AppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar" + # Delete all files on the Taskbar + Get-ChildItem -Path $taskbarPath -File | Remove-Item -Force + Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesRemovedChanges" + Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesChanges" + Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "Favorites" + + # Stop-Process -Name explorer -Force + + $process = Get-Process -Name "explorer" + Stop-Process -InputObject $process + # Wait for the process to exit + Wait-Process -InputObject $process + Start-Sleep -Seconds 3 + + # Delete Edge Icon from the desktop + $edgeShortcutFiles = Get-ChildItem -Path $desktopPath -Filter "*Edge*.lnk" + # Check if Edge shortcuts exist on the desktop + if ($edgeShortcutFiles) + { + foreach ($shortcutFile in $edgeShortcutFiles) + { + # Remove each Edge shortcut + Remove-Item -Path $shortcutFile.FullName -Force + Write-Host "Edge shortcut '$($shortcutFile.Name)' removed from the desktop." + } + } + Remove-Item -Path "$env:USERPROFILE\Desktop\*.lnk" + Remove-Item -Path "C:\Users\Default\Desktop\*.lnk" + + # ************************************************ + # Create WinUtil shortcut on the desktop + # + $desktopPath = "$($env:USERPROFILE)\Desktop" + # Specify the target PowerShell command + $command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'irm https://christitus.com/win | iex'" + # Specify the path for the shortcut + $shortcutPath = Join-Path $desktopPath 'winutil.lnk' + # Create a shell object + $shell = New-Object -ComObject WScript.Shell + + # Create a shortcut object + $shortcut = $shell.CreateShortcut($shortcutPath) + + if (Test-Path -Path "c:\Windows\cttlogo.png") + { + $shortcut.IconLocation = "c:\Windows\cttlogo.png" + } + + # Set properties of the shortcut + $shortcut.TargetPath = "powershell.exe" + $shortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"$command`"" + # Save the shortcut + $shortcut.Save() + Write-Host "Shortcut created at: $shortcutPath" + # + # Done create WinUtil shortcut on the desktop + # ************************************************ + + Start-Process explorer + +'@ + $firstRun | Out-File -FilePath "$env:temp\FirstStartup.ps1" -Force +} function Invoke-WinUtilBingSearch { <# @@ -1029,590 +1669,6 @@ function Invoke-WinUtilVerboseLogon { Write-Warning $psitem.Exception.StackTrace } } -function Remove-Features([switch] $dumpFeatures = $false, [switch] $keepDefender = $false) { -<# - - .SYNOPSIS - Removes certain features from ISO image - - .PARAMETER Name - dumpFeatures - Dumps all features found in the ISO into a file called allfeaturesdump.txt. This file can be examined and used to decide what to remove. - keepDefender - Should Defender be removed from the ISO? - - .EXAMPLE - Remove-Features -keepDefender:$false - -#> - $appxlist = dism /English /image:$scratchDir /Get-Features | Select-String -Pattern "Feature Name : " -CaseSensitive -SimpleMatch - $appxlist = $appxlist -split "Feature Name : " | Where-Object {$_} - if ($dumpFeatures) - { - $appxlist > allfeaturesdump.txt - } - - $appxlist = $appxlist | Where-Object { - $_ -NotLike "*Printing*" -AND - $_ -NotLike "*TelnetClient*" -AND - $_ -NotLike "*PowerShell*" -AND - $_ -NotLike "*NetFx*" - } - - if ($keepDefender) { $appxlist = $appxlist | Where-Object { $_ -NotLike "*Defender*" }} - - foreach($feature in $appxlist) - { - $status = "Removing feature $feature" - Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$appxlist.Count*100) - Write-Debug "Removing feature $feature" - # dism /image:$scratchDir /Disable-Feature /FeatureName:$feature /Remove /NoRestart > $null - } - Write-Progress -Activity "Removing features" -Status "Ready" -Completed -} - -function Remove-Packages -{ - $appxlist = dism /English /Image:$scratchDir /Get-Packages | Select-String -Pattern "Package Identity : " -CaseSensitive -SimpleMatch - $appxlist = $appxlist -split "Package Identity : " | Where-Object {$_} - - $appxlist = $appxlist | Where-Object { - $_ -NotLike "*ApplicationModel*" -AND - $_ -NotLike "*indows-Client-LanguagePack*" -AND - $_ -NotLike "*LanguageFeatures-Basic*" -AND - $_ -NotLike "*Package_for_ServicingStack*" -AND - $_ -NotLike "*.NET*" -AND - $_ -NotLike "*Store*" -AND - $_ -NotLike "*VCLibs*" -AND - $_ -NotLike "*AAD.BrokerPlugin", - $_ -NotLike "*LockApp*" -AND - $_ -NotLike "*Notepad*" -AND - $_ -NotLike "*immersivecontrolpanel*" -AND - $_ -NotLike "*ContentDeliveryManager*" -AND - $_ -NotLike "*PinningConfirMationDialog*" -AND - $_ -NotLike "*SecHealthUI*" -AND - $_ -NotLike "*SecureAssessmentBrowser*" -AND - $_ -NotLike "*PrintDialog*" -AND - $_ -NotLike "*AssignedAccessLockApp*" -AND - $_ -NotLike "*OOBENetworkConnectionFlow*" -AND - $_ -NotLike "*Apprep.ChxApp*" -AND - $_ -NotLike "*CBS*" -AND - $_ -NotLike "*OOBENetworkCaptivePortal*" -AND - $_ -NotLike "*PeopleExperienceHost*" -AND - $_ -NotLike "*ParentalControls*" -AND - $_ -NotLike "*Win32WebViewHost*" -AND - $_ -NotLike "*InputApp*" -AND - $_ -NotLike "*AccountsControl*" -AND - $_ -NotLike "*AsyncTextService*" -AND - $_ -NotLike "*CapturePicker*" -AND - $_ -NotLike "*CredDialogHost*" -AND - $_ -NotLike "*BioEnrollMent*" -AND - $_ -NotLike "*ShellExperienceHost*" -AND - $_ -NotLike "*DesktopAppInstaller*" -AND - $_ -NotLike "*WebMediaExtensions*" -AND - $_ -NotLike "*WMIC*" -AND - $_ -NotLike "*UI.XaML*" - } - - foreach ($appx in $appxlist) - { - $status = "Removing $appx" - Write-Progress -Activity "Removing Apps" -Status $status -PercentComplete ($counter++/$appxlist.Count*100) - dism /English /image:$scratchDir /Remove-Package /PackageName:$appx /NoRestart - } - Write-Progress -Activity "Removing Apps" -Status "Ready" -Completed -} - -function Remove-ProvisionedPackages([switch] $keepSecurity = $false) -{ -<# - - .SYNOPSIS - Removes AppX packages from a Windows image during MicroWin processing - - .PARAMETER Name - keepSecurity - Boolean that determines whether to keep "Microsoft.SecHealthUI" (Windows Security) in the Windows image - - .EXAMPLE - Remove-ProvisionedPackages -keepSecurity:$false - -#> - $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { - $_.PackageName -NotLike "*AppInstaller*" -AND - $_.PackageName -NotLike "*Store*" -and - $_.PackageName -NotLike "*dism*" -and - $_.PackageName -NotLike "*Foundation*" -and - $_.PackageName -NotLike "*FodMetadata*" -and - $_.PackageName -NotLike "*LanguageFeatures*" -and - $_.PackageName -NotLike "*Notepad*" -and - $_.PackageName -NotLike "*Printing*" -and - $_.PackageName -NotLike "*Wifi*" -and - $_.PackageName -NotLike "*Foundation*" - } - - if ($?) - { - if ($keepSecurity) { $appxProvisionedPackages = $appxProvisionedPackages | Where-Object { $_.PackageName -NotLike "*SecHealthUI*" }} - $counter = 0 - foreach ($appx in $appxProvisionedPackages) - { - $status = "Removing Provisioned $($appx.PackageName)" - Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) - dism /English /image:$scratchDir /Remove-ProvisionedAppxPackage /PackageName:$($appx.PackageName) /NoRestart - } - Write-Progress -Activity "Removing Provisioned Apps" -Status "Ready" -Completed - } - else - { - Write-Host "Could not get Provisioned App information. Skipping process..." - } -} - -function Copy-ToUSB([string] $fileToCopy) -{ - foreach ($volume in Get-Volume) { - if ($volume -and $volume.FileSystemLabel -ieq "ventoy") { - $destinationPath = "$($volume.DriveLetter):\" - #Copy-Item -Path $fileToCopy -Destination $destinationPath -Force - # Get the total size of the file - $totalSize = (Get-Item $fileToCopy).length - - Copy-Item -Path $fileToCopy -Destination $destinationPath -Verbose -Force -Recurse -Container -PassThru | - ForEach-Object { - # Calculate the percentage completed - $completed = ($_.BytesTransferred / $totalSize) * 100 - - # Display the progress bar - Write-Progress -Activity "Copying File" -Status "Progress" -PercentComplete $completed -CurrentOperation ("{0:N2} MB / {1:N2} MB" -f ($_.BytesTransferred / 1MB), ($totalSize / 1MB)) - } - - Write-Host "File copied to Ventoy drive $($volume.DriveLette)" - return - } - } - Write-Host "Ventoy USB Key is not inserted" -} - -function Remove-FileOrDirectory([string] $pathToDelete, [string] $mask = "", [switch] $Directory = $false) -{ - if(([string]::IsNullOrEmpty($pathToDelete))) { return } - if (-not (Test-Path -Path "$($pathToDelete)")) { return } - - $yesNo = Get-LocalizedYesNo - Write-Host "[INFO] In Your local takeown expects '$($yesNo[0])' as a Yes answer." - - # Specify the path to the directory - # $directoryPath = "$($scratchDir)\Windows\System32\LogFiles\WMI\RtBackup" - # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null - # icacls "$($directoryPath)" /q /c /t /reset > $null - # icacls $directoryPath /setowner "*S-1-5-32-544" - # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q - # Remove-Item -Path $directoryPath -Recurse -Force - - # # Grant full control to BUILTIN\Administrators using icacls - # $directoryPath = "$($scratchDir)\Windows\System32\WebThreatDefSvc" - # takeown /a /r /d $yesNo[0] /f "$($directoryPath)" > $null - # icacls "$($directoryPath)" /q /c /t /reset > $null - # icacls $directoryPath /setowner "*S-1-5-32-544" - # icacls $directoryPath /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q - # Remove-Item -Path $directoryPath -Recurse -Force - - $itemsToDelete = [System.Collections.ArrayList]::new() - - if ($mask -eq "") - { - Write-Debug "Adding $($pathToDelete) to array." - [void]$itemsToDelete.Add($pathToDelete) - } - else - { - Write-Debug "Adding $($pathToDelete) to array and mask is $($mask)" - if ($Directory) { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse -Directory } - else { $itemsToDelete = Get-ChildItem $pathToDelete -Include $mask -Recurse } - } - - foreach($itemToDelete in $itemsToDelete) - { - $status = "Deleteing $($itemToDelete)" - Write-Progress -Activity "Removing Items" -Status $status -PercentComplete ($counter++/$itemsToDelete.Count*100) - - if (Test-Path -Path "$($itemToDelete)" -PathType Container) - { - $status = "Deleting directory: $($itemToDelete)" - - takeown /r /d $yesNo[0] /a /f "$($itemToDelete)" - icacls "$($itemToDelete)" /q /c /t /reset - icacls $itemToDelete /setowner "*S-1-5-32-544" - icacls $itemToDelete /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q - Remove-Item -Force -Recurse "$($itemToDelete)" - } - elseif (Test-Path -Path "$($itemToDelete)" -PathType Leaf) - { - $status = "Deleting file: $($itemToDelete)" - - takeown /a /f "$($itemToDelete)" - icacls "$($itemToDelete)" /q /c /t /reset - icacls "$($itemToDelete)" /setowner "*S-1-5-32-544" - icacls "$($itemToDelete)" /grant "*S-1-5-32-544:(OI)(CI)F" /t /c /q - Remove-Item -Force "$($itemToDelete)" - } - } - Write-Progress -Activity "Removing Items" -Status "Ready" -Completed -} - -function New-Unattend { - - # later if we wont to remove even more bloat EU requires MS to remove everything from English(world) - # Below is an example how to do it we probably should create a drop down with common locals - # - # - # - # - # en-US - # - # en-US - # en-US - # en-US - # en-US - # - # - - # - # - # - # en-US - # en-US - # en-US - # en-US - # - # - # using here string to embedd unattend - # - # 1 - # net user administrator /active:yes - # - - $unattend = @' - - - - - 0 - - - false - - - - - - - 1 - CMD /C echo LAU GG>C:\Windows\LogAuditUser.txt - StartMenu - - - - - - - - true - false - false - true - true - true - 3 - - - - 1 - cmd.exe /c echo 23>c:\windows\csup.txt - - - 2 - CMD /C echo GG>C:\Windows\LogOobeSystem.txt - - - 3 - powershell -ExecutionPolicy Bypass -File c:\windows\FirstStartup.ps1 - - - - - -'@ - $unattend | Out-File -FilePath "$env:temp\unattend.xml" -Force -} - -function New-CheckInstall { - - # using here string to embedd firstrun - $checkInstall = @' - @echo off - if exist "C:\windows\cpu.txt" ( - echo C:\windows\cpu.txt exists - ) else ( - echo C:\windows\cpu.txt does not exist - ) - if exist "C:\windows\SerialNumber.txt" ( - echo C:\windows\SerialNumber.txt exists - ) else ( - echo C:\windows\SerialNumber.txt does not exist - ) - if exist "C:\unattend.xml" ( - echo C:\unattend.xml exists - ) else ( - echo C:\unattend.xml does not exist - ) - if exist "C:\Windows\Setup\Scripts\SetupComplete.cmd" ( - echo C:\Windows\Setup\Scripts\SetupComplete.cmd exists - ) else ( - echo C:\Windows\Setup\Scripts\SetupComplete.cmd does not exist - ) - if exist "C:\Windows\Panther\unattend.xml" ( - echo C:\Windows\Panther\unattend.xml exists - ) else ( - echo C:\Windows\Panther\unattend.xml does not exist - ) - if exist "C:\Windows\System32\Sysprep\unattend.xml" ( - echo C:\Windows\System32\Sysprep\unattend.xml exists - ) else ( - echo C:\Windows\System32\Sysprep\unattend.xml does not exist - ) - if exist "C:\Windows\FirstStartup.ps1" ( - echo C:\Windows\FirstStartup.ps1 exists - ) else ( - echo C:\Windows\FirstStartup.ps1 does not exist - ) - if exist "C:\Windows\winutil.ps1" ( - echo C:\Windows\winutil.ps1 exists - ) else ( - echo C:\Windows\winutil.ps1 does not exist - ) - if exist "C:\Windows\LogSpecialize.txt" ( - echo C:\Windows\LogSpecialize.txt exists - ) else ( - echo C:\Windows\LogSpecialize.txt does not exist - ) - if exist "C:\Windows\LogAuditUser.txt" ( - echo C:\Windows\LogAuditUser.txt exists - ) else ( - echo C:\Windows\LogAuditUser.txt does not exist - ) - if exist "C:\Windows\LogOobeSystem.txt" ( - echo C:\Windows\LogOobeSystem.txt exists - ) else ( - echo C:\Windows\LogOobeSystem.txt does not exist - ) - if exist "c:\windows\csup.txt" ( - echo c:\windows\csup.txt exists - ) else ( - echo c:\windows\csup.txt does not exist - ) - if exist "c:\windows\LogFirstRun.txt" ( - echo c:\windows\LogFirstRun.txt exists - ) else ( - echo c:\windows\LogFirstRun.txt does not exist - ) -'@ - $checkInstall | Out-File -FilePath "$env:temp\checkinstall.cmd" -Force -Encoding Ascii -} - -function New-FirstRun { - - # using here string to embedd firstrun - $firstRun = @' - # Set the global error action preference to continue - $ErrorActionPreference = "Continue" - function Remove-RegistryValue - { - param ( - [Parameter(Mandatory = $true)] - [string]$RegistryPath, - - [Parameter(Mandatory = $true)] - [string]$ValueName - ) - - # Check if the registry path exists - if (Test-Path -Path $RegistryPath) - { - $registryValue = Get-ItemProperty -Path $RegistryPath -Name $ValueName -ErrorAction SilentlyContinue - - # Check if the registry value exists - if ($registryValue) - { - # Remove the registry value - Remove-ItemProperty -Path $RegistryPath -Name $ValueName -Force - Write-Host "Registry value '$ValueName' removed from '$RegistryPath'." - } - else - { - Write-Host "Registry value '$ValueName' not found in '$RegistryPath'." - } - } - else - { - Write-Host "Registry path '$RegistryPath' not found." - } - } - - function Stop-UnnecessaryServices - { - $servicesAuto = @" - "AudioSrv", - "AudioEndpointBuilder", - "BFE", - "BITS", - "BrokerInfrastructure", - "CDPSvc", - "CDPUserSvc_dc2a4", - "CoreMessagingRegistrar", - "CryptSvc", - "DPS", - "DcomLaunch", - "Dhcp", - "DispBrokerDesktopSvc", - "Dnscache", - "DoSvc", - "DusmSvc", - "EventLog", - "EventSystem", - "FontCache", - "LSM", - "LanmanServer", - "LanmanWorkstation", - "MapsBroker", - "MpsSvc", - "OneSyncSvc_dc2a4", - "Power", - "ProfSvc", - "RpcEptMapper", - "RpcSs", - "SCardSvr", - "SENS", - "SamSs", - "Schedule", - "SgrmBroker", - "ShellHWDetection", - "Spooler", - "SysMain", - "SystemEventsBroker", - "TextInputManagementService", - "Themes", - "TrkWks", - "UserManager", - "VGAuthService", - "VMTools", - "WSearch", - "Wcmsvc", - "WinDefend", - "Winmgmt", - "WlanSvc", - "WpnService", - "WpnUserService_dc2a4", - "cbdhsvc_dc2a4", - "edgeupdate", - "gpsvc", - "iphlpsvc", - "mpssvc", - "nsi", - "sppsvc", - "tiledatamodelsvc", - "vm3dservice", - "webthreatdefusersvc_dc2a4", - "wscsvc" -"@ - - $allServices = Get-Service | Where-Object { $_.StartType -eq "Automatic" -and $servicesAuto -NotContains $_.Name} - foreach($service in $allServices) - { - Stop-Service -Name $service.Name -PassThru - Set-Service $service.Name -StartupType Manual - "Stopping service $($service.Name)" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber - } - } - - "FirstStartup has worked" | Out-File -FilePath c:\windows\LogFirstRun.txt -Append -NoClobber - - $Theme = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" - Set-ItemProperty -Path $Theme -Name AppsUseLightTheme -Value 1 - Set-ItemProperty -Path $Theme -Name SystemUsesLightTheme -Value 1 - - # figure this out later how to set updates to security only - #Import-Module -Name PSWindowsUpdate; - #Stop-Service -Name wuauserv - #Set-WUSettings -MicrosoftUpdateEnabled -AutoUpdateOption 'Never' - #Start-Service -Name wuauserv - - Stop-UnnecessaryServices - - $taskbarPath = "$env:AppData\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar" - # Delete all files on the Taskbar - Get-ChildItem -Path $taskbarPath -File | Remove-Item -Force - Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesRemovedChanges" - Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "FavoritesChanges" - Remove-RegistryValue -RegistryPath "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband" -ValueName "Favorites" - - # Stop-Process -Name explorer -Force - - $process = Get-Process -Name "explorer" - Stop-Process -InputObject $process - # Wait for the process to exit - Wait-Process -InputObject $process - Start-Sleep -Seconds 3 - - # Delete Edge Icon from the desktop - $edgeShortcutFiles = Get-ChildItem -Path $desktopPath -Filter "*Edge*.lnk" - # Check if Edge shortcuts exist on the desktop - if ($edgeShortcutFiles) - { - foreach ($shortcutFile in $edgeShortcutFiles) - { - # Remove each Edge shortcut - Remove-Item -Path $shortcutFile.FullName -Force - Write-Host "Edge shortcut '$($shortcutFile.Name)' removed from the desktop." - } - } - Remove-Item -Path "$env:USERPROFILE\Desktop\*.lnk" - Remove-Item -Path "C:\Users\Default\Desktop\*.lnk" - - # ************************************************ - # Create WinUtil shortcut on the desktop - # - $desktopPath = "$($env:USERPROFILE)\Desktop" - # Specify the target PowerShell command - $command = "powershell.exe -NoProfile -ExecutionPolicy Bypass -Command 'irm https://christitus.com/win | iex'" - # Specify the path for the shortcut - $shortcutPath = Join-Path $desktopPath 'winutil.lnk' - # Create a shell object - $shell = New-Object -ComObject WScript.Shell - - # Create a shortcut object - $shortcut = $shell.CreateShortcut($shortcutPath) - - if (Test-Path -Path "c:\Windows\cttlogo.png") - { - $shortcut.IconLocation = "c:\Windows\cttlogo.png" - } - - # Set properties of the shortcut - $shortcut.TargetPath = "powershell.exe" - $shortcut.Arguments = "-NoProfile -ExecutionPolicy Bypass -Command `"$command`"" - # Save the shortcut - $shortcut.Save() - Write-Host "Shortcut created at: $shortcutPath" - # - # Done create WinUtil shortcut on the desktop - # ************************************************ - - Start-Process explorer - -'@ - $firstRun | Out-File -FilePath "$env:temp\FirstStartup.ps1" -Force -} function Remove-WinUtilAPPX { <# @@ -1734,46 +1790,6 @@ function Set-WinUtilRegistry { Write-Warning $psitem.Exception.StackTrace } } -function Set-WinUtilRestorePoint { - <# - - .SYNOPSIS - Creates a Restore Point - - #> - - # Check if the user has administrative privileges - if (-Not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { - Write-Host "Please run this script as an administrator." - return - } - - # Check if System Restore is enabled for the main drive - try { - # Try getting restore points to check if System Restore is enabled - Enable-ComputerRestore -Drive "$env:SystemDrive" - } catch { - Write-Host "An error occurred while enabling System Restore: $_" - } - - # Check if the SystemRestorePointCreationFrequency value exists - $exists = Get-ItemProperty -path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -name "SystemRestorePointCreationFrequency" -ErrorAction SilentlyContinue - if($null -eq $exists){ - write-host 'Changing system to allow multiple restore points per day' - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -Name "SystemRestorePointCreationFrequency" -Value "0" -Type DWord -Force -ErrorAction Stop | Out-Null - } - - # Get all the restore points for the current day - $existingRestorePoints = Get-ComputerRestorePoint | Where-Object { $_.CreationTime.Date -eq (Get-Date).Date } - - # Check if there is already a restore point created today - if ($existingRestorePoints.Count -eq 0) { - $description = "System Restore Point created by WinUtil" - - Checkpoint-Computer -Description $description -RestorePointType "MODIFY_SETTINGS" - Write-Host -ForegroundColor Green "System Restore Point Created Successfully" - } -} function Set-WinUtilScheduledTask { <# @@ -2022,10 +2038,10 @@ function Invoke-WPFButton { "WPFGetInstalledTweaks" {Invoke-WPFGetInstalled -CheckBox "tweaks"} "WPFGetIso" {Invoke-WPFGetIso} "WPFMicrowin" {Invoke-WPFMicrowin} - "WPFCloseButton" {Invoke-CloseButton} + "WPFCloseButton" {Invoke-WPFCloseButton} } } -function Invoke-CloseButton { +function Invoke-WPFCloseButton { <# @@ -2394,11 +2410,19 @@ function Invoke-WPFGetIso { return } - Write-Host "Mounting Iso. Please wait." - $mountedISO = Mount-DiskImage -PassThru "$filePath" - Write-Host "Done mounting Iso $mountedISO" - $driveLetter = (Get-Volume -DiskImage $mountedISO).DriveLetter - Write-Host "Iso mounted to '$driveLetter'" + try { + Write-Host "Mounting Iso. Please wait." + $mountedISO = Mount-DiskImage -PassThru "$filePath" + Write-Host "Done mounting Iso $mountedISO" + $driveLetter = (Get-Volume -DiskImage $mountedISO).DriveLetter + Write-Host "Iso mounted to '$driveLetter'" + } catch { + # @ChrisTitusTech please copy this wiki and change the link below to your copy of the wiki + Write-Error "Failed to mount the image. Error: $($_.Exception.Message)" + Write-Error "This is NOT winutil's problem, your ISO might be corrupt, or there is a problem on the system" + Write-Error "Please refer to this wiki for more details https://github.com/KonTy/winutil/wiki/Error-in-Winutil-MicroWin-during-ISO-mounting" + return + } # storing off values in hidden fields for further steps # there is probably a better way of doing this, I don't have time to figure this out $sync.MicrowinIsoDrive.Text = $driveLetter @@ -2600,7 +2624,40 @@ function Invoke-WPFMicrowin { return } - + # Define the constants for Windows API +Add-Type @" +using System; +using System.Runtime.InteropServices; + +public class PowerManagement { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); + + [FlagsAttribute] + public enum EXECUTION_STATE : uint { + ES_SYSTEM_REQUIRED = 0x00000001, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_CONTINUOUS = 0x80000000, + } +} +"@ + + # Prevent the machine from sleeping + [PowerManagement]::SetThreadExecutionState([PowerManagement]::EXECUTION_STATE::ES_CONTINUOUS -bor [PowerManagement]::EXECUTION_STATE::ES_SYSTEM_REQUIRED -bor [PowerManagement]::EXECUTION_STATE::ES_DISPLAY_REQUIRED) + + # Ask the user where to save the file + $SaveDialog = New-Object System.Windows.Forms.SaveFileDialog + $SaveDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop') + $SaveDialog.Filter = "ISO images (*.iso)|*.iso" + $SaveDialog.ShowDialog() | Out-Null + + if ($SaveDialog.FileName -eq "") { + Write-Host "No file name for the target image was specified" + return + } + + Write-Host "Target ISO location: $($SaveDialog.FileName)" + $index = $sync.MicrowinWindowsFlavors.SelectedValue.Split(":")[0].Trim() Write-Host "Index chosen: '$index' from $($sync.MicrowinWindowsFlavors.SelectedValue)" @@ -2697,14 +2754,6 @@ function Invoke-WPFMicrowin { Remove-FileOrDirectory -pathToDelete "$($scratchDir)\Windows\SystemApps" -mask "*ParentalControls*" -Directory Write-Host "Removal complete!" - # *************************** Automation black *************************** - # this doesn't work for some reason, this script is not being run at the end of the install - # if someone knows how to fix this, feel free to modify - New-Item -ItemType Directory -Force -Path $scratchDir\Windows\Setup\Scripts\ - "wmic cpu get Name > C:\windows\cpu.txt" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - "wmic bios get serialnumber > C:\windows\SerialNumber.txt" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - "devmgmt.msc /s" | Out-File -FilePath "$($scratchDir)\Windows\Setup\Scripts\SetupComplete.cmd" -NoClobber -Append - Write-Host "Create unattend.xml" New-Unattend Write-Host "Done Create unattend.xml" @@ -2845,7 +2894,7 @@ function Invoke-WPFMicrowin { if (-not (Test-Path -Path "$mountDir\sources\install.wim")) { - Write-Error "Somethig went wrong and '$mountDir\sources\install.wim' doesn't exist. Please report this bug to the devs" + Write-Error "Something went wrong and '$mountDir\sources\install.wim' doesn't exist. Please report this bug to the devs" return } Write-Host "Windows image completed. Continuing with boot.wim." @@ -2913,13 +2962,23 @@ function Invoke-WPFMicrowin { Write-Host "[INFO] Using oscdimg.exe from: $oscdimgPath" #& oscdimg.exe -m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso - Start-Process -FilePath $oscdimgPath -ArgumentList "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso" -NoNewWindow -Wait + #Start-Process -FilePath $oscdimgPath -ArgumentList "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir $env:temp\microwin.iso" -NoNewWindow -Wait + #Start-Process -FilePath $oscdimgPath -ArgumentList '-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir `"$($SaveDialog.FileName)`"' -NoNewWindow -Wait + $oscdimgProc = New-Object System.Diagnostics.Process + $oscdimgProc.StartInfo.FileName = $oscdimgPath + $oscdimgProc.StartInfo.Arguments = "-m -o -u2 -udfver102 -bootdata:2#p0,e,b$mountDir\boot\etfsboot.com#pEF,e,b$mountDir\efi\microsoft\boot\efisys.bin $mountDir `"$($SaveDialog.FileName)`"" + $oscdimgProc.StartInfo.CreateNoWindow = $True + $oscdimgProc.StartInfo.WindowStyle = "Hidden" + $oscdimgProc.StartInfo.UseShellExecute = $False + $oscdimgProc.Start() + $oscdimgProc.WaitForExit() if ($copyToUSB) { - Write-Host "Copying microwin.iso to the USB drive" - Copy-ToUSB("$env:temp\microwin.iso") - Write-Host "Done Copying microwin.iso to USB drive!" + Write-Host "Copying target ISO to the USB drive" + #Copy-ToUSB("$env:temp\microwin.iso") + Copy-ToUSB("$($SaveDialog.FileName)") + if ($?) { Write-Host "Done Copying target ISO to USB drive!" } else { Write-Host "ISO copy failed." } } Write-Host " _____ " @@ -2934,18 +2993,20 @@ function Invoke-WPFMicrowin { Write-Host "`n`nPerforming Cleanup..." Remove-Item -Recurse -Force "$($scratchDir)" Remove-Item -Recurse -Force "$($mountDir)" - $msg = "Done. ISO image is located here: $env:temp\microwin.iso" + #$msg = "Done. ISO image is located here: $env:temp\microwin.iso" + $msg = "Done. ISO image is located here: $($SaveDialog.FileName)" Write-Host $msg [System.Windows.MessageBox]::Show($msg, "Winutil", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) } else { Write-Host "ISO creation failed. The "$($mountDir)" directory has not been removed." } - $sync.MicrowinOptionsPanel.Visibility = 'Collapsed' - $sync.MicrowinFinalIsoLocation.Text = "$env:temp\microwin.iso" - + #$sync.MicrowinFinalIsoLocation.Text = "$env:temp\microwin.iso" + $sync.MicrowinFinalIsoLocation.Text = "$($SaveDialog.FileName)" + # Allow the machine to sleep again (optional) + [PowerManagement]::SetThreadExecutionState(0) $sync.ProcessRunning = $false } } @@ -3219,10 +3280,16 @@ function Invoke-WPFtweaksbutton { $sync.ProcessRunning = $true - Set-WinUtilRestorePoint + # Executes first if selected + if ("WPFEssTweaksRestorePoint" -in $Tweaks) { + Invoke-WinUtilTweaks "WPFEssTweaksRestorePoint" + } - Foreach ($tweak in $tweaks){ - Invoke-WinUtilTweaks $tweak + # Execute other selected tweaks + foreach ($tweak in $tweaks) { + if ($tweak -ne "WPFEssTweaksRestorePoint") { + Invoke-WinUtilTweaks $tweak + } } $sync.ProcessRunning = $false @@ -3713,11 +3780,20 @@ $inputXML = ' + Title="Chris Titus Tech''s Windows Utility" Height="800" Width="1280"> + + + + + + + + + + + + + + +