Created
August 23, 2025 10:11
-
-
Save michaelsstuff/d3408f8d001f21caa4f288a184eb5855 to your computer and use it in GitHub Desktop.
Install script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| Fresh Windows bootstrap script | |
| - Enables WSL, Virtual Machine Platform, optional Windows Sandbox | |
| - Installs chosen applications with winget (idempotent) | |
| - Optional sets: mobile / gaming | |
| - Privacy tweaks (optional), logging, reboot detection | |
| - Re-runnable: skips already enabled features & installed packages | |
| Execution Policy / First Run: | |
| Windows may block direct execution (Restricted / AllSigned). Launch one of these from an elevated PowerShell: | |
| 1) One-shot bypass: | |
| powershell -NoProfile -ExecutionPolicy Bypass -File .\!install.ps1 | |
| 2) Current session only: | |
| Set-ExecutionPolicy -Scope Process Bypass -Force; .\!install.ps1 | |
| 3) Unblock downloaded file (removes MOTW): | |
| Unblock-File .\!install.ps1 | |
| Or use the helper batch launcher (run-install.cmd) created alongside this script. | |
| #> | |
| #[CmdletBinding()] + param must appear before any executable statements (like Set-StrictMode) | |
| [CmdletBinding()] | |
| param( | |
| [switch] $SkipRebootCheck, | |
| [switch] $IgnoreStalePendingFileRenames, | |
| [switch] $MobileDevice, | |
| [switch] $ApplyPrivacyTweaks, | |
| [switch] $GamingDevice | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| ### --- Helper Functions --- ### | |
| function Test-IsAdmin { | |
| ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()). | |
| IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| } | |
| function Get-RebootPendingReasons { | |
| <# Returns an array of reason strings; empty array => no reboot required (best effort). #> | |
| $reasons = @() | |
| try { | |
| if (Test-Path 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') { $reasons += 'CBS:RebootPending' } | |
| if (Test-Path 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') { $reasons += 'WU:RebootRequired' } | |
| $sess = 'HKLM:SYSTEM\CurrentControlSet\Control\Session Manager' | |
| $pfr = (Get-ItemProperty -Path $sess -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations | |
| if ($pfr) { | |
| # Normalize (drop blanks) | |
| $raw = @() | |
| foreach ($e in $pfr) { if ($e -and -not [string]::IsNullOrWhiteSpace([string]$e)) { $raw += [string]$e } } | |
| Set-Variable -Name PendingFileRenameOperationsRaw -Value $raw -Scope Script | |
| # PendingFileRenameOperations is an array of pairs (source, destination). Destination can be empty -> delete. | |
| $entries = @() | |
| for ($i = 0; $i -lt $raw.Count; $i += 2) { | |
| $src = $raw[$i] | |
| $dst = if ($i + 1 -lt $raw.Count) { $raw[$i + 1] } else { '' } | |
| if (-not $src) { continue } | |
| $entries += [pscustomobject]@{ Source=$src; Destination=$dst } | |
| } | |
| function _CleanPath([string]$p) { | |
| if (-not $p) { return $null } | |
| # Remove leading *<n>\??\ prefix if present | |
| $c = $p -replace '^\*[0-9]+\\\?\?\\','' | |
| # Remove leading \\?\ (extended path) if present | |
| $c = $c -replace '^\\\\\?\\','' | |
| return $c | |
| } | |
| $ignorePatterns = @( | |
| '(?i)\\Windows\\Temp\\', | |
| '(?i)\\Windows\\SystemTemp\\', | |
| '(?i)\\AppData\\Local\\Temp\\' | |
| ) | |
| function _IsIgnorableEntry($entry) { | |
| $paths = @($entry.Source, $entry.Destination) | Where-Object { $_ } | |
| if ($paths.Count -eq 0) { return $true } | |
| # Quick pattern-based ignore (temp locations, .tmp files) | |
| foreach ($p in $paths) { | |
| foreach ($pat in $ignorePatterns) { if ($p -match $pat) { return $true } } | |
| if ($p -match '(?i)\.tmp$') { return $true } | |
| } | |
| if ($IgnoreStalePendingFileRenames) { | |
| # If all referenced paths (after cleaning) do NOT exist, treat as stale -> ignore | |
| $existing = 0 | |
| foreach ($p in $paths) { | |
| $clean = _CleanPath $p | |
| if ($clean -and (Test-Path $clean)) { $existing++ } | |
| } | |
| if ($existing -eq 0) { return $true } | |
| } | |
| return $false | |
| } | |
| $meaningful = $entries | Where-Object { -not (_IsIgnorableEntry $_) } | |
| if ($meaningful.Count -gt 0) { | |
| $reasons += "SessionManager:PendingFileRenameOperations($($meaningful.Count))" | |
| Set-Variable -Name PendingFileRenameOperationsMeaningful -Value $meaningful -Scope Script | |
| } else { | |
| Set-Variable -Name PendingFileRenameOperationsMeaningful -Value @() -Scope Script | |
| } | |
| } | |
| $updateExeVolatile = (Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Updates' -Name UpdateExeVolatile -ErrorAction SilentlyContinue).UpdateExeVolatile | |
| if ($updateExeVolatile -and $updateExeVolatile -ne 0) { $reasons += "Updates:UpdateExeVolatile=$updateExeVolatile" } | |
| } catch { | |
| Write-Verbose "Get-RebootPendingReasons error: $($_.Exception.Message)" | |
| } | |
| return $reasons | |
| } | |
| function Enable-FeatureIfNeeded { | |
| param( | |
| [Parameter(Mandatory)] [string]$Name | |
| ) | |
| $feature = Get-WindowsOptionalFeature -Online -FeatureName $Name -ErrorAction SilentlyContinue | |
| if ($feature -and $feature.State -eq 'Enabled') { | |
| Write-Host "[WSL] Feature '$Name' already enabled" -ForegroundColor DarkGray | |
| } else { | |
| Write-Host "[WSL] Enabling feature '$Name'..." -ForegroundColor Cyan | |
| dism.exe /online /enable-feature /featurename:$Name /all /norestart | Out-Null | |
| } | |
| } | |
| function Enable-WindowsSandboxIfAvailable { | |
| <# Attempts to enable Windows Sandbox (Containers-DisposableClientVM) if the feature exists on this SKU / architecture. | |
| Skips quietly on unsupported editions (e.g. Home) or when the optional feature is absent. | |
| #> | |
| $featureName = 'Containers-DisposableClientVM' | |
| try { | |
| # Quick architecture / OS checks | |
| if (-not [Environment]::Is64BitOperatingSystem) { | |
| Write-Host '[Sandbox] Not a 64-bit OS; skipping.' -ForegroundColor DarkGray; return | |
| } | |
| $cv = Get-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion' -ErrorAction Stop | |
| $edition = $cv.EditionID | |
| $eligible = 'Professional','ProfessionalN','Enterprise','EnterpriseN','Education','EducationN','Pro','ProN' | |
| if ($eligible -notcontains $edition) { | |
| # Still attempt detection of feature presence; some custom editions might allow it | |
| Write-Host "[Sandbox] Edition '$edition' not in standard eligible list; probing feature anyway..." -ForegroundColor DarkGray | |
| } | |
| $feature = Get-WindowsOptionalFeature -Online -FeatureName $featureName -ErrorAction SilentlyContinue | |
| if (-not $feature) { | |
| Write-Host '[Sandbox] Optional feature not found on this system; skipping.' -ForegroundColor DarkGray; return | |
| } | |
| if ($feature.State -eq 'Enabled') { | |
| Write-Host '[Sandbox] Windows Sandbox already enabled.' -ForegroundColor DarkGray; return | |
| } | |
| if ($feature.State -eq 'Disabled') { | |
| Write-Host '[Sandbox] Enabling Windows Sandbox...' -ForegroundColor Cyan | |
| dism.exe /online /enable-feature /featurename:$featureName /all /norestart | Out-Null | |
| } else { | |
| Write-Host "[Sandbox] Feature present but state '$($feature.State)' (no action)." -ForegroundColor Yellow | |
| } | |
| } catch { | |
| Write-Host "[Sandbox] Unable to query/enable Windows Sandbox: $($_.Exception.Message)" -ForegroundColor Yellow | |
| } | |
| } | |
| function Install-App { | |
| param( | |
| [Parameter(Mandatory)] [string]$Id | |
| ) | |
| Write-Host "----> $Id" -ForegroundColor Yellow | |
| # Check if installed | |
| $listOutput = winget list --id $Id -e --accept-source-agreements 2>$null | Out-String | |
| if ($listOutput -notmatch 'No installed package found' -and $listOutput -match [Regex]::Escape($Id)) { | |
| Write-Host "Already installed: $Id" -ForegroundColor DarkGray | |
| return | |
| } | |
| try { | |
| winget install --id $Id -e --silent --accept-package-agreements --accept-source-agreements | |
| } catch { | |
| Write-Warning "Failed to install $Id : $($_.Exception.Message)" | |
| } | |
| } | |
| function Ensure-Winget { | |
| Write-Host 'Checking for winget...' -ForegroundColor Cyan | |
| $wingetCmd = Get-Command winget -ErrorAction SilentlyContinue | |
| if ($wingetCmd) { | |
| try { $v = winget --version 2>$null; Write-Host "winget present (version $v)" -ForegroundColor DarkGray } catch { Write-Host 'winget present.' -ForegroundColor DarkGray } | |
| return $true | |
| } | |
| # Fallback: check App Installer (Store package) which provides winget | |
| $appInstaller = Get-AppxPackage -Name 'Microsoft.DesktopAppInstaller' -ErrorAction SilentlyContinue | |
| if ($appInstaller) { | |
| Write-Host 'App Installer package found; winget should become available after shell restart.' -ForegroundColor Yellow | |
| return $false | |
| } | |
| Write-Warning 'winget not found. Attempting to trigger Microsoft Store App Installer installation.' | |
| $storeUri = 'ms-windows-store://pdp/?productid=9NBLGGH4NNS1' | |
| try { | |
| Start-Process $storeUri | |
| Write-Host 'Please install "App Installer" from the Store window that opened, then re-run this script.' -ForegroundColor Magenta | |
| } catch { | |
| Write-Warning "Failed to open Microsoft Store. Install App Installer manually (ProductId 9NBLGGH4NNS1). Error: $($_.Exception.Message)" | |
| } | |
| return $false | |
| } | |
| function Invoke-AppxRemoval { | |
| Write-Host 'Removing bundled AppX packages...' -ForegroundColor Cyan | |
| $packages = @( | |
| 'Microsoft.BingWeather', | |
| 'Microsoft.BingNews', | |
| 'Microsoft.GetHelp', | |
| 'Microsoft.Getstarted', | |
| 'MicrosoftTeams', | |
| 'Microsoft.MicrosoftSolitaireCollection', | |
| 'Microsoft.MicrosoftOfficeHub', | |
| 'Microsoft.People', | |
| 'Microsoft.Todos', | |
| 'Microsoft.WindowsAlarms', | |
| 'microsoft.windowscommunicationsapps', | |
| 'Microsoft.WindowsCamera', | |
| 'Microsoft.WindowsSoundRecorder', | |
| 'Microsoft.WindowsMaps', | |
| 'Microsoft.WindowsFeedbackHub' | |
| ) | |
| foreach ($p in $packages) { | |
| try { | |
| $found = Get-AppxPackage -Name $p -ErrorAction SilentlyContinue | |
| if ($found) { | |
| Write-Host "Removing AppX: ${p}" -ForegroundColor Yellow | |
| $found | Remove-AppxPackage -ErrorAction SilentlyContinue | |
| } else { | |
| Write-Host "Not installed (skip): ${p}" -ForegroundColor DarkGray | |
| } | |
| } catch { Write-Warning "Failed removing ${p} (user packages): $($_.Exception.Message)" } | |
| try { | |
| $prov = Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -eq $p } | |
| if ($prov) { | |
| Write-Host "Removing provisioned package: ${p}" -ForegroundColor Yellow | |
| $prov | Remove-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | Out-Null | |
| } | |
| } catch { Write-Warning "Failed removing provisioned ${p}: $($_.Exception.Message)" } | |
| } | |
| Write-Host 'AppX removal phase complete.' -ForegroundColor Green | |
| } | |
| function Invoke-PrivacyTweaks { | |
| <# | |
| Applies a curated set of Windows 11 privacy / de-bloat style registry settings to reduce telemetry, | |
| targeted ads, suggestions, consumer experiences, and activity tracking. | |
| A backup of previous values is written to %USERPROFILE%\privacy-backup-<timestamp>.json so changes can be reverted manually. | |
| NOTE: Some settings only take effect after sign-out or reboot. Use responsibly; enterprise policies or managed devices | |
| may override or conflict with these keys. This is intentionally conservative (avoids disabling Windows Update or Defender). | |
| #> | |
| Write-Host 'Applying privacy & ad/telemetry reduction tweaks...' -ForegroundColor Cyan | |
| $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' | |
| $backupPath = Join-Path $env:USERPROFILE "privacy-backup-$timestamp.json" | |
| $backup = @() | |
| function Backup-And-Set { | |
| param( | |
| [Parameter(Mandatory)] [string]$Path, | |
| [Parameter(Mandatory)] [string]$Name, | |
| [Parameter(Mandatory)] [object]$Value, | |
| [ValidateSet('DWord','String')] [string]$Type = 'DWord' | |
| ) | |
| try { | |
| if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null } | |
| $existing = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue | |
| $old = if ($existing) { $existing.$Name } else { $null } | |
| $backup += [pscustomobject]@{ Path=$Path; Name=$Name; OldValue=$old; NewValue=$Value; Type=$Type } | |
| if ($Type -eq 'DWord') { | |
| New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType DWord -Force | Out-Null | |
| } else { | |
| New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType String -Force | Out-Null | |
| } | |
| } catch { | |
| Write-Warning "Failed setting $Path : $Name -> $Value ($Type) : $($_.Exception.Message)" | |
| } | |
| } | |
| # Telemetry level (0 = Security on Enterprise/Education, best-effort on other SKUs) | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'AllowTelemetry' -Value 0 | |
| # Advertising ID off | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo' -Name 'Disabled' -Value 1 | |
| # Consumer features / suggestions / spotlight | |
| $cloudContentLM = 'HKLM:SOFTWARE\Policies\Microsoft\Windows\CloudContent' | |
| Backup-And-Set -Path $cloudContentLM -Name 'DisableConsumerFeatures' -Value 1 | |
| Backup-And-Set -Path $cloudContentLM -Name 'DisableSoftLanding' -Value 1 | |
| Backup-And-Set -Path $cloudContentLM -Name 'DisableTailoredExperiencesWithDiagnosticData' -Value 1 | |
| Backup-And-Set -Path $cloudContentLM -Name 'DisableWindowsSpotlightFeatures' -Value 1 | |
| Backup-And-Set -Path $cloudContentLM -Name 'DisableSpotlightCollection' -Value 1 | |
| # Activity history | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\System' -Name 'PublishUserActivities' -Value 0 | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\System' -Name 'UploadUserActivities' -Value 0 | |
| # Feedback frequency (0 = no prompts) under current user | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Siuf\Rules' -Name 'NumberOfSIUFInPeriod' -Value 0 | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Siuf\Rules' -Name 'PeriodInNanoSeconds' -Value 0 | |
| # Cortana / Search | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\Windows Search' -Name 'AllowCortana' -Value 0 | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\Windows Search' -Name 'DisableWebSearch' -Value 1 | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\Windows Search' -Name 'ConnectedSearchUseWeb' -Value 0 | |
| # Location services off (policy) - skip if mobile device flagged | |
| if (-not $script:IsMobileDevice) { | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\LocationAndSensors' -Name 'DisableLocation' -Value 1 | |
| } else { | |
| Write-Host '[Privacy] Skipping location disable (mobile device).' -ForegroundColor DarkGray | |
| } | |
| # OneDrive (optional) - comment out if you rely on OneDrive | |
| # Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\OneDrive' -Name 'DisableFileSyncNGSC' -Value 1 | |
| # Windows Tips (Current User) | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-338389Enabled' -Value 0 | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-353694Enabled' -Value 0 | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-353696Enabled' -Value 0 | |
| Backup-And-Set -Path 'HKCU:Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-280815Enabled' -Value 0 | |
| # Store suggestions (turn off app suggestions) | |
| Backup-And-Set -Path 'HKLM:SOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableThirdPartySuggestions' -Value 1 | |
| try { $backup | ConvertTo-Json -Depth 4 | Set-Content -Path $backupPath -Encoding UTF8 } catch { | |
| Write-Warning "Failed to write backup file: $($_.Exception.Message)" | |
| } | |
| Write-Host "Privacy tweaks applied. Backup saved to: $backupPath" -ForegroundColor Green | |
| } | |
| function Disable-FastStartup { | |
| Write-Host 'Disabling Windows Fast Startup (Hybrid Boot)...' -ForegroundColor Cyan | |
| $path = 'HKLM:SYSTEM\CurrentControlSet\Control\Session Manager\Power' | |
| try { | |
| if (-not (Test-Path $path)) { New-Item -Path $path -Force | Out-Null } | |
| $old = (Get-ItemProperty -Path $path -Name HiberbootEnabled -ErrorAction SilentlyContinue).HiberbootEnabled | |
| New-ItemProperty -Path $path -Name HiberbootEnabled -Value 0 -PropertyType DWord -Force | Out-Null | |
| Write-Host "Fast Startup disabled (old value: $old)" -ForegroundColor Green | |
| } catch { | |
| Write-Warning "Failed to set HiberbootEnabled: $($_.Exception.Message)" | |
| } | |
| # To also disable hibernation (and remove hiberfil.sys) uncomment the line below: | |
| # try { powercfg /hibernate off | Out-Null; Write-Host 'Hibernation disabled.' -ForegroundColor Green } catch { Write-Warning "Failed disabling hibernation: $($_.Exception.Message)" } | |
| } | |
| ### --- Pre-flight --- ### | |
| if (-not (Test-IsAdmin)) { | |
| Write-Warning 'This script must be run as Administrator. Right-click PowerShell and choose "Run as administrator".' | |
| break | |
| } | |
| try { Start-Transcript -Path (Join-Path $env:USERPROFILE "install-log-$(Get-Date -Format yyyyMMdd_HHmmss).txt") -ErrorAction Stop } catch {} | |
| Write-Host "Starting bootstrap @ $(Get-Date)" -ForegroundColor Green | |
| if (Ensure-Winget) { | |
| Write-Host 'Updating winget sources...' -ForegroundColor Cyan | |
| try { winget source update --accept-source-agreements | Out-Null } catch { Write-Warning 'Winget source update failed.' } | |
| } else { | |
| Write-Warning 'Skipping winget source update because winget is not currently available.' | |
| } | |
| ### --- Feature enablement (WSL2) --- ### | |
| Enable-FeatureIfNeeded -Name Microsoft-Windows-Subsystem-Linux | |
| Enable-FeatureIfNeeded -Name VirtualMachinePlatform | |
| Enable-WindowsSandboxIfAvailable | |
| if (-not $SkipRebootCheck) { | |
| $rebootReasons = @(Get-RebootPendingReasons) | |
| if ($rebootReasons.Count -gt 0) { | |
| Write-Host "A reboot is reported pending (reasons: $($rebootReasons -join ', '))." -ForegroundColor Magenta | |
| if ($script:PendingFileRenameOperationsRaw) { | |
| Write-Host 'Raw PendingFileRenameOperations entries (for diagnostics):' -ForegroundColor DarkGray | |
| $script:PendingFileRenameOperationsRaw | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } | |
| } | |
| $choice = Read-Host 'Reboot now before continuing? (y=Reboot / s=Skip this time / i=Ignore future PFR stale entries)' | |
| switch -Regex ($choice) { | |
| '^[Yy]$' { | |
| Write-Host 'Rebooting...' -ForegroundColor Magenta | |
| Stop-Transcript | Out-Null 2>$null | |
| Restart-Computer -Force; return | |
| } | |
| '^[Ii]$' { | |
| Write-Host 'Ignoring stale PendingFileRenameOperations for this run.' -ForegroundColor DarkGray | |
| $IgnoreStalePendingFileRenames = $true | |
| } | |
| } | |
| } else { | |
| Write-Host 'No reboot indicators detected.' -ForegroundColor DarkGray | |
| } | |
| } else { | |
| Write-Host 'Reboot check skipped via parameter.' -ForegroundColor DarkGray | |
| } | |
| ### --- WSL Distribution --- ### | |
| $desiredDistro = 'openSUSE-Tumbleweed' | |
| Write-Host "Ensuring WSL distro '$desiredDistro' installed..." -ForegroundColor Cyan | |
| $existingDistros = (wsl -l -q 2>$null) | ForEach-Object { $_.Trim() } | Where-Object { $_ } | |
| if ($existingDistros -contains $desiredDistro) { | |
| Write-Host "WSL distro already present: $desiredDistro" -ForegroundColor DarkGray | |
| } else { | |
| # Set default version to 2 (only if supported) | |
| try { wsl --set-default-version 2 2>$null } catch {} | |
| wsl --install -d $desiredDistro | |
| Write-Host 'If the install triggered a reboot, rerun this script afterwards to continue application installs.' -ForegroundColor Magenta | |
| } | |
| ### --- Fast Startup Disable (always) --- ### | |
| Disable-FastStartup | |
| ### --- Application Lists & Prompts --- ### | |
| $apps = @( | |
| '7zip.7zip', | |
| 'AppWork.JDownloader', | |
| 'Balena.Etcher', | |
| 'Brave.Brave', | |
| 'CPUID.CPU-Z', | |
| 'Deezer.Deezer', | |
| 'Discord.Discord', | |
| 'Docker.DockerDesktop', | |
| 'Git.Git', | |
| 'GitHub.cli', | |
| 'GnuPG.Gpg4win', | |
| 'AgileBits.1Password', | |
| 'WinSCP.WinSCP', | |
| 'Malwarebytes.Malwarebytes', | |
| 'Microsoft.PowerToys', | |
| 'Microsoft.VisualStudioCode', | |
| 'OpenMedia.4KVideoDownloader', | |
| 'OpenWhisperSystems.Signal', | |
| 'Plex.PlexMediaPlayer', | |
| 'PointPlanck.FileBot', | |
| 'Postman.Postman', | |
| 'Python.Python.3.13', | |
| 'Rufus.Rufus', | |
| 'VideoLAN.VLC', | |
| 'WhatsApp.WhatsApp', | |
| 'WireGuard.WireGuard', | |
| 'WiresharkFoundation.Wireshark', | |
| 'Yubico.YubikeyManager', | |
| 'Inkscape.Inkscape', | |
| 'GlavSoft.RemoteRipple', | |
| 'hadolint.hadolint', | |
| 'mvdan.shfmt' | |
| ) | |
| $mobileApps = @( | |
| 'Cloudflare.Warp', | |
| 'Proton.ProtonVPN', | |
| 'Citrix.Workspace', | |
| '9WZDNCRDJ8LH' # Cisco AnyConnect (msstore ProductId) | |
| ) | |
| $gamingApps = @('EpicGames.EpicGamesLauncher','GOG.Galaxy','NexusMods.Vortex','Valve.Steam') | |
| $MobileDeviceAnswer = $MobileDevice | |
| $GamingDeviceAnswer = $GamingDevice | |
| if (-not $PSBoundParameters.ContainsKey('MobileDevice')) { $MobileDeviceAnswer = (Read-Host 'Is this a mobile device? (y/n)').Trim() } | |
| if ($MobileDeviceAnswer -or $MobileDevice) { | |
| if ($MobileDeviceAnswer -match '^[Yy]$' -or $MobileDevice) { | |
| Write-Host 'Adding mobile software...' -ForegroundColor Cyan | |
| $apps += $mobileApps | |
| } | |
| } | |
| # Gaming device prompt / inclusion | |
| if (-not $PSBoundParameters.ContainsKey('GamingDevice')) { $GamingDeviceAnswer = (Read-Host 'Is this a gaming device? (y/n)').Trim() } | |
| if ($GamingDeviceAnswer -or $GamingDevice) { | |
| if ($GamingDeviceAnswer -match '^[Yy]$' -or $GamingDevice) { | |
| Write-Host 'Adding gaming software (stores / mod manager)...' -ForegroundColor Cyan | |
| $apps += $gamingApps | |
| } | |
| } | |
| $apps = $apps | Sort-Object -Unique | |
| # Record mobile device choice for later conditional tweaks | |
| $script:IsMobileDevice = ($MobileDeviceAnswer -match '^[Yy]$' -or $MobileDevice) | |
| ### --- Optional Privacy Tweaks (after prompts so we know if mobile) --- ### | |
| if ($ApplyPrivacyTweaks) { | |
| Invoke-PrivacyTweaks | |
| } else { | |
| $applyAns = Read-Host 'Apply Windows privacy/ads/telemetry reduction tweaks? (y/n)' | |
| if ($applyAns -match '^[Yy]$') { Invoke-PrivacyTweaks } | |
| } | |
| Write-Host "Total packages queued: $($apps.Count)" -ForegroundColor Green | |
| ### --- Remove Built-in AppX Packages Before Installing --- ### | |
| Invoke-AppxRemoval | |
| ### --- Install Loop --- ### | |
| $sw = [System.Diagnostics.Stopwatch]::StartNew() | |
| foreach ($id in $apps) { | |
| Install-App -Id $id | |
| } | |
| $sw.Stop() | |
| Write-Host "Completed in $([int]$sw.Elapsed.TotalMinutes)m $(($sw.Elapsed.Seconds))s" -ForegroundColor Green | |
| try { Stop-Transcript | Out-Null } catch {} | |
| Write-Host 'All done.' -ForegroundColor Green |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment