Public/Install-WingetProfile.ps1
|
function Install-WingetProfile { <# .SYNOPSIS Install packages from shareable community setup profiles. .DESCRIPTION Loads a curated package profile (JSON) from a URL, GitHub gist, local file, or the built-in profile library, then installs all listed packages. Profiles are the ecosystem builder: anyone can publish a "Gamer Setup", "DevOps Toolkit", or "Data Science Stack" as a simple JSON file and share it with a URL. One command replicates an entire workflow. .PARAMETER Url URL to a profile JSON file (GitHub raw, gist, any HTTP). .PARAMETER Path Path to a local profile JSON file. .PARAMETER Name Name of a built-in profile: Developer, Gamer, DataScience, DevOps, Designer, Security, Productivity, Minimal. .PARAMETER List Show available built-in profiles and their package counts. .PARAMETER WhatIf Show what would be installed without actually installing. .PARAMETER SkipInstalled Skip packages already installed. Default: true. .PARAMETER Export Export your current installed packages as a shareable profile. .PARAMETER ExportPath Path for exported profile. Default: .\my-profile.json .EXAMPLE Install-WingetProfile -Name Developer Installs the built-in Developer profile. .EXAMPLE Install-WingetProfile -Url "https://raw.githubusercontent.com/user/repo/main/profile.json" Installs from a community-shared profile URL. .EXAMPLE Install-WingetProfile -Export -ExportPath ".\my-setup.json" Exports your installed packages as a shareable profile. .EXAMPLE Install-WingetProfile -List Shows all built-in profiles. .NOTES Author: Matthew Bubb Profile format: { "name": "...", "packages": ["Id1", "Id2", ...] } #> [CmdletBinding(DefaultParameterSetName = 'ByName')] param( [Parameter(ParameterSetName = 'ByUrl', Mandatory)] [string]$Url, [Parameter(ParameterSetName = 'ByPath', Mandatory)] [ValidateScript({ Test-Path $_ })] [string]$Path, [Parameter(ParameterSetName = 'ByName', Position = 0)] [ValidateSet('Developer', 'Gamer', 'DataScience', 'DevOps', 'Designer', 'Security', 'Productivity', 'Minimal')] [string]$Name = 'Developer', [Parameter(ParameterSetName = 'List', Mandatory)] [switch]$List, [switch]$WhatIf, [bool]$SkipInstalled = $true, [Parameter(ParameterSetName = 'Export', Mandatory)] [switch]$Export, [Parameter(ParameterSetName = 'Export')] [string]$ExportPath = ".\my-profile.json" ) # --- BUILT-IN PROFILE LIBRARY --- $builtInProfiles = @{ 'Developer' = @{ Name = "Full-Stack Developer" Description = "Everything a modern software developer needs" Author = "WingetBatch" Packages = @( 'Git.Git', 'Microsoft.VisualStudioCode', 'OpenJS.NodeJS.LTS', 'Python.Python.3.12', 'Docker.DockerDesktop', 'Microsoft.DotNet.SDK.8', 'GoLang.Go', 'PostgreSQL.PostgreSQL', 'Redis.Redis', 'Postman.Postman', 'Microsoft.WindowsTerminal', '7zip.7zip' ) } 'Gamer' = @{ Name = "Gaming Setup" Description = "Game libraries, streaming, and communication" Author = "WingetBatch" Packages = @( 'Valve.Steam', 'Discord.Discord', 'OBSProject.OBSStudio', 'EpicGames.EpicGamesLauncher', 'GOG.Galaxy', 'Spotify.Spotify', 'Mozilla.Firefox', 'VideoLAN.VLC', '7zip.7zip' ) } 'DataScience' = @{ Name = "Data Science Stack" Description = "Python, R, Jupyter, and visualization tools" Author = "WingetBatch" Packages = @( 'Python.Python.3.12', 'Anaconda.Anaconda3', 'RProject.R', 'RStudio.RStudio.OpenSource', 'Microsoft.VisualStudioCode', 'Git.Git', 'Docker.DockerDesktop', 'PostgreSQL.PostgreSQL', 'Julia.Julia', 'Microsoft.PowerBI' ) } 'DevOps' = @{ Name = "DevOps & Platform Engineering" Description = "Infrastructure, containers, cloud CLIs, monitoring" Author = "WingetBatch" Packages = @( 'Git.Git', 'Docker.DockerDesktop', 'Kubernetes.kubectl', 'Hashicorp.Terraform', 'Helm.Helm', 'Microsoft.AzureCLI', 'Amazon.AWSCLI', 'Python.Python.3.12', 'Microsoft.VisualStudioCode', 'Grafana.Grafana', 'WiresharkFoundation.Wireshark', 'PuTTY.PuTTY' ) } 'Designer' = @{ Name = "Creative & Design" Description = "Image editing, vector art, 3D, and video" Author = "WingetBatch" Packages = @( 'Figma.Figma', 'GIMP.GIMP', 'Inkscape.Inkscape', 'BlenderFoundation.Blender', 'DaVinciResolve.DaVinciResolve', 'Audacity.Audacity', 'OBSProject.OBSStudio', 'VideoLAN.VLC', 'Git.Git', '7zip.7zip' ) } 'Security' = @{ Name = "Security Research Lab" Description = "Network analysis, RE tools, and lab infrastructure" Author = "WingetBatch" Packages = @( 'WiresharkFoundation.Wireshark', 'Nmap.Nmap', 'Python.Python.3.12', 'Oracle.VirtualBox', 'Ghidra.Ghidra', 'GnuPG.GnuPG', 'KeePassXCTeam.KeePassXC', 'Git.Git', 'Microsoft.VisualStudioCode', 'Docker.DockerDesktop', 'Tor.TorBrowser' ) } 'Productivity' = @{ Name = "Productivity & Office" Description = "Communication, notes, and workflow tools" Author = "WingetBatch" Packages = @( 'Mozilla.Firefox', 'Obsidian.Obsidian', 'Microsoft.Teams', 'Slack.Slack', 'Zoom.Zoom', 'ShareX.ShareX', '7zip.7zip', 'VideoLAN.VLC', 'Notion.Notion', 'Todoist.Todoist' ) } 'Minimal' = @{ Name = "Minimal Essentials" Description = "Bare minimum: browser, editor, git, terminal" Author = "WingetBatch" Packages = @( 'Git.Git', 'Microsoft.VisualStudioCode', 'Mozilla.Firefox', 'Microsoft.WindowsTerminal', '7zip.7zip' ) } } # --- LIST --- if ($List) { Write-Host "" Write-Host " ╔══════════════════════════════════════════════════════╗" -ForegroundColor Cyan Write-Host " ║ Built-in Setup Profiles ║" -ForegroundColor Cyan Write-Host " ╚══════════════════════════════════════════════════════╝" -ForegroundColor Cyan Write-Host "" foreach ($profile in $builtInProfiles.GetEnumerator() | Sort-Object Key) { Write-Host " $($profile.Key.PadRight(14))" -NoNewline -ForegroundColor Yellow Write-Host " $($profile.Value.Name)" -NoNewline -ForegroundColor White Write-Host " ($($profile.Value.Packages.Count) pkgs)" -ForegroundColor DarkGray Write-Host " $(''.PadRight(14)) $($profile.Value.Description)" -ForegroundColor DarkGray } Write-Host "" Write-Host " Usage: Install-WingetProfile -Name <ProfileName>" -ForegroundColor DarkGray Write-Host " Custom: Install-WingetProfile -Url <url> | -Path <file>" -ForegroundColor DarkGray Write-Host "" return } # --- EXPORT --- if ($Export) { $installed = Microsoft.WinGet.Client\Get-WinGetPackage -ErrorAction SilentlyContinue $profile = @{ name = "My Setup - $env:COMPUTERNAME" description = "Exported from $($env:COMPUTERNAME) on $(Get-Date -Format 'yyyy-MM-dd')" author = $env:USERNAME created = (Get-Date -ToString 'o') packages = @($installed | ForEach-Object { $_.Id } | Sort-Object) } $profile | ConvertTo-Json -Depth 5 | Set-Content -Path $ExportPath -Encoding UTF8 Write-Host "" Write-Host " ✓ Profile exported: $ExportPath" -ForegroundColor Green Write-Host " $($profile.packages.Count) packages | Share this file or host it on GitHub." -ForegroundColor DarkGray Write-Host "" return } # --- LOAD PROFILE --- $profile = $null if ($Url) { Write-Host " Fetching profile from: $Url" -ForegroundColor DarkGray try { $raw = Invoke-RestMethod -Uri $Url -ErrorAction Stop $profile = if ($raw -is [string]) { $raw | ConvertFrom-Json -AsHashtable } else { $raw } } catch { Write-Error "Failed to fetch profile: $($_.Exception.Message)" return } } elseif ($Path) { $profile = Get-Content -Path $Path -Raw | ConvertFrom-Json -AsHashtable } else { $profile = $builtInProfiles[$Name] } if (-not $profile -or -not $profile.Packages) { Write-Error "Invalid profile: no 'packages' array found." return } $profileName = $profile.Name ?? $profile.name ?? "Custom Profile" $packageList = $profile.Packages ?? $profile.packages # --- Filter installed --- $toInstall = $packageList if ($SkipInstalled) { $installed = Microsoft.WinGet.Client\Get-WinGetPackage -ErrorAction SilentlyContinue $installedIds = @($installed | ForEach-Object { $_.Id }) $toInstall = $packageList | Where-Object { $_ -notin $installedIds } $skippedCount = $packageList.Count - $toInstall.Count } # --- Display --- Write-Host "" Write-Host " ╔══════════════════════════════════════════════════════╗" -ForegroundColor Magenta Write-Host " ║ $profileName" -NoNewline -ForegroundColor Magenta Write-Host (" ".PadRight([Math]::Max(0, 40 - $profileName.Length))) -NoNewline -ForegroundColor Magenta Write-Host "║" -ForegroundColor Magenta Write-Host " ╚══════════════════════════════════════════════════════╝" -ForegroundColor Magenta Write-Host "" if ($profile.Description -or $profile.description) { Write-Host " $($profile.Description ?? $profile.description)" -ForegroundColor DarkGray Write-Host "" } Write-Host " Total: $($packageList.Count) | To install: $($toInstall.Count)" -NoNewline -ForegroundColor White if ($SkipInstalled -and $skippedCount -gt 0) { Write-Host " | Already installed: $skippedCount" -NoNewline -ForegroundColor Green } Write-Host "" Write-Host "" if ($toInstall.Count -eq 0) { Write-Host " ✓ All packages already installed. Nothing to do!" -ForegroundColor Green Write-Host "" return } # Show package list foreach ($pkg in $toInstall) { Write-Host " • $pkg" -ForegroundColor Cyan } Write-Host "" if ($WhatIf) { Write-Host " [WhatIf] Would install $($toInstall.Count) packages. Remove -WhatIf to execute." -ForegroundColor Yellow Write-Host "" return } # --- INSTALL --- $confirm = $PSCmdlet.ShouldContinue("Install $($toInstall.Count) packages from '$profileName'?", "Confirm Profile Install") if (-not $confirm) { Write-Host " Cancelled." -ForegroundColor Yellow return } $success = 0; $failed = 0 $j = 0 foreach ($pkgId in $toInstall) { $j++ Write-Host " [$j/$($toInstall.Count)] $pkgId" -NoNewline -ForegroundColor Cyan Write-Host "..." -NoNewline try { Microsoft.WinGet.Client\Install-WinGetPackage -Id $pkgId -Mode Silent | Out-Null Write-Host " ✓" -ForegroundColor Green $success++ } catch { Write-Host " ✗ ($($_.Exception.Message))" -ForegroundColor Red $failed++ } } Write-Host "" Write-Host " Profile '$profileName' complete: $success installed, $failed failed." -ForegroundColor $(if ($failed -eq 0) { 'Green' } else { 'Yellow' }) Write-Host "" } |