diff --git a/.github/workflows/compile-check.yaml b/.github/workflows/compile-check.yaml new file mode 100644 index 00000000..ff2d2fa1 --- /dev/null +++ b/.github/workflows/compile-check.yaml @@ -0,0 +1,22 @@ +name: Compile & Check + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: # Manual trigger added + workflow_call: # Allow other Actions to call this workflow + +jobs: + Compile-and-Check: + runs-on: windows-latest + steps: + - name: Checkout Sources + uses: actions/checkout@v4 + + - name: Compile and Syntaxcheck winutil.ps1 + shell: pwsh + run: | + Set-ExecutionPolicy Bypass -Scope Process -Force; ./Compile.ps1 + continue-on-error: false # Directly fail the job on error, removing the need for a separate check \ No newline at end of file diff --git a/.github/workflows/compile.yaml b/.github/workflows/compile.yaml deleted file mode 100644 index 5e68042a..00000000 --- a/.github/workflows/compile.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Compile - -on: - push: - branches: - - main - - test* - workflow_dispatch: # Manual trigger added - -jobs: - build-runspace: - runs-on: windows-latest - env: - CERTIFICATE_BASE64: ${{ secrets.CERTIFICATE_BASE64 }} - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - - name: Compile project - shell: pwsh - run: | - Set-ExecutionPolicy Bypass -Scope Process -Force; ./Compile.ps1 - continue-on-error: false # Directly fail the job on error, removing the need for a separate check - - name: Create and import code signing certificate - shell: pwsh - run: | - [System.IO.File]::WriteAllBytes("$env:USERPROFILE\code-signing-cert.pfx", [System.Convert]::FromBase64String("$env:CERTIFICATE_BASE64")) - Import-PfxCertificate -FilePath "$env:USERPROFILE\code-signing-cert.pfx" -CertStoreLocation Cert:\CurrentUser\My - - name: Code sign winutil.ps1 - shell: pwsh - run: | - $cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1 - if ($null -eq $cert) { throw "Code signing certificate not found" } - Set-AuthenticodeSignature -FilePath ./winutil.ps1 -Certificate $cert - - name: Verify code signature - shell: pwsh - run: | - $signature = Get-AuthenticodeSignature -FilePath ./winutil.ps1 - if ($signature.Status -ne 'Valid') { throw "Code signing failed" } - - name: Upload winutil.ps1 as artifact - uses: actions/upload-artifact@v2 - with: - name: winutil - path: ./winutil.ps1 - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: Compile Winutil - if: success() diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yaml similarity index 100% rename from .github/workflows/github-pages.yml rename to .github/workflows/github-pages.yaml diff --git a/.github/workflows/pre-release.yaml b/.github/workflows/pre-release.yaml index 7a3e1327..56210e4a 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -14,6 +14,12 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 + - name: Compile project + shell: pwsh + run: | + Set-ExecutionPolicy Bypass -Scope Process -Force; ./Compile.ps1 + continue-on-error: false # Directly fail the job on error, removing the need for a separate check + - name: Set Version to Todays Date id: extract_version run: | @@ -41,6 +47,31 @@ jobs: } shell: pwsh + - name: Create and import code signing certificate + shell: pwsh + run: | + [System.IO.File]::WriteAllBytes("$env:USERPROFILE\code-signing-cert.pfx", [System.Convert]::FromBase64String("$env:CERTIFICATE_BASE64")) + Import-PfxCertificate -FilePath "$env:USERPROFILE\code-signing-cert.pfx" -CertStoreLocation Cert:\CurrentUser\My + + - name: Code sign winutil.ps1 + shell: pwsh + run: | + $cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1 + if ($null -eq $cert) { throw "Code signing certificate not found" } + Set-AuthenticodeSignature -FilePath ./winutil.ps1 -Certificate $cert + + - name: Verify code signature + shell: pwsh + run: | + $signature = Get-AuthenticodeSignature -FilePath ./winutil.ps1 + if ($signature.Status -ne 'Valid') { throw "Code signing failed" } + + - name: Upload winutil.ps1 as artifact + uses: actions/upload-artifact@v2 + with: + name: winutil + path: ./winutil.ps1 + - name: Create and Upload Release id: create_release uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/remove-winutil.yaml b/.github/workflows/remove-winutil.yaml new file mode 100644 index 00000000..69396a3e --- /dev/null +++ b/.github/workflows/remove-winutil.yaml @@ -0,0 +1,34 @@ +name: Remove winutil.ps1 if included in a Push + +on: + push: + branches: + - '**' + +jobs: + check-and-delete-file: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check if winutil.ps1 exists + id: check_existence + run: | + if [ -f "winutil.ps1" ]; then + echo "winutil_exists=true" >> $GITHUB_OUTPUT + else + echo "winutil_exists=false" >> $GITHUB_OUTPUT + fi + + - name: Delete winutil.ps1 if it exists + if: steps.check_existence.outputs.winutil_exists == 'true' + run: | + git config --global user.email "winutil-action@noreply.github.com" + git config --global user.name "winutil-action" + git rm winutil.ps1 + git commit -m "Delete winutil.ps1 as it is not allowed" + git push origin HEAD:${{ github.ref }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yaml similarity index 100% rename from .github/workflows/sponsors.yml rename to .github/workflows/sponsors.yaml diff --git a/Compile.ps1 b/Compile.ps1 index d1440219..0ef662a6 100644 --- a/Compile.ps1 +++ b/Compile.ps1 @@ -144,6 +144,16 @@ if ($Debug) { Set-Content -Path "$workingdir\$scriptname" -Value ($script_content -join "`r`n") -Encoding ascii Write-Progress -Activity "Compiling" -Completed +Update-Progress -Activity "Validating" -StatusMessage "Checking winutil.ps1 Syntax" -Percent 0 +try { + $null = Get-Command -Syntax .\winutil.ps1 +} +catch { + Write-Warning "Syntax Validation for 'winutil.ps1' has failed" + Write-Host "$($Error[0])" -ForegroundColor Red +} +Write-Progress -Activity "Validating" -Completed + if ($run) { try { Start-Process -FilePath "pwsh" -ArgumentList "$workingdir\$scriptname" diff --git a/winutil.ps1 b/winutil.ps1 deleted file mode 100644 index 2158ed4f..00000000 --- a/winutil.ps1 +++ /dev/null @@ -1,16256 +0,0 @@ -################################################################################################################ -### ### -### 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.08.07 -#> -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.08.07" -$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, position=0)] - [string]$bitmapPath, - [Parameter(Mandatory, 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, position=0)] - [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 - . "$($sync.PSScriptRoot)\functions\private\Get-WPFObjectName.ps1" - - $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 - - - - - - - - - - - -