diff --git a/.github/workflows/close-discussion.yml b/.github/workflows/close-discussion-on-pr.yaml similarity index 97% rename from .github/workflows/close-discussion.yml rename to .github/workflows/close-discussion-on-pr.yaml index b0209efb..bd821b94 100644 --- a/.github/workflows/close-discussion.yml +++ b/.github/workflows/close-discussion-on-pr.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Check if PR was merged if: github.event.pull_request.merged == true diff --git a/.github/workflows/issue-slash-commands.yaml b/.github/workflows/close-issue-command.yaml similarity index 100% rename from .github/workflows/issue-slash-commands.yaml rename to .github/workflows/close-issue-command.yaml 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..75f69e2f 100644 --- a/.github/workflows/pre-release.yaml +++ b/.github/workflows/pre-release.yaml @@ -10,10 +10,18 @@ on: jobs: build-runspace: runs-on: windows-latest + env: + CERTIFICATE_BASE64: ${{ secrets.CERTIFICATE_BASE64 }} steps: - 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 +49,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@v4 + 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/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index df5f3d5d..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,44 +0,0 @@ -name: Release WinUtil - -on: - workflow_dispatch: # Manual trigger added - -jobs: - build-runspace: - runs-on: windows-latest - outputs: - version: ${{ steps.extract_version.outputs.version }} - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Extract Version from winutil.ps1 - id: extract_version - run: | - $version = '' - Get-Content ./winutil.ps1 -TotalCount 30 | ForEach-Object { - if ($_ -match 'Version\s*:\s*(\d{2}\.\d{2}\.\d{2})') { - $version = $matches[1] - echo "version=$version" >> $GITHUB_OUTPUT - break - } - } - if (-not $version) { - Write-Error "Version not found in winutil.ps1" - exit 1 - } - shell: pwsh - - - name: Create and Upload Release - id: create_release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.extract_version.outputs.version }} - name: Release ${{ steps.extract_version.outputs.version }} - body: "![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/winutil/${{ steps.extract_version.outputs.version }}/winutil.ps1)" - append_body: true - files: ./winutil.ps1 - prerelease: false - make_latest: "true" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 8009ec31..fdb59e2b 100644 --- a/Compile.ps1 +++ b/Compile.ps1 @@ -43,7 +43,7 @@ if (-NOT $SkipPreprocessing) { $preprocessingFilePath = ".\tools\Invoke-Preprocessing.ps1" . "$(($workingdir -replace ('\\$', '')) + '\' + ($preprocessingFilePath -replace ('\.\\', '')))" - $excludedFiles = @('.\.git\', '.\.gitignore', '.\.gitattributes', '.\.github\CODEOWNERS', '.\LICENSE', '.\winutil.ps1', "$preprocessingFilePath", '*.png', '*.exe') + $excludedFiles = @('.\.git\', '.\.gitignore', '.\.gitattributes', '.\.github\CODEOWNERS', '.\LICENSE', "$preprocessingFilePath", '*.png', '*.exe') $msg = "Pre-req: Code Formatting" Invoke-Preprocessing -WorkingDir "$workingdir" -ExcludedFiles $excludedFiles -ProgressStatusMessage $msg } @@ -152,6 +152,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/IntegratedServicesRegionPolicySet.json b/IntegratedServicesRegionPolicySet.json deleted file mode 100644 index 393e9920..00000000 --- a/IntegratedServicesRegionPolicySet.json +++ /dev/null @@ -1 +0,0 @@ - "enabled": ["AT", "BE", "BG", "CH", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", "FR", "GF", "GP", "GR", "HR", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MT", "MQ", "NL", "NO", "PL", "PT", "RE", "RO", "SE", "SI", "SK", "YT"] diff --git a/config/tweaks.json b/config/tweaks.json index 43098ec3..08ccb95a 100644 --- a/config/tweaks.json +++ b/config/tweaks.json @@ -2557,14 +2557,14 @@ }, "WPFTweaksRemoveEdge": { "Content": "Remove Microsoft Edge", - "Description": "Removes MS Edge when it gets reinstalled by updates. Credit: Techie Jack", + "Description": "Removes MS Edge when it gets reinstalled by updates. Credit: Psyirius", "category": "z__Advanced Tweaks - CAUTION", "panel": "1", "Order": "a029_", "InvokeScript": [ " - Uninstall-WinUtilEdgeBrowser - " + Uninstall-WinUtilEdgeBrowser + " ], "UndoScript": [ " diff --git a/edgeremoval.ps1 b/edgeremoval.ps1 deleted file mode 100644 index 362fe396..00000000 --- a/edgeremoval.ps1 +++ /dev/null @@ -1,138 +0,0 @@ -$msedgeProcess = Get-Process -Name "msedge" -ErrorAction SilentlyContinue -$widgetsProcess = Get-Process -Name "widgets" -ErrorAction SilentlyContinue -# Checking if Microsoft Edge is running -if ($msedgeProcess) { - Stop-Process -Name "msedge" -Force -} else { - Write-Output "msedge process is not running." -} -# Checking if Widgets is running -if ($widgetsProcess) { - Stop-Process -Name "widgets" -Force -} else { - Write-Output "widgets process is not running." -} - -function Uninstall-Process { - param ( - [Parameter(Mandatory = $true)] - [string]$Key - ) - - $originalNation = [microsoft.win32.registry]::GetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', [Microsoft.Win32.RegistryValueKind]::String) - - # Set Nation to 84 (France) temporarily - [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', 68, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null - - # credits to he3als for the Acl commands - $fileName = "IntegratedServicesRegionPolicySet.json" - $pathISRPS = [Environment]::SystemDirectory + "\" + $fileName - $aclISRPS = Get-Acl -Path $pathISRPS - $aclISRPSBackup = [System.Security.AccessControl.FileSecurity]::new() - $aclISRPSBackup.SetSecurityDescriptorSddlForm($acl.Sddl) - if (Test-Path -Path $pathISRPS) { - try { - $admin = [System.Security.Principal.NTAccount]$(New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value - - $aclISRPS.SetOwner($admin) - $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($admin, 'FullControl', 'Allow') - $aclISRPS.AddAccessRule($rule) - Set-Acl -Path $pathISRPS -AclObject $aclISRPS - - Rename-Item -Path $pathISRPS -NewName ($fileName + '.bak') -Force - } - catch { - Write-Error "[$Mode] Failed to set owner for $pathISRPS" - } - } - - $baseKey = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' - $registryPath = $baseKey + '\ClientState\' + $Key - - if (!(Test-Path -Path $registryPath)) { - Write-Host "[$Mode] Registry key not found: $registryPath" - return - } - - Remove-ItemProperty -Path $registryPath -Name "experiment_control_labels" -ErrorAction SilentlyContinue | Out-Null - - $uninstallString = (Get-ItemProperty -Path $registryPath).UninstallString - $uninstallArguments = (Get-ItemProperty -Path $registryPath).UninstallArguments - - if ([string]::IsNullOrEmpty($uninstallString) -or [string]::IsNullOrEmpty($uninstallArguments)) { - Write-Host "[$Mode] Cannot find uninstall methods for $Mode" - return - } - - $uninstallArguments += " --force-uninstall --delete-profile" - - # $uninstallCommand = "`"$uninstallString`"" + $uninstallArguments - if (!(Test-Path -Path $uninstallString)) { - Write-Host "[$Mode] setup.exe not found at: $uninstallString" - return - } - Start-Process -FilePath $uninstallString -ArgumentList $uninstallArguments -Wait -NoNewWindow -Verbose - - # Restore Acl - if (Test-Path -Path ($pathISRPS + '.bak')) { - Rename-Item -Path ($pathISRPS + '.bak') -NewName $fileName -Force - Set-Acl -Path $pathISRPS -AclObject $aclISRPSBackup - } - - # Restore Nation - [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', $originalNation, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null - - if ((Get-ItemProperty -Path $baseKey).IsEdgeStableUninstalled -eq 1) { - Write-Host "[$Mode] Edge Stable has been successfully uninstalled" - } -} - -function Uninstall-Edge { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - [microsoft.win32.registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev", "AllowUninstall", 1, [Microsoft.Win32.RegistryValueKind]::DWord) | Out-Null - - Uninstall-Process -Key '{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}' - - @( "$env:ProgramData\Microsoft\Windows\Start Menu\Programs", - "$env:PUBLIC\Desktop", - "$env:USERPROFILE\Desktop" ) | ForEach-Object { - $shortcutPath = Join-Path -Path $_ -ChildPath "Microsoft Edge.lnk" - if (Test-Path -Path $shortcutPath) { - Remove-Item -Path $shortcutPath -Force - } - } - -} - -function Uninstall-WebView { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - # Force to use system-wide WebView2 - # [microsoft.win32.registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\WebView2\BrowserExecutableFolder", "*", "%%SystemRoot%%\System32\Microsoft-Edge-WebView") - - Uninstall-Process -Key '{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' -} - -function Uninstall-EdgeUpdate { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - $registryPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' - if (!(Test-Path -Path $registryPath)) { - Write-Host "Registry key not found: $registryPath" - return - } - $uninstallCmdLine = (Get-ItemProperty -Path $registryPath).UninstallCmdLine - - if ([string]::IsNullOrEmpty($uninstallCmdLine)) { - Write-Host "Cannot find uninstall methods for $Mode" - return - } - - Write-Output "Uninstalling: $uninstallCmdLine" - Start-Process cmd.exe "/c $uninstallCmdLine" -WindowStyle Hidden -Wait -} - -Uninstall-Edge - # "WebView" { Uninstall-WebView } - # "EdgeUpdate" { Uninstall-EdgeUpdate } diff --git a/functions/private/ConvertTo-Icon.ps1 b/functions/private/ConvertTo-Icon.ps1 index ce06327e..a4017755 100644 --- a/functions/private/ConvertTo-Icon.ps1 +++ b/functions/private/ConvertTo-Icon.ps1 @@ -51,7 +51,7 @@ function ConvertTo-Icon { # Handle the thrown exception here... } - This Example make use of '-overrideIconFile' Optional Parameter, the default for this paramter is $true. + This Example make use of '-overrideIconFile' Optional Parameter, the default for this parameter 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. diff --git a/functions/private/Uninstall-WinUtilEdgeBrowser.ps1 b/functions/private/Uninstall-WinUtilEdgeBrowser.ps1 index ccc22a93..4d004f39 100644 --- a/functions/private/Uninstall-WinUtilEdgeBrowser.ps1 +++ b/functions/private/Uninstall-WinUtilEdgeBrowser.ps1 @@ -1,152 +1,101 @@ Function Uninstall-WinUtilEdgeBrowser { - <# - .SYNOPSIS - This will uninstall edge by changing the region to Ireland and uninstalling edge the changing it back - + Uninstall the Edge Browser (Chromium) from the system in an elegant way. + .DESCRIPTION + This will switch up the region to one of the EEA countries temporarily and uninstall the Edge Browser (Chromium). #> -$msedgeProcess = Get-Process -Name "msedge" -ErrorAction SilentlyContinue -$widgetsProcess = Get-Process -Name "widgets" -ErrorAction SilentlyContinue -# Checking if Microsoft Edge is running -if ($msedgeProcess) { - Stop-Process -Name "msedge" -Force -} else { - Write-Output "msedge process is not running." -} -# Checking if Widgets is running -if ($widgetsProcess) { - Stop-Process -Name "widgets" -Force -} else { - Write-Output "widgets process is not running." -} + function Uninstall-EdgeClient { + param ( + [Parameter(Mandatory = $true)] + [string]$Key + ) -function Uninstall-Process { - param ( - [Parameter(Mandatory = $true)] - [string]$Key - ) + $originalNation = [microsoft.win32.registry]::GetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', [Microsoft.Win32.RegistryValueKind]::String) - $originalNation = [microsoft.win32.registry]::GetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', [Microsoft.Win32.RegistryValueKind]::String) + # Set Nation to any of the EEA regions temporarily + # Refer: https://learn.microsoft.com/en-us/windows/win32/intl/table-of-geographical-locations + $tmpNation = 68 # Ireland + [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', $tmpNation, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null - # Set Nation to 84 (France) temporarily - [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', 68, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null + $baseKey = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' + $registryPath = $baseKey + '\ClientState\' + $Key - # credits to he3als for the Acl commands - $fileName = "IntegratedServicesRegionPolicySet.json" - $pathISRPS = [Environment]::SystemDirectory + "\" + $fileName - $aclISRPS = Get-Acl -Path $pathISRPS - $aclISRPSBackup = [System.Security.AccessControl.FileSecurity]::new() - $aclISRPSBackup.SetSecurityDescriptorSddlForm($acl.Sddl) - if (Test-Path -Path $pathISRPS) { - try { - $admin = [System.Security.Principal.NTAccount]$(New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')).Translate([System.Security.Principal.NTAccount]).Value - - $aclISRPS.SetOwner($admin) - $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($admin, 'FullControl', 'Allow') - $aclISRPS.AddAccessRule($rule) - Set-Acl -Path $pathISRPS -AclObject $aclISRPS - - Rename-Item -Path $pathISRPS -NewName ($fileName + '.bak') -Force + if (!(Test-Path -Path $registryPath)) { + Write-Host "[$Mode] Registry key not found: $registryPath" + return } - catch { - Write-Error "[$Mode] Failed to set owner for $pathISRPS" + + # Remove the status flag + Remove-ItemProperty -Path $baseKey -Name "IsEdgeStableUninstalled" -ErrorAction SilentlyContinue | Out-Null + + Remove-ItemProperty -Path $registryPath -Name "experiment_control_labels" -ErrorAction SilentlyContinue | Out-Null + + $uninstallString = (Get-ItemProperty -Path $registryPath).UninstallString + $uninstallArguments = (Get-ItemProperty -Path $registryPath).UninstallArguments + + if ([string]::IsNullOrEmpty($uninstallString) -or [string]::IsNullOrEmpty($uninstallArguments)) { + Write-Host "[$Mode] Cannot find uninstall methods for $Mode" + return + } + + # Extra arguments to nuke it + $uninstallArguments += " --force-uninstall --delete-profile" + + # $uninstallCommand = "`"$uninstallString`"" + $uninstallArguments + if (!(Test-Path -Path $uninstallString)) { + Write-Host "[$Mode] setup.exe not found at: $uninstallString" + return + } + Start-Process -FilePath $uninstallString -ArgumentList $uninstallArguments -Wait -NoNewWindow -Verbose + + # Restore Nation back to the original + [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', $originalNation, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null + + # might not exist in some cases + if ((Get-ItemProperty -Path $baseKey).IsEdgeStableUninstalled -eq 1) { + Write-Host "[$Mode] Edge Stable has been successfully uninstalled" } } - $baseKey = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' - $registryPath = $baseKey + '\ClientState\' + $Key + function Uninstall-Edge { + Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - if (!(Test-Path -Path $registryPath)) { - Write-Host "[$Mode] Registry key not found: $registryPath" - return + [microsoft.win32.registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev", "AllowUninstall", 1, [Microsoft.Win32.RegistryValueKind]::DWord) | Out-Null + + Uninstall-EdgeClient -Key '{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}' } - Remove-ItemProperty -Path $registryPath -Name "experiment_control_labels" -ErrorAction SilentlyContinue | Out-Null + function Uninstall-WebView { + # FIXME: might not work on some systems - $uninstallString = (Get-ItemProperty -Path $registryPath).UninstallString - $uninstallArguments = (Get-ItemProperty -Path $registryPath).UninstallArguments + Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - if ([string]::IsNullOrEmpty($uninstallString) -or [string]::IsNullOrEmpty($uninstallArguments)) { - Write-Host "[$Mode] Cannot find uninstall methods for $Mode" - return + Uninstall-EdgeClient -Key '{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' } - $uninstallArguments += " --force-uninstall --delete-profile" + function Uninstall-EdgeUpdate { + # FIXME: might not work on some systems - # $uninstallCommand = "`"$uninstallString`"" + $uninstallArguments - if (!(Test-Path -Path $uninstallString)) { - Write-Host "[$Mode] setup.exe not found at: $uninstallString" - return - } - Start-Process -FilePath $uninstallString -ArgumentList $uninstallArguments -Wait -NoNewWindow -Verbose + Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - # Restore Acl - if (Test-Path -Path ($pathISRPS + '.bak')) { - Rename-Item -Path ($pathISRPS + '.bak') -NewName $fileName -Force - Set-Acl -Path $pathISRPS -AclObject $aclISRPSBackup - } - - # Restore Nation - [microsoft.win32.registry]::SetValue('HKEY_USERS\.DEFAULT\Control Panel\International\Geo', 'Nation', $originalNation, [Microsoft.Win32.RegistryValueKind]::String) | Out-Null - - if ((Get-ItemProperty -Path $baseKey).IsEdgeStableUninstalled -eq 1) { - Write-Host "[$Mode] Edge Stable has been successfully uninstalled" - } -} - -function Uninstall-Edge { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - [microsoft.win32.registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdateDev", "AllowUninstall", 1, [Microsoft.Win32.RegistryValueKind]::DWord) | Out-Null - - Uninstall-Process -Key '{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}' - - @( "$env:ProgramData\Microsoft\Windows\Start Menu\Programs", - "$env:PUBLIC\Desktop", - "$env:USERPROFILE\Desktop" ) | ForEach-Object { - $shortcutPath = Join-Path -Path $_ -ChildPath "Microsoft Edge.lnk" - if (Test-Path -Path $shortcutPath) { - Remove-Item -Path $shortcutPath -Force + $registryPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' + if (!(Test-Path -Path $registryPath)) { + Write-Host "Registry key not found: $registryPath" + return } + $uninstallCmdLine = (Get-ItemProperty -Path $registryPath).UninstallCmdLine + + if ([string]::IsNullOrEmpty($uninstallCmdLine)) { + Write-Host "Cannot find uninstall methods for $Mode" + return + } + + Start-Process cmd.exe "/c $uninstallCmdLine" -WindowStyle Hidden -Wait } -} - -function Uninstall-WebView { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft EdgeWebView" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - # Force to use system-wide WebView2 - # [microsoft.win32.registry]::SetValue("HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\WebView2\BrowserExecutableFolder", "*", "%%SystemRoot%%\System32\Microsoft-Edge-WebView") - - Uninstall-Process -Key '{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' -} - -function Uninstall-EdgeUpdate { - Remove-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update" -Name "NoRemove" -ErrorAction SilentlyContinue | Out-Null - - $registryPath = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate' - if (!(Test-Path -Path $registryPath)) { - Write-Host "Registry key not found: $registryPath" - return - } - $uninstallCmdLine = (Get-ItemProperty -Path $registryPath).UninstallCmdLine - - if ([string]::IsNullOrEmpty($uninstallCmdLine)) { - Write-Host "Cannot find uninstall methods for $Mode" - return - } - - Write-Output "Uninstalling: $uninstallCmdLine" - Start-Process cmd.exe "/c $uninstallCmdLine" -WindowStyle Hidden -Wait -} - -Uninstall-Edge - # "WebView" { Uninstall-WebView } - # "EdgeUpdate" { Uninstall-EdgeUpdate } - - - - + Uninstall-Edge + # Uninstall-WebView - WebView is needed for Visual Studio and some MS Store Games like Forza + Uninstall-EdgeUpdate } 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 - - - - - - - - - - - -