Public/Install-WinslopFix.ps1
|
function Install-WinslopFix { <# .SYNOPSIS Installs WinslopFix as a persistent Scheduled Task running as SYSTEM. .DESCRIPTION Deploys the WinslopFix module to ProgramData, registers the Event Log source, and creates a Scheduled Task that runs at system startup under the SYSTEM account. No UAC prompts. No user interaction required. The installation is idempotent — safe to run multiple times. Existing installations are upgraded in place when -Force is specified. Optionally disables Copilot AI features (Recall, Click to Do) during installation via the -DisableAIFeatures switch. .PARAMETER InstallPath Destination directory for the deployed module files. Default: $env:ProgramData\WinslopFix .PARAMETER DisableAIFeatures Also disable all Copilot AI features (Recall, Click to Do, Windows AI) during installation. Equivalent to running: Disable-CopilotAIFeature -Feature All .PARAMETER Force Overwrite an existing installation without prompting. .EXAMPLE Install-WinslopFix Install with default settings. Creates a SYSTEM-level Scheduled Task. .EXAMPLE Install-WinslopFix -DisableAIFeatures Install the guard and also disable Recall, Click to Do, etc. .EXAMPLE Install-WinslopFix -Force -Verbose Reinstall/upgrade with verbose output showing each step. .LINK https://github.com/DailenG/WinslopFix #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()] [string]$InstallPath = "$env:ProgramData\WinslopFix", [Parameter()] [switch]$DisableAIFeatures, [Parameter()] [switch]$Force ) process { Assert-Administrator $taskName = 'WinslopFix' $taskPath = '\WinslopFix\' # --- Pre-flight: Platform detection --- $platform = Test-CopilotPlatform if (-not $platform.IsCopilotPlatform) { Write-Warning ( 'This machine does not appear to be a Copilot+ PC. ' + "NPU: $($platform.HasNPU), AI Service: $($platform.HasAIFabricService), " + "Active Sessions: $($platform.WorkloadsSessionHostCount). " + 'WinslopFix may not be necessary on this system.' ) } # --- Check for existing installation --- $existingTask = Get-ScheduledTask -TaskName $taskName -TaskPath $taskPath -ErrorAction SilentlyContinue if ($existingTask -and -not $Force) { Write-Warning 'WinslopFix is already installed. Use -Force to reinstall/upgrade.' return } # --- Step 1: Create installation directory --- if ($PSCmdlet.ShouldProcess($InstallPath, 'Create installation directory')) { if (-not (Test-Path -LiteralPath $InstallPath)) { New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null Write-Verbose "Created directory: $InstallPath" } } # --- Step 2: Copy module files --- if ($PSCmdlet.ShouldProcess($InstallPath, 'Deploy module files')) { # Determine source — the module's own root directory $moduleRoot = Split-Path $PSScriptRoot -Parent $filesToCopy = @( 'WinslopFix.psd1' 'WinslopFix.psm1' ) $dirsToCopy = @('Public', 'Private', 'Config') foreach ($file in $filesToCopy) { $src = Join-Path $moduleRoot $file if (Test-Path -LiteralPath $src) { Copy-Item -LiteralPath $src -Destination $InstallPath -Force Write-Verbose "Copied: $file" } else { Write-Warning "Module file not found: $src" } } foreach ($dir in $dirsToCopy) { $src = Join-Path $moduleRoot $dir if (Test-Path -LiteralPath $src) { $dest = Join-Path $InstallPath $dir Copy-Item -LiteralPath $src -Destination $dest -Recurse -Force Write-Verbose "Copied directory: $dir" } } Write-Verbose "Module deployed to: $InstallPath" } # --- Step 3: Register Event Log source --- if ($PSCmdlet.ShouldProcess('WinslopFix', 'Register Event Log source')) { try { if (-not [System.Diagnostics.EventLog]::SourceExists('WinslopFix')) { New-EventLog -LogName Application -Source 'WinslopFix' -ErrorAction Stop Write-Verbose 'Event Log source registered: WinslopFix' } else { Write-Verbose 'Event Log source already exists: WinslopFix' } } catch { Write-Warning "Failed to register Event Log source: $($_.Exception.Message)" } } # --- Step 4: Clean up legacy WinslopFix/SafeAutentic artifacts --- if ($PSCmdlet.ShouldProcess('Legacy artifacts', 'Remove WinslopFix/SafeAutentic remnants')) { # Remove old SafeAutentic scheduled tasks $legacyTasks = @( @{ TaskPath = '\SafeAutentic\'; TaskName = 'WorkloadManager_Init' } @{ TaskPath = '\SafeAutentic\'; TaskName = 'WorkloadManager_Run' } ) foreach ($task in $legacyTasks) { $existing = Get-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction SilentlyContinue if ($existing) { Unregister-ScheduledTask -TaskName $task.TaskName -TaskPath $task.TaskPath -Confirm:$false Write-Verbose "Removed legacy task: $($task.TaskPath)$($task.TaskName)" } } # Remove old Run keys from original SafeAutentic installer $runKeyPaths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' ) foreach ($regPath in $runKeyPaths) { $existing = Get-ItemProperty -Path $regPath -Name 'WorkloadManager' -ErrorAction SilentlyContinue if ($existing) { Remove-ItemProperty -Path $regPath -Name 'WorkloadManager' -ErrorAction SilentlyContinue Write-Verbose "Removed legacy Run key: $regPath\WorkloadManager" } } # Remove old ProgramData directory $legacyPath = "$env:ProgramData\WorkloadManager" if (Test-Path -LiteralPath $legacyPath) { Remove-Item -LiteralPath $legacyPath -Recurse -Force -ErrorAction SilentlyContinue Write-Verbose "Removed legacy directory: $legacyPath" } # Remove old Event Log source try { if ([System.Diagnostics.EventLog]::SourceExists('WorkloadManager')) { Remove-EventLog -Source 'WorkloadManager' -ErrorAction SilentlyContinue Write-Verbose 'Removed legacy Event Log source: WorkloadManager' } } catch { } } # --- Step 5: Remove existing scheduled task if upgrading --- if ($existingTask) { if ($PSCmdlet.ShouldProcess($taskName, 'Remove existing Scheduled Task for upgrade')) { Unregister-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Confirm:$false Write-Verbose 'Removed existing Scheduled Task for upgrade.' } } # --- Step 6: Create Scheduled Task (SYSTEM, no UAC) --- if ($PSCmdlet.ShouldProcess($taskName, 'Register Scheduled Task as SYSTEM')) { $scriptPath = Join-Path $InstallPath 'WinslopFix.psm1' $command = "Import-Module '$scriptPath' -Force; Start-WinslopFix" $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument ( "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$command`"" ) $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet ` -AllowStartIfOnBatteries ` -DontStopIfGoingOnBatteries ` -StartWhenAvailable ` -RestartCount 3 ` -RestartInterval (New-TimeSpan -Minutes 1) ` -ExecutionTimeLimit (New-TimeSpan -Days 365) Register-ScheduledTask ` -TaskName $taskName ` -TaskPath $taskPath ` -Action $action ` -Trigger $trigger ` -Principal $principal ` -Settings $settings ` -Description 'WinslopFix: Manages WorkloadsSessionHost memory consumption on Copilot+ PCs.' ` -Force | Out-Null Write-Verbose "Scheduled Task registered: $taskPath$taskName (SYSTEM, at startup)" } # --- Step 7: Optionally disable AI features --- if ($DisableAIFeatures) { Write-Verbose 'Disabling Copilot AI features as requested...' Disable-CopilotAIFeature -Feature All } # --- Step 8: Start the task immediately --- if ($PSCmdlet.ShouldProcess($taskName, 'Start Scheduled Task now')) { Start-ScheduledTask -TaskName $taskName -TaskPath $taskPath -ErrorAction SilentlyContinue Write-Verbose 'WinslopFix Scheduled Task started.' } # --- Output summary --- Write-WinslopFixLog -Message "WinslopFix installed to '$InstallPath'. Scheduled Task registered as SYSTEM." -EventId 1000 [PSCustomObject]@{ PSTypeName = 'WinslopFix.InstallResult' InstallPath = $InstallPath TaskName = "$taskPath$taskName" TaskPrincipal = 'SYSTEM' AIDisabled = [bool]$DisableAIFeatures IsCopilotPC = $platform.IsCopilotPlatform LegacyCleaned = $true Timestamp = (Get-Date).ToString('o') } } } |