# MyCentreK Win 1.0.2+cc29b21 # Windows 10 and 11 tweaks, debloat and optimisation. Built 2026-10-21 21:37 from src/ by build/Build.ps1. # # Copyright (c) 2026 Guilherme Francalacci. MIT License. # Contains code from Sophia Script for Windows, Copyright (c) 2023 farag2 (Team Sophia), MIT License: # https://github.com/farag2/Sophia-Script-for-Windows # # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the "Software"), to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and # to permit persons to whom the Software is furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in all copies or substantial portions # of the Software. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO # THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. [CmdletBinding()] param ( [Parameter(Mandatory = $false)] [string] $ProfileId, [Parameter(Mandatory = $false)] [string[]] $Category = @(), [Parameter(Mandatory = $false)] [string[]] $Include = @(), [Parameter(Mandatory = $false)] [string[]] $Exclude = @(), [Parameter(Mandatory = $false)] [string] $Undo, [switch] $DryRun, [switch] $Force ) $Global:MyCentreKWinVersion = "1.0.2+cc29b21" $Global:Localization = ConvertFrom-StringData -StringData @' CopilotPCSupport = Your PC has no a built-in AI accelerator (NPU), so there's no Windows AI related functions support. No need to disable anything using Windows policies. Recall function and Copilot application will only be removed. CustomStartMenu = A third-party Start Menu is installed. Disable = Disable EventViewerCustomViewDescription = Process creation and command-line auditing events. EventViewerCustomViewName = Process Creation InstallNotification = {0} is being installed... JSONNotValid = JSON file "{0}" is not valid. LocationServicesDisabled = Network shell commands need location permission to access WLAN information. Turn on Location services on the Location page in Privacy & security settings. NoResponse = Connection could not be established with {0}. NoScheduledTasks = No scheduled tasks to disable. NoSupportedNetworkAdapters = No network adapters which support function "Allow the computer to turn off this device to save power". NoUWPApps = No UWP apps to uninstall. OneDriveAccountWarning = Before uninstalling OneDrive, sign out of your Microsoft account in OneDrive. OneDriveInstalled = OneDrive is already installed. OneDriveNotInstalled = OneDrive is already uninstalled. PackageIsInstalled = The latest version of {0} is already installed. PackageNotInstalled = "{0}" app is not installed. PhotosNotInstalled = Photos application is not installed. ReservedStorageIsInUse = Reserved storage is being used. RestartFunction = Please re-run the "{0}" function. ScheduledTasks = Scheduled tasks SearchHighlightsDisabled = Search highlights are already hidden in Start. Skipped = Function "{0}" skipped. TaskbarSmallIcons = You use small taskbar buttons. ThirdPartyArchiverInstalled = A third-party archiver is installed. ThirdPartyAVInstalled = A third-party antivirus is installed. Uninstall = Uninstall UninstallNotification = {0} is being uninstalled... UninstallUWPForAll = For all users UWPAppsTitle = UWP Apps WidgetNotInstalled = Widget is not installed. '@ $Global:LgpoDownloadUrl = "https://download.microsoft.com/download/8/5/C/85C25433-A1B0-4FFA-9429-7E023E7DA8D8/LGPO.zip" function Sync-MyCentreKWinGroupPolicy { [CmdletBinding()] param () $PolicyFile = "$env:TEMP\LGPO.txt" if (-not (Test-Path -Path $PolicyFile)) { return } $Folder = Join-Path -Path $env:TEMP -ChildPath "MyCentreK-LGPO" try { Remove-Item -Path $Folder -Recurse -Force -ErrorAction Ignore New-Item -Path $Folder -ItemType Directory -Force | Out-Null Invoke-WebRequest -Uri $Global:LgpoDownloadUrl -OutFile "$Folder\LGPO.zip" -UseBasicParsing Expand-Archive -Path "$Folder\LGPO.zip" -DestinationPath $Folder -Force $Lgpo = Get-ChildItem -Path $Folder -Filter LGPO.exe -Recurse | Select-Object -First 1 if (-not $Lgpo) { throw "LGPO.exe is not in the downloaded archive" } $Signature = Get-AuthenticodeSignature -FilePath $Lgpo.FullName if (($Signature.Status -ne "Valid") -or ($Signature.SignerCertificate.Subject -notmatch "O=Microsoft Corporation")) { throw "LGPO.exe is not validly signed by Microsoft ($($Signature.Status)), so it was not run" } & $Lgpo.FullName /t $PolicyFile | Out-Null "N" | & "$env:SystemRoot\System32\gpupdate.exe" /force | Out-Null } catch { Write-Warning -Message "The Group Policy editor was not updated: $($_.Exception.Message). The tweaks themselves are applied." } finally { Remove-Item -Path $Folder -Recurse -Force -ErrorAction Ignore Get-ChildItem -Path $PolicyFile -Force -ErrorAction Ignore | Remove-Item -Force -ErrorAction Ignore } } function Start-MyCentreKWinExplorer { [CmdletBinding()] param () $Deadline = (Get-Date).AddSeconds(10) while ((-not (Get-Process -Name explorer -ErrorAction Ignore)) -and ((Get-Date) -lt $Deadline)) { Start-Sleep -Milliseconds 500 } if (-not (Get-Process -Name explorer -ErrorAction Ignore)) { Start-Process -FilePath "$env:SystemRoot\explorer.exe" } Start-Sleep -Seconds 2 } function Complete-MyCentreKWin { [CmdletBinding()] param () $Signature = @{ Namespace = "WinAPI" Name = "UpdateEnvironment" Language = "CSharp" CompilerParameters = $CompilerParameters MemberDefinition = @" private static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff); private const int WM_SETTINGCHANGE = 0x1a; private const int SMTO_ABORTIFHUNG = 0x0002; [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern IntPtr SendMessageTimeout(IntPtr hWnd, int Msg, IntPtr wParam, string lParam, int fuFlags, int uTimeout, IntPtr lpdwResult); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] static extern bool SendNotifyMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam); public static void Refresh() { // Update desktop icons SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero); // Update environment variables SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, null, SMTO_ABORTIFHUNG, 100, IntPtr.Zero); // Update taskbar SendNotifyMessage(HWND_BROADCAST, WM_SETTINGCHANGE, IntPtr.Zero, "TraySettings"); } private static readonly IntPtr hWnd = new IntPtr(65535); private const int Msg = 273; // Virtual key ID of the F5 in File Explorer private static readonly UIntPtr UIntPtr = new UIntPtr(41504); [DllImport("user32.dll", SetLastError=true)] public static extern int PostMessageW(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam); public static void PostMessage() { // Simulate pressing F5 to refresh the desktop PostMessageW(hWnd, Msg, UIntPtr, IntPtr.Zero); } "@ } if (-not ("WinAPI.UpdateEnvironment" -as [type])) { Add-Type @Signature } Write-Host -Object "" Write-Host -Object "Refreshing Windows so the changes show..." -ForegroundColor White [WinAPI.UpdateEnvironment]::PostMessage() [WinAPI.UpdateEnvironment]::Refresh() Stop-Process -Name StartMenuExperienceHost -Force -ErrorAction Ignore Get-Process -Name explorer -ErrorAction Ignore | Stop-Process -Force Start-MyCentreKWinExplorer foreach ($OpenedFolder in $Global:OpenedFolders) { if ($OpenedFolder -and (Test-Path -Path $OpenedFolder)) { Start-Process -FilePath "$env:SystemRoot\explorer.exe" -ArgumentList $OpenedFolder } } Sync-MyCentreKWinGroupPolicy } function Initialize-MyCentreKWin { [CmdletBinding()] param ( [switch] $DryRun ) $Host.UI.RawUI.WindowTitle = "MyCentreK Win $($Global:MyCentreKWinVersion)" [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $Global:ProgressPreference = "SilentlyContinue" if ($PSVersionTable.PSEdition -ne "Desktop") { throw "Run MyCentreK Win in Windows PowerShell 5.1 (powershell.exe), not PowerShell $($PSVersionTable.PSVersion). Several tweaks rely on modules that only work there." } if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") { throw "PowerShell is running in $($ExecutionContext.SessionState.LanguageMode) mode, usually because of a security policy. MyCentreK Win needs FullLanguage." } if ($env:PROCESSOR_ARCHITECTURE -ne "AMD64") { throw "MyCentreK Win supports x64 Windows only. This PC is $env:PROCESSOR_ARCHITECTURE." } $OperatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem if ($OperatingSystem.ProductType -ne 1) { throw "MyCentreK Win is for Windows 10 and 11 desktops, not Windows Server ($($OperatingSystem.Caption))." } $Global:WindowsBuild = [int]$OperatingSystem.BuildNumber $Global:IsWindows11 = $Global:WindowsBuild -ge 22000 if ((-not $Global:IsWindows11) -and ($Global:WindowsBuild -lt 19044)) { throw "MyCentreK Win supports Windows 10 21H2 (build 19044) and later, and Windows 11. This PC is $($OperatingSystem.Caption), build $($Global:WindowsBuild)." } if (-not $DryRun) { $Principal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList ([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "Run MyCentreK Win from PowerShell opened with ""Run as administrator""." } } $CurrentUserName = [Security.Principal.WindowsIdentity]::GetCurrent().Name | Split-Path -Leaf $LoginUserName = (Get-CimInstance -ClassName Win32_Process -Filter "name='explorer.exe'" | Invoke-CimMethod -MethodName GetOwner | Select-Object -First 1).User if ($LoginUserName -and ($CurrentUserName -ne $LoginUserName)) { $Message = "PowerShell is elevated as ""$CurrentUserName"" but ""$LoginUserName"" is signed in, so per-user tweaks would go to the wrong profile. Sign in as an administrator account and run it from there." if ($DryRun) { Write-Warning -Message $Message } else { throw $Message } } $Global:CompilerParameters = [System.CodeDom.Compiler.CompilerParameters]::new("System.dll") $Global:CompilerParameters.TempFiles = [System.CodeDom.Compiler.TempFileCollection]::new($env:TEMP, $false) $Global:CompilerParameters.GenerateInMemory = $true $Signature = @{ Namespace = "WinAPI" Name = "GetStrings" Language = "CSharp" UsingNamespace = "System.Text" CompilerParameters = $CompilerParameters MemberDefinition = @" [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern IntPtr GetModuleHandle(string lpModuleName); [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern int LoadString(IntPtr hInstance, uint uID, StringBuilder lpBuffer, int nBufferMax); public static string GetString(uint strId) { IntPtr intPtr = GetModuleHandle("shell32.dll"); StringBuilder sb = new StringBuilder(255); LoadString(intPtr, strId, sb, sb.Capacity); return sb.ToString(); } // Get string from other DLLs [DllImport("shlwapi.dll", CharSet=CharSet.Unicode)] private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, string ppvReserved); public static string GetIndirectString(string indirectString) { try { int returnValue; StringBuilder lptStr = new StringBuilder(1024); returnValue = SHLoadIndirectString(indirectString, lptStr, 1024, null); if (returnValue == 0) { return lptStr.ToString(); } else { return null; } } catch { return null; } } "@ } if (-not ("WinAPI.GetStrings" -as [type])) { Add-Type @Signature -ErrorAction Stop } $Signature = @{ Namespace = "WinAPI" Name = "ForegroundWindow" Language = "CSharp" CompilerParameters = $CompilerParameters MemberDefinition = @" [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetForegroundWindow(IntPtr hWnd); "@ } if (-not ("WinAPI.ForegroundWindow" -as [type])) { Add-Type @Signature -ErrorAction Stop } $Global:Browse = [WinAPI.GetStrings]::GetString(9015) $Global:No = [WinAPI.GetStrings]::GetString(33232).Replace("&", "") $Global:Yes = [WinAPI.GetStrings]::GetString(33224).Replace("&", "") $Global:Skip = [WinAPI.GetStrings]::GetString(16956) $Chassis = @((Get-CimInstance -ClassName Win32_SystemEnclosure).ChassisTypes) $Global:IsLaptop = [bool](Get-CimInstance -ClassName Win32_Battery) -or [bool]($Chassis | Where-Object -FilterScript {$_ -in @(8, 9, 10, 11, 12, 14, 18, 21, 30, 31, 32)}) try { $InstalledAVs = @(Get-CimInstance -ClassName AntiVirusProduct -Namespace root/SecurityCenter2 -ErrorAction Stop) $Global:DefenderDefaultAV = $true if ($InstalledAVs.Count -gt 1) { $Defender = $InstalledAVs | Where-Object -FilterScript {$_.instanceGuid -eq "{D68DDC3A-831F-4fae-9E44-DA132C1ACF46}"} $Global:DefenderDefaultAV = [bool]$Defender -and (('0x{0:x}' -f $Defender.productState).Substring(3, 2) -notmatch "00|01") } } catch { $Global:DefenderDefaultAV = $false Write-Warning -Message "Could not read the antivirus state from Windows Security Center. Microsoft Defender tweaks will be skipped." } $Global:OpenedFolders = @((New-Object -ComObject Shell.Application).Windows() | ForEach-Object -Process {$_.Document.Folder.Self.Path}) if ($Global:IsWindows11 -and ($Global:WindowsBuild -lt 26100)) { Write-Warning -Message "This is Windows 11 build $($Global:WindowsBuild). The tweaks are written for 24H2 (26100) and later; some may not apply on older builds." } if ((-not $Global:IsWindows11) -and ($Global:WindowsBuild -lt 19045)) { Write-Warning -Message "This is Windows 10 build $($Global:WindowsBuild). The tweaks are written for 22H2 (19045)." } $RebootPending = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" ) | Where-Object -FilterScript {Test-Path -Path $_} if ($RebootPending) { Write-Warning -Message "Windows is waiting for a restart to finish installing updates. Restarting first is safer." } } function Show-MyCentreKWinMenu { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Catalog ) $Windows = if ($Global:IsWindows11) {"Windows 11"} else {"Windows 10"} $Device = if ($Global:IsLaptop) {"laptop"} else {"desktop"} while ($true) { Write-Host -Object "" Write-Host -Object " MyCentreK Win $($Global:MyCentreKWinVersion)" -ForegroundColor White Write-Host -Object " $Windows, build $($Global:WindowsBuild), $Device" -ForegroundColor DarkGray Write-Host -Object "" $Options = @() foreach ($CatalogProfile in $Catalog.profiles) { $Options += [pscustomobject]@{Key = "$($Options.Count + 1)"; Label = $CatalogProfile.name; Description = $CatalogProfile.description; Action = "profile"; ProfileId = $CatalogProfile.id} } $Options += [pscustomobject]@{Key = "$($Options.Count + 1)"; Label = "Restore defaults"; Description = "Put back the Windows default for every tweak MyCentreK Win can change"; Action = "profile"; ProfileId = "restore"} $Options += [pscustomobject]@{Key = "$($Options.Count + 1)"; Label = "Custom"; Description = "Start from a profile and choose which categories to run"; Action = "custom"; ProfileId = $null} $Options += [pscustomobject]@{Key = "$($Options.Count + 1)"; Label = "Tools"; Description = "One-off fixes that are in no profile"; Action = "tools"; ProfileId = $null} $Options += [pscustomobject]@{Key = "$($Options.Count + 1)"; Label = "Undo a run"; Description = "Put back the settings a previous run changed on this PC"; Action = "undo"; ProfileId = $null} foreach ($Option in $Options) { Write-Host -Object (" {0}. {1,-18}" -f $Option.Key, $Option.Label) -NoNewline -ForegroundColor Cyan Write-Host -Object $Option.Description -ForegroundColor Gray } Write-Host -Object " Q. Quit" -ForegroundColor Cyan Write-Host -Object "" $Answer = (Read-Host -Prompt " Choose").Trim() if ($Answer -in @("q", "Q")) { return $null } $Choice = $Options | Where-Object -FilterScript {$_.Key -eq $Answer} if (-not $Choice) { Write-Host -Object " ""$Answer"" is not an option." -ForegroundColor Yellow continue } switch ($Choice.Action) { "profile" { return @{ProfileId = $Choice.ProfileId; Category = @(); Include = @(); Dialogs = $false} } "custom" { $Custom = Show-MyCentreKWinCustomMenu -Catalog $Catalog if ($Custom) { return $Custom } } "undo" { $Undo = Show-MyCentreKWinUndoMenu if ($Undo) { return $Undo } } "tools" { $Tools = Show-MyCentreKWinToolsMenu -Catalog $Catalog if ($Tools) { return $Tools } } } } } function Show-MyCentreKWinCustomMenu { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Catalog ) Write-Host -Object "" Write-Host -Object " Start from which profile?" -ForegroundColor White $Number = 0 foreach ($CatalogProfile in $Catalog.profiles) { $Number++ Write-Host -Object " $Number. $($CatalogProfile.name)" -ForegroundColor Cyan } $Answer = (Read-Host -Prompt " Choose").Trim() $Index = 0 if ((-not [int]::TryParse($Answer, [ref]$Index)) -or ($Index -lt 1) -or ($Index -gt $Number)) { Write-Host -Object " ""$Answer"" is not an option." -ForegroundColor Yellow return $null } $ProfileId = @($Catalog.profiles)[$Index - 1].id $Categories = @($Catalog.tweaks | Where-Object -FilterScript {$_.profiles -contains $ProfileId} | ForEach-Object -Process {$_.category} | Select-Object -Unique) Write-Host -Object "" Write-Host -Object " Which categories? Numbers separated by commas, or Enter for all." -ForegroundColor White for ($i = 0; $i -lt $Categories.Count; $i++) { $InCategory = @($Catalog.tweaks | Where-Object -FilterScript {($_.category -eq $Categories[$i]) -and ($_.profiles -contains $ProfileId)}).Count Write-Host -Object (" {0,2}. {1,-16} {2} tweaks" -f ($i + 1), $Categories[$i], $InCategory) -ForegroundColor Cyan } $Selected = Read-MyCentreKWinNumbers -Prompt " Categories" -Count $Categories.Count if ($null -eq $Selected) { return $null } $Selected = @($Selected | ForEach-Object -Process {$Categories[$_ - 1]}) $Optional = @($Catalog.tweaks | Where-Object -FilterScript { ($_.profiles -notcontains $ProfileId) -and ($_.category -ne "Tools") -and (($Selected.Count -eq 0) -or ($Selected -contains $_.category)) -and (Test-MyCentreKWinApplies -Tweak $_) }) $Include = @() if ($Optional.Count -gt 0) { Write-Host -Object "" Write-Host -Object " Also apply any of these? They are off by default. Numbers, or Enter for none." -ForegroundColor White for ($i = 0; $i -lt $Optional.Count; $i++) { Write-Host -Object (" {0,2}. {1}" -f ($i + 1), $Optional[$i].title) -ForegroundColor Cyan } $Picked = Read-MyCentreKWinNumbers -Prompt " Optional" -Count $Optional.Count if ($null -eq $Picked) { return $null } $Include = @($Picked | ForEach-Object -Process {$Optional[$_ - 1].id}) } @{ProfileId = $ProfileId; Category = $Selected; Include = $Include; Dialogs = $true} } function Show-MyCentreKWinToolsMenu { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Catalog ) $Tools = @($Catalog.tweaks | Where-Object -FilterScript {($_.category -eq "Tools") -and (Test-MyCentreKWinApplies -Tweak $_)}) if ($Tools.Count -eq 0) { Write-Host -Object " No tools apply to this PC." -ForegroundColor Yellow return $null } Write-Host -Object "" Write-Host -Object " Which tools? Numbers separated by commas." -ForegroundColor White for ($i = 0; $i -lt $Tools.Count; $i++) { Write-Host -Object (" {0,2}. {1}" -f ($i + 1), $Tools[$i].title) -ForegroundColor Cyan } $Picked = Read-MyCentreKWinNumbers -Prompt " Tools" -Count $Tools.Count if (($null -eq $Picked) -or ($Picked.Count -eq 0)) { return $null } @{ProfileId = "tools"; Category = @(); Include = @($Picked | ForEach-Object -Process {$Tools[$_ - 1].id}); Dialogs = $false} } function Show-MyCentreKWinUndoMenu { [CmdletBinding()] param () $Runs = @(Get-MyCentreKWinUndoRun | Select-Object -First 15) if ($Runs.Count -eq 0) { Write-Host -Object " No saved runs on this PC yet. Every run that changes something saves one." -ForegroundColor Yellow return $null } Write-Host -Object "" Write-Host -Object " Which run? Undo the newest first: an older run's changes may have been changed again since." -ForegroundColor White for ($i = 0; $i -lt $Runs.Count; $i++) { $Mark = if ($Runs[$i].Undone) {" (undone)"} else {""} Write-Host -Object (" {0,2}. {1} {2,-22} {3} changes{4}" -f ($i + 1), $Runs[$i].Date, $Runs[$i].Label, $Runs[$i].Changes, $Mark) -ForegroundColor Cyan } $Answer = (Read-Host -Prompt " Run").Trim() $Index = 0 if ((-not [int]::TryParse($Answer, [ref]$Index)) -or ($Index -lt 1) -or ($Index -gt $Runs.Count)) { Write-Host -Object " ""$Answer"" is not one of the numbers." -ForegroundColor Yellow return $null } @{Undo = $Runs[$Index - 1].Name} } function Read-MyCentreKWinNumbers { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Prompt, [Parameter(Mandatory = $true)] [int] $Count ) $Numbers = @() $Index = 0 foreach ($Part in ((Read-Host -Prompt $Prompt).Trim() -split "[,\s]+" | Where-Object -FilterScript {$_ -ne ""})) { if ((-not [int]::TryParse($Part, [ref]$Index)) -or ($Index -lt 1) -or ($Index -gt $Count)) { Write-Host -Object " ""$Part"" is not one of the numbers." -ForegroundColor Yellow return $null } $Numbers += $Index } ,@($Numbers | Select-Object -Unique) } function Set-MyCentreKWinRegistry { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [hashtable[]] $Values, [switch] $Remove ) foreach ($Item in $Values) { if ($Remove) { Remove-ItemProperty -Path $Item.Path -Name $Item.Name -Force -ErrorAction Ignore } else { if (-not (Test-Path -Path $Item.Path)) { New-Item -Path $Item.Path -Force | Out-Null } New-ItemProperty -Path $Item.Path -Name $Item.Name -PropertyType $Item.Type -Value $Item.Value -Force | Out-Null } if ($Item.Path -match "\\Policies\\") { $Scope = if ($Item.Path -like "HKLM:*") {"Computer"} else {"User"} $PolicyPath = $Item.Path -replace "^HK(LM|CU):\\", "" if ($Remove) { Set-Policy -Scope $Scope -Path $PolicyPath -Name $Item.Name -Type CLEAR } else { $PolicyType = switch ($Item.Type) {"String" {"SZ"} default {"DWORD"}} Set-Policy -Scope $Scope -Path $PolicyPath -Name $Item.Name -Type $PolicyType -Value $Item.Value } } } } function ConvertTo-MyCentreKWinArguments { [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory = $true)] [AllowNull()] $Arguments ) $Table = @{} if ($null -ne $Arguments) { foreach ($Property in $Arguments.PSObject.Properties) { $Table[$Property.Name] = $Property.Value } } $Table } function Format-MyCentreKWinCall { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $true)] [string] $Function, [Parameter(Mandatory = $true)] [hashtable] $Arguments ) $Parts = @($Function) foreach ($Name in $Arguments.Keys) { $Value = $Arguments[$Name] if ($Value -is [bool]) { $Parts += "-$Name" } else { $Parts += "-$Name $(@($Value) -join ', ')" } } $Parts -join " " } function Test-MyCentreKWinApplies { [CmdletBinding()] [OutputType([bool])] param ( [Parameter(Mandatory = $true)] $Tweak ) $Os = if ($Global:IsWindows11) {"11"} else {"10"} $Device = if ($Global:IsLaptop) {"laptop"} else {"desktop"} ($Tweak.os -contains $Os) -and (($Tweak.device -eq "any") -or ($Tweak.device -eq $Device)) } function Get-MyCentreKWinPlan { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Catalog, [Parameter(Mandatory = $true)] [string] $ProfileId, [Parameter(Mandatory = $false)] [string[]] $Category = @(), [Parameter(Mandatory = $false)] [string[]] $Include = @(), [Parameter(Mandatory = $false)] [string[]] $Exclude = @() ) foreach ($Tweak in $Catalog.tweaks) { if ((-not (Test-MyCentreKWinApplies -Tweak $Tweak)) -or ($Exclude -contains $Tweak.id)) { continue } if ($Include -contains $Tweak.id) { $Arguments = ConvertTo-MyCentreKWinArguments -Arguments $Tweak.apply } elseif (($Category.Count -gt 0) -and ($Category -notcontains $Tweak.category)) { continue } elseif ($ProfileId -eq "restore") { if ($null -eq $Tweak.restore) { continue } $Arguments = ConvertTo-MyCentreKWinArguments -Arguments $Tweak.restore } else { if ($Tweak.profiles -notcontains $ProfileId) { continue } $Arguments = ConvertTo-MyCentreKWinArguments -Arguments $Tweak.apply } [pscustomobject]@{ Id = $Tweak.id Category = $Tweak.category Title = $Tweak.title Function = $Tweak.function Arguments = $Arguments Call = Format-MyCentreKWinCall -Function $Tweak.function -Arguments $Arguments } } } function Invoke-MyCentreKWinPlan { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [object[]] $Plan, [switch] $DryRun ) $Number = 0 foreach ($Step in $Plan) { $Number++ Write-Host -Object ("[{0}/{1}] {2}: {3}" -f $Number, $Plan.Count, $Step.Category, $Step.Title) -ForegroundColor Cyan if ($DryRun) { Write-Host -Object " $($Step.Call)" -ForegroundColor DarkGray [pscustomobject]@{Step = $Step; Status = "DryRun"; Message = $null} continue } if (-not (Get-Command -Name $Step.Function -CommandType Function -ErrorAction Ignore)) { [pscustomobject]@{Step = $Step; Status = "Failed"; Message = "Function $($Step.Function) is not in this build"} continue } $Arguments = $Step.Arguments $ErrorCount = $Global:Error.Count try { & $Step.Function @Arguments | Out-Null if ($Global:Error.Count -gt $ErrorCount) { $Message = $Global:Error[0].ToString().Replace('& $Step.Function @Arguments | Out-Null', $Step.Call) [pscustomobject]@{Step = $Step; Status = "Warning"; Message = $Message} } else { [pscustomobject]@{Step = $Step; Status = "Done"; Message = $null} } } catch { Write-Warning -Message "$($Step.Call) failed: $($_.Exception.Message)" [pscustomobject]@{Step = $Step; Status = "Failed"; Message = $_.Exception.Message} } } } function Write-MyCentreKWinSummary { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [object[]] $Results, [Parameter(Mandatory = $false)] [string] $LogPath ) $Count = @{} foreach ($Status in @("Done", "Warning", "Failed", "DryRun")) { $Count[$Status] = @($Results | Where-Object -FilterScript {$_.Status -eq $Status}).Count } Write-Host -Object "" Write-Host -Object "Summary" -ForegroundColor White if ($Count.DryRun) { Write-Host -Object " $($Count.DryRun) tweaks would run. Nothing was changed." -ForegroundColor Green } else { Write-Host -Object " Done: $($Count.Done)" -ForegroundColor Green Write-Host -Object " Warnings: $($Count.Warning)" -ForegroundColor Yellow Write-Host -Object " Failed: $($Count.Failed)" -ForegroundColor Red } foreach ($Result in ($Results | Where-Object -FilterScript {$_.Status -in @("Warning", "Failed")})) { $Colour = if ($Result.Status -eq "Failed") {"Red"} else {"Yellow"} Write-Host -Object " $($Result.Status): $($Result.Step.Call)" -ForegroundColor $Colour Write-Host -Object " $($Result.Message)" -ForegroundColor DarkGray } if ($LogPath) { Write-Host -Object "" Write-Host -Object "Full log: $LogPath" } } function New-MyCentreKWinLog { [CmdletBinding()] param () $LogFolder = "$env:ProgramData\MyCentreK Win\Logs" New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null $LogPath = "$LogFolder\MyCentreK-Win-$(Get-Date -Format 'yyyy-MM-dd-HHmmss').log" Start-Transcript -Path $LogPath | Out-Null $LogPath } function Remove-MyCentreKWinTaskFolder { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name ) $ScheduleService = New-Object -ComObject Schedule.Service $ScheduleService.Connect() $Folder = @($ScheduleService.GetFolder("\").GetFolders(0)) | Where-Object -FilterScript {$_.Name -eq $Name} if ($Folder -and ($Folder.GetTasks(1).Count -eq 0) -and (@($Folder.GetFolders(0)).Count -eq 0)) { $ScheduleService.GetFolder("\").DeleteFolder($Name, 0) } } function New-MyCentreKWinRestorePoint { [CmdletBinding()] param ( [switch] $Force ) Write-Host -Object "" Write-Host -Object "Creating a restore point..." -ForegroundColor White try { & { $ErrorActionPreference = "Stop" CreateRestorePoint | Out-Null } } catch { Write-Warning -Message "No restore point was created: $($_.Exception.Message)" if (-not $Force) { $Answer = Read-Host -Prompt "Continue without a restore point? [y/N]" if ($Answer -notin @("y", "Y", "yes", "Yes")) { Write-Host -Object "Nothing was changed." return $false } } } $true } function Global:Set-Policy { [CmdletBinding()] param ( [Parameter( Mandatory = $true, Position = 1 )] [string] [ValidateSet("Computer", "User")] $Scope, [Parameter( Mandatory = $true, Position = 2 )] [string] $Path, [Parameter( Mandatory = $true, Position = 3 )] [string] $Name, [Parameter( Mandatory = $true, Position = 4 )] [ValidateSet("DWORD", "SZ", "EXSZ", "CLEAR")] [string] $Type, [Parameter( Mandatory = $false, Position = 5 )] $Value ) if (-not (Test-Path -Path "$env:SystemRoot\System32\gpedit.msc")) { return } switch ($Type) { "CLEAR" { $Policy = @" $Scope $($Path) $($Name) $($Type)`n "@ } default { $Policy = @" $Scope $($Path) $($Name) $($Type):$($Value)`n "@ } } Add-Content -Path "$env:TEMP\LGPO.txt" -Value $Policy -Encoding Default -Force } $Global:MyCentreKWinUndoFolder = "$env:ProgramData\MyCentreK Win\Undo" $Global:MyCentreKWinStateTrees = @( @{Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces"; Values = @("NameServer"); Depth = 1}, @{Path = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters"; Values = $null; Depth = 4}, @{Path = "HKLM:\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes"; Values = $null; Depth = 3}, @{Path = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; Values = @("POWERSHELL_TELEMETRY_OPTOUT", "MP_FORCE_USE_SANDBOX"); Depth = 0} ) function Split-MyCentreKWinRegistryPath { param ([string]$Path) $Hive, $Subkey = $Path -split ":\\", 2 $Base = switch ($Hive) { "HKLM" {[Microsoft.Win32.Registry]::LocalMachine} "HKCU" {[Microsoft.Win32.Registry]::CurrentUser} } @{Base = $Base; Subkey = $Subkey} } function ConvertTo-MyCentreKWinValueState { param ([Microsoft.Win32.RegistryValueKind]$Kind, $Raw) $Data = switch ($Kind) { "Binary" {[Convert]::ToBase64String([byte[]]$Raw)} "MultiString" {,[string[]]$Raw} "DWord" {[int]$Raw} "QWord" {[long]$Raw} "String" {[string]$Raw} "ExpandString" {[string]$Raw} default {$null} } @{Type = "$Kind"; Data = $Data; Canon = "$Kind|" + (ConvertTo-Json -InputObject $Data -Compress)} } function Get-MyCentreKWinValueState { param ([string]$Path, [string]$Name) $Split = Split-MyCentreKWinRegistryPath -Path $Path $Key = $Split.Base.OpenSubKey($Split.Subkey) if (-not $Key) { return $null } try { if ($Key.GetValueNames() -notcontains $Name) { return $null } ConvertTo-MyCentreKWinValueState -Kind $Key.GetValueKind($Name) -Raw $Key.GetValue($Name, $null, "DoNotExpandEnvironmentNames") } finally { $Key.Close() } } function Add-MyCentreKWinKeyState { param ($State, [string]$Path, $Values, [int]$Depth) $Split = Split-MyCentreKWinRegistryPath -Path $Path try { $Key = $Split.Base.OpenSubKey($Split.Subkey) } catch { return } if (-not $Key) { $State["$Path|"] = "absent" return } try { $State["$Path|"] = "present" foreach ($Name in $Key.GetValueNames()) { if (($null -eq $Values) -or ($Values -contains $Name)) { $State["$Path|$Name"] = ConvertTo-MyCentreKWinValueState -Kind $Key.GetValueKind($Name) -Raw $Key.GetValue($Name, $null, "DoNotExpandEnvironmentNames") } } if ($Depth -gt 0) { foreach ($Subkey in $Key.GetSubKeyNames()) { Add-MyCentreKWinKeyState -State $State -Path "$Path\$Subkey" -Values $Values -Depth ($Depth - 1) } } } finally { $Key.Close() } } function Get-MyCentreKWinState { [CmdletBinding()] param () $State = @{} foreach ($Path in @($Global:MyCentreKWinStateKeys)) { Add-MyCentreKWinKeyState -State $State -Path $Path -Values $null -Depth 0 } foreach ($Tree in $Global:MyCentreKWinStateTrees) { Add-MyCentreKWinKeyState -State $State -Path $Tree.Path -Values $Tree.Values -Depth $Tree.Depth } foreach ($Task in @(Get-ScheduledTask -ErrorAction Ignore)) { $State["task:$($Task.TaskPath)$($Task.TaskName)"] = if ($Task.State -eq "Disabled") {"Disabled"} else {"Enabled"} } $State } function Compare-MyCentreKWinState { [CmdletBinding()] param ($Before, $After) $Names = @($Before.Keys) + @($After.Keys) | Sort-Object -Unique foreach ($Name in $Names) { $Old = $Before[$Name] $New = $After[$Name] if ($Name -like "task:*") { $Old = if ($null -eq $Old) {"absent"} else {$Old} $New = if ($null -eq $New) {"absent"} else {$New} $Ours = ($Name -like "task:\MyCentreK Win\*") -or ($Name -like "task:\Sophia\*") if (($Old -ne $New) -and ($Ours -or (($Old -ne "absent") -and ($New -ne "absent")))) { [ordered]@{Kind = "Task"; Task = $Name.Substring(5); Before = $Old; After = $New} } continue } $Path, $ValueName = $Name -split "\|", 2 if ($ValueName -eq "") { if (($Old -eq "absent") -and ($New -eq "present")) { [ordered]@{Kind = "KeyCreated"; Path = $Path} } continue } $OldCanon = if ($Old) {$Old.Canon} else {""} $NewCanon = if ($New) {$New.Canon} else {""} if ($OldCanon -ne $NewCanon) { [ordered]@{Kind = "Value"; Path = $Path; Name = $ValueName; Before = $Old; After = $New} } } } function Save-MyCentreKWinUndo { [CmdletBinding()] param ( $Before, $After, [string]$Label, [string[]]$Tweaks ) $Changes = @(Compare-MyCentreKWinState -Before $Before -After $After) if ($Changes.Count -eq 0) { return $null } $Policies = @(foreach ($Name in $Before.Keys) { $KeyPath, $ValueName = $Name -split "\|", 2 if ((Test-MyCentreKWinPolicyKey -Path $KeyPath) -and ($ValueName -ne "") -and ($Name -notlike "task:*") -and $Before[$Name].Type -in @("DWord", "String", "ExpandString")) { [ordered]@{Path = $KeyPath; Name = $ValueName; Before = $Before[$Name]} } }) New-Item -Path $Global:MyCentreKWinUndoFolder -ItemType Directory -Force | Out-Null $Path = "$($Global:MyCentreKWinUndoFolder)\$(Get-Date -Format 'yyyy-MM-dd-HHmmss')-$Label.json" $Run = [ordered]@{ version = $Global:MyCentreKWinVersion date = (Get-Date).ToString("s") label = $Label user = [Security.Principal.WindowsIdentity]::GetCurrent().Name tweaks = @($Tweaks) changes = $Changes policies = $Policies } [System.IO.File]::WriteAllText($Path, (ConvertTo-Json -InputObject $Run -Depth 8), (New-Object -TypeName System.Text.UTF8Encoding -ArgumentList $false)) $Path } function Get-MyCentreKWinUndoRun { [CmdletBinding()] param () Get-ChildItem -Path $Global:MyCentreKWinUndoFolder -Filter *.json -ErrorAction Ignore | Sort-Object -Property Name -Descending | ForEach-Object -Process { $Run = Get-Content -Path $_.FullName -Raw -Encoding UTF8 | ConvertFrom-Json [pscustomobject]@{ Name = $_.BaseName -replace "\.undone$", "" Path = $_.FullName Date = $Run.date Label = $Run.label Changes = @($Run.changes).Count Undone = $_.BaseName -like "*.undone" Run = $Run } } } function Set-MyCentreKWinValueState { param ([string]$Path, [string]$Name, $State) $Split = Split-MyCentreKWinRegistryPath -Path $Path if ($null -eq $State) { $Key = $Split.Base.OpenSubKey($Split.Subkey, $true) if ($Key) { try { $Key.DeleteValue($Name, $false) } finally { $Key.Close() } } return } $Data = switch ($State.Type) { "Binary" {,[Convert]::FromBase64String($State.Data)} "MultiString" {,[string[]]@($State.Data)} "DWord" {[int]$State.Data} "QWord" {[long]$State.Data} "String" {[string]$State.Data} "ExpandString" {[string]$State.Data} default {throw "values of type $($State.Type) cannot be written back"} } $Key = $Split.Base.CreateSubKey($Split.Subkey) try { $Key.SetValue($Name, $Data, [Microsoft.Win32.RegistryValueKind]$State.Type) } finally { $Key.Close() } } function Test-MyCentreKWinPolicyKey { param ([string]$Path) ($Path -match "\\Policies\\") -or (@($Global:MyCentreKWinPolicyKeys) -contains $Path) } function Set-MyCentreKWinPolicyRecord { param ([string]$Path, [string]$Name, $State) if (-not (Test-MyCentreKWinPolicyKey -Path $Path)) { return } $Scope = if ($Path -like "HKLM:*") {"Computer"} else {"User"} $PolicyPath = $Path -replace "^HK(LM|CU):\\", "" if (-not $State) { Set-Policy -Scope $Scope -Path $PolicyPath -Name $Name -Type CLEAR } elseif ($State.Type -in @("DWord", "String", "ExpandString")) { $PolicyType = switch ($State.Type) {"DWord" {"DWORD"} "String" {"SZ"} "ExpandString" {"EXSZ"}} Set-Policy -Scope $Scope -Path $PolicyPath -Name $Name -Type $PolicyType -Value $State.Data } } function Invoke-MyCentreKWinUndo { [CmdletBinding()] param ( $Run, [switch] $DryRun ) $Changes = @($Run.changes) $PowerChanged = $false $Decided = foreach ($Change in $Changes) { switch ($Change.Kind) { "Value" { $Where = "$($Change.Path)\$(if ($Change.Name) {$Change.Name} else {'(default)'})" $Current = Get-MyCentreKWinValueState -Path $Change.Path -Name $Change.Name $CurrentCanon = if ($Current) {$Current.Canon} else {""} $AfterCanon = if ($Change.After) {$Change.After.Canon} else {""} $Status = if ($CurrentCanon -eq $AfterCanon) {"Would"} else {"Skipped"} [pscustomobject]@{Change = $Change; Where = $Where; Status = $Status} } "Task" { $TaskPath = (Split-Path -Path $Change.Task -Parent).TrimEnd("\") + "\" $Task = Get-ScheduledTask -TaskPath $TaskPath -TaskName (Split-Path -Path $Change.Task -Leaf) -ErrorAction Ignore $Current = if (-not $Task) {"absent"} elseif ($Task.State -eq "Disabled") {"Disabled"} else {"Enabled"} $Status = if ($Current -ne $Change.After) {"Skipped"} elseif ($Change.After -eq "absent") {"Impossible"} else {"Would"} [pscustomobject]@{Change = $Change; Where = "task $($Change.Task)"; Status = $Status} } } } foreach ($Item in $Decided) { switch ($Item.Status) { "Skipped" {[pscustomobject]@{Status = "Skipped"; Item = $Item.Where; Message = "changed since the run"}} "Impossible" {[pscustomobject]@{Status = "Failed"; Item = $Item.Where; Message = "removed by the run; cannot be recreated"}} "Would" { if ($DryRun) { [pscustomobject]@{Status = "Would"; Item = $Item.Where; Message = $null} continue } $Change = $Item.Change try { if ($Change.Kind -eq "Task") { $TaskPath = (Split-Path -Path $Change.Task -Parent).TrimEnd("\") + "\" $TaskName = Split-Path -Path $Change.Task -Leaf switch ($Change.Before) { "absent" {Unregister-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -Confirm:$false -ErrorAction Stop} "Enabled" {Enable-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -ErrorAction Stop | Out-Null} "Disabled" {Disable-ScheduledTask -TaskPath $TaskPath -TaskName $TaskName -ErrorAction Stop | Out-Null} } } elseif (($Change.Path -like "*\Tcpip\Parameters\Interfaces\*") -and ($Change.Name -eq "NameServer")) { $Guid = Split-Path -Path $Change.Path -Leaf $Adapter = Get-NetAdapter -ErrorAction Ignore | Where-Object -FilterScript {$_.InterfaceGuid -eq $Guid} if (-not $Adapter) { throw "the network adapter is no longer there" } $Servers = @(if ($Change.Before) {"$($Change.Before.Data)" -split "[,\s]+" | Where-Object -FilterScript {$_}}) if ($Servers.Count -gt 0) { Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ServerAddresses $Servers -ErrorAction Stop } else { Set-DnsClientServerAddress -InterfaceIndex $Adapter.ifIndex -ResetServerAddresses -ErrorAction Stop } } else { Set-MyCentreKWinValueState -Path $Change.Path -Name $Change.Name -State $Change.Before Set-MyCentreKWinPolicyRecord -Path $Change.Path -Name $Change.Name -State $Change.Before if ($Change.Path -like "*\Control\Power\User\PowerSchemes*") { $PowerChanged = $true } } [pscustomobject]@{Status = "Reverted"; Item = $Item.Where; Message = $null; Change = $Change} } catch { [pscustomobject]@{Status = "Failed"; Item = $Item.Where; Message = $_.Exception.Message} } } } } if (-not $DryRun) { foreach ($Change in ($Changes | Where-Object -FilterScript {$_.Kind -eq "KeyCreated"} | Sort-Object -Property {$_.Path.Length} -Descending)) { $Split = Split-MyCentreKWinRegistryPath -Path $Change.Path $Key = $Split.Base.OpenSubKey($Split.Subkey) if ($Key) { $Empty = ($Key.ValueCount -eq 0) -and ($Key.SubKeyCount -eq 0) $Key.Close() if ($Empty) { $Split.Base.DeleteSubKey($Split.Subkey, $false) } } } if (@($Changes | Where-Object -FilterScript {($_.Kind -eq "Task") -and ($_.Task -like "\MyCentreK Win\*") -and ($_.Before -eq "absent")}).Count -gt 0) { Remove-MyCentreKWinTaskFolder -Name "MyCentreK Win" } if ($PowerChanged) { $Active = [Microsoft.Win32.Registry]::GetValue("HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes", "ActivePowerScheme", $null) if ($Active) { & powercfg.exe /setactive $Active | Out-Null } } Clear-DnsClientCache -ErrorAction Ignore } } function Get-MyCentreKWinPolicyToKeep { [CmdletBinding()] param ($Run) $Changed = @($Run.changes | Where-Object -FilterScript {$_.Kind -eq "Value"} | ForEach-Object -Process {"$($_.Path)|$($_.Name)"}) foreach ($Policy in @($Run.policies)) { if ($Changed -contains "$($Policy.Path)|$($Policy.Name)") { continue } $Current = Get-MyCentreKWinValueState -Path $Policy.Path -Name $Policy.Name if ($Current -and ($Current.Canon -eq $Policy.Before.Canon)) { $Policy } } } function Repair-MyCentreKWinPolicy { [CmdletBinding()] param ($Keep, $Results) $Repaired = 0 foreach ($Result in @($Results | Where-Object -FilterScript {($_.Status -eq "Reverted") -and ($_.Change.Kind -eq "Value") -and ($_.Change.Name -ne "NameServer")})) { $Change = $Result.Change $Current = Get-MyCentreKWinValueState -Path $Change.Path -Name $Change.Name $CurrentCanon = if ($Current) {$Current.Canon} else {""} $BeforeCanon = if ($Change.Before) {$Change.Before.Canon} else {""} if ($CurrentCanon -ne $BeforeCanon) { Set-MyCentreKWinValueState -Path $Change.Path -Name $Change.Name -State $Change.Before $Repaired++ } } foreach ($Policy in @($Keep)) { $Current = Get-MyCentreKWinValueState -Path $Policy.Path -Name $Policy.Name if ((-not $Current) -or ($Current.Canon -ne $Policy.Before.Canon)) { Set-MyCentreKWinValueState -Path $Policy.Path -Name $Policy.Name -State $Policy.Before $Repaired++ } } $Repaired } function Start-MyCentreKWinUndo { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $Name, [Parameter(Mandatory = $true)] $Catalog, [switch] $DryRun, [switch] $Force ) $Runs = @(Get-MyCentreKWinUndoRun) $Selected = if ($Name -eq "latest") {$Runs | Where-Object -FilterScript {-not $_.Undone} | Select-Object -First 1} else {$Runs | Where-Object -FilterScript {$_.Name -eq $Name} | Select-Object -First 1} if (-not $Selected) { Write-Host -Object "No saved run ""$Name"" to undo. Saved runs are in $($Global:MyCentreKWinUndoFolder)." -ForegroundColor Yellow return } Write-Host -Object "" Write-Host -Object "Undo the $($Selected.Label) run of $($Selected.Date): $($Selected.Changes) saved changes." -ForegroundColor White if ($Selected.Undone) { Write-Host -Object "This run was undone already; whatever still matches it will be undone again." -ForegroundColor Yellow } $Notes = @(foreach ($Id in @($Selected.Run.tweaks)) { $Tweak = $Catalog.tweaks | Where-Object -FilterScript {$_.id -eq $Id} | Select-Object -First 1 if ($Tweak -and ($Tweak.PSObject.Properties.Name -contains "undo") -and $Tweak.undo) { "$($Tweak.id): $($Tweak.undo)" } }) if ($Notes.Count -gt 0) { Write-Host -Object "" Write-Host -Object "Not undone by this (the run's other effects):" -ForegroundColor Yellow $Notes | ForEach-Object -Process {Write-Host -Object " $_" -ForegroundColor Gray} } $Preview = @(Invoke-MyCentreKWinUndo -Run $Selected.Run -DryRun) $Skipped = @($Preview | Where-Object -FilterScript {$_.Status -eq "Skipped"}) $Impossible = @($Preview | Where-Object -FilterScript {$_.Status -eq "Failed"}) Write-Host -Object "" Write-Host -Object "$(@($Preview | Where-Object -FilterScript {$_.Status -eq 'Would'}).Count) changes can be undone; $($Skipped.Count) changed since the run and will be left alone; $($Impossible.Count) cannot be undone." -ForegroundColor White foreach ($Item in ($Skipped + $Impossible)) { Write-Host -Object " $($Item.Status): $($Item.Item) ($($Item.Message))" -ForegroundColor DarkGray } if ($DryRun) { Write-Host -Object "Nothing was changed." return } if (-not $Force) { $Answer = Read-Host -Prompt "Undo it? [y/N]" if ($Answer -notin @("y", "Y", "yes", "Yes")) { Write-Host -Object "Nothing was changed." return } } $LogPath = New-MyCentreKWinLog try { if (-not (New-MyCentreKWinRestorePoint -Force:$Force)) { return } $Before = Get-MyCentreKWinState $Keep = @(Get-MyCentreKWinPolicyToKeep -Run $Selected.Run) foreach ($Policy in $Keep) { Set-MyCentreKWinPolicyRecord -Path $Policy.Path -Name $Policy.Name -State $Policy.Before } $Results = @(Invoke-MyCentreKWinUndo -Run $Selected.Run) Complete-MyCentreKWin $Repaired = Repair-MyCentreKWinPolicy -Keep $Keep -Results $Results if ($Repaired -gt 0) { Write-Host -Object "$Repaired values that changed again during the Group Policy refresh were put back." -ForegroundColor White } $UndoFile = Save-MyCentreKWinUndo -Before $Before -After (Get-MyCentreKWinState) -Label "undo-$($Selected.Name)" -Tweaks @() if (-not $Selected.Undone) { Rename-Item -Path $Selected.Path -NewName ([System.IO.Path]::GetFileNameWithoutExtension($Selected.Path) + ".undone.json") } Write-Host -Object "" Write-Host -Object "Undo summary" -ForegroundColor White foreach ($Status in @("Reverted", "Skipped", "Failed")) { Write-Host -Object (" {0,-9} {1}" -f "$($Status):", @($Results | Where-Object -FilterScript {$_.Status -eq $Status}).Count) } foreach ($Item in ($Results | Where-Object -FilterScript {$_.Status -eq "Failed"})) { Write-Host -Object " Failed: $($Item.Item): $($Item.Message)" -ForegroundColor Red } if ($UndoFile) { Write-Host -Object "This undo can itself be undone: $(Split-Path -Path $UndoFile -Leaf)" } Write-Host -Object "Full log: $LogPath" } finally { Stop-Transcript | Out-Null } } function CreateRestorePoint { $SystemDriveUniqueID = (Get-Volume | Where-Object -FilterScript {$_.DriveLetter -eq "$($env:SystemDrive[0])"}).UniqueID $SystemProtection = ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SPP\Clients" -ErrorAction Ignore)."{09F7EDC5-294E-4180-AF6A-FB0E6A0E9513}") | Where-Object -FilterScript {$_ -match [regex]::Escape($SystemDriveUniqueID)} $Global:ComputerRestorePoint = $false if (-not $SystemProtection) { $Global:ComputerRestorePoint = $true Enable-ComputerRestore -Drive $env:SystemDrive } New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -Name SystemRestorePointCreationFrequency -PropertyType DWord -Value 0 -Force Checkpoint-Computer -Description "MyCentreK Win" -RestorePointType MODIFY_SETTINGS New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -Name SystemRestorePointCreationFrequency -PropertyType DWord -Value 1440 -Force if ($Global:ComputerRestorePoint) { Disable-ComputerRestore -Drive $env:SystemDrive } } function DiagTrackService { param ( [Parameter( Mandatory = $true, ParameterSetName = "Disable" )] [switch] $Disable, [Parameter( Mandatory = $true, ParameterSetName = "Enable" )] [switch] $Enable ) switch ($PSCmdlet.ParameterSetName) { "Disable" { Get-Service -Name DiagTrack -ErrorAction Ignore | Stop-Service -Force Get-Service -Name DiagTrack -ErrorAction Ignore | Set-Service -StartupType Disabled Get-NetFirewallRule -Group DiagTrack -ErrorAction Ignore | Set-NetFirewallRule -Enabled True -Action Block } "Enable" { Get-Service -Name DiagTrack -ErrorAction Ignore | Set-Service -StartupType Automatic Get-Service -Name DiagTrack -ErrorAction Ignore | Start-Service Get-NetFirewallRule -Group DiagTrack -ErrorAction Ignore | Set-NetFirewallRule -Enabled True -Action Allow } } } function DiagnosticDataLevel { param ( [Parameter( Mandatory = $true, ParameterSetName = "Minimal" )] [switch] $Minimal, [Parameter( Mandatory = $true, ParameterSetName = "Default" )] [switch] $Default ) if (-not (Test-Path -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection)) { New-Item -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Force } if (-not (Test-Path -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack)) { New-Item -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack -Force } Remove-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection -Name AllowTelemetry -Force -ErrorAction Ignore Set-Policy -Scope Computer -Path SOFTWARE\Policies\Microsoft\Windows\DataCollection -Name AllowTelemetry -Type CLEAR switch ($PSCmdlet.ParameterSetName) { "Minimal" { $EditionID = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").EditionID if (($EditionID -match "Enterprise") -or ($EditionID -match "Education")) { New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name AllowTelemetry -PropertyType DWord -Value 0 -Force } else { New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name AllowTelemetry -PropertyType DWord -Value 1 -Force } New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name MaxTelemetryAllowed -PropertyType DWord -Value 1 -Force New-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack -Name ShowedToastAtLevel -PropertyType DWord -Value 1 -Force } "Default" { New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name MaxTelemetryAllowed -PropertyType DWord -Value 3 -Force New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection -Name AllowTelemetry -PropertyType DWord -Value 3 -Force New-ItemProperty -Path HKCU:\Software\Microsoft\Windows\CurrentVersion\Diagnostics\DiagTrack -Name ShowedToastAtLevel -PropertyType DWord -Value 3 -Force } } } function ErrorReporting { param ( [Parameter( Mandatory = $true, ParameterSetName = "Disable" )] [switch] $Disable, [Parameter( Mandatory = $true, ParameterSetName = "Enable" )] [switch] $Enable ) Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Error Reporting", "HKCU:\Software\Policies\Microsoft\Windows\Windows Error Reporting" -Name Disabled -Force -ErrorAction Ignore Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\PCHealth\ErrorReporting" -Name DoReport -Force -ErrorAction Ignore Set-Policy -Scope Computer -Path "SOFTWARE\Policies\Microsoft\Windows\Windows Error Reporting" -Name Disabled -Type CLEAR Set-Policy -Scope User -Path "Software\Policies\Microsoft\Windows\Windows Error Reporting" -Name Disabled -Type CLEAR Set-Policy -Scope Computer -Path "SOFTWARE\Policies\Microsoft\PCHealth\ErrorReporting" -Name DoReport -Type CLEAR switch ($PSCmdlet.ParameterSetName) { "Disable" { Get-ScheduledTask -TaskName QueueReporting -ErrorAction Ignore | Disable-ScheduledTask Get-Service -Name WerSvc | Stop-Service -Force Get-Service -Name WerSvc | Set-Service -StartupType Disabled } "Enable" { Get-ScheduledTask -TaskName QueueReporting -ErrorAction Ignore | Enable-ScheduledTask Get-Service -Name WerSvc | Set-Service -StartupType Manual Get-Service -Name WerSvc | Start-Service } } } function FeedbackFrequency { param ( [Parameter( Mandatory = $true, ParameterSetName = "Never" )] [switch] $Never, [Parameter( Mandatory = $true, ParameterSetName = "Automatically" )] [switch] $Automatically ) Remove-ItemProperty -Path HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection -Name DoNotShowFeedbackNotifications -Force -ErrorAction Ignore Set-Policy -Scope Computer -Path SOFTWARE\Policies\Microsoft\Windows\DataCollection -Name DoNotShowFeedbackNotifications -Type CLEAR Remove-ItemProperty -Path HKCU:\Software\Microsoft\Siuf\Rules -Name PeriodInNanoSeconds -Force -ErrorAction Ignore switch ($PSCmdlet.ParameterSetName) { "Never" { if (-not (Test-Path -Path HKCU:\Software\Microsoft\Siuf\Rules)) { New-Item -Path HKCU:\Software\Microsoft\Siuf\Rules -Force } New-ItemProperty -Path HKCU:\Software\Microsoft\Siuf\Rules -Name NumberOfSIUFInPeriod -PropertyType DWord -Value 0 -Force } "Automatically" { Remove-ItemProperty -Path HKCU:\Software\Microsoft\Siuf\Rules -Name NumberOfSIUFInPeriod -Force -ErrorAction Ignore } } } function ScheduledTasks { param ( [Parameter( Mandatory = $true, ParameterSetName = "Disable" )] [switch] $Disable, [Parameter( Mandatory = $true, ParameterSetName = "Enable" )] [switch] $Enable ) Add-Type -AssemblyName PresentationCore, PresentationFramework $SelectedTasks = New-Object -TypeName System.Collections.ArrayList($null) [string[]]$CheckedScheduledTasks = @( "MareBackup", "Microsoft Compatibility Appraiser", "Microsoft Compatibility Appraiser Exp", "StartupAppTask", "ProgramDataUpdater", "Proxy", "Consolidator", "UsbCeip", "Microsoft-Windows-DiskDiagnosticDataCollector", "MapsToastTask", "MapsUpdateTask" ) [xml]$XAML = @"