It’s a lot of work reinstalling your system… But not with PowerShell and Winget!

Whenever I reinstall my Windows laptop (Or a new management VM), I have a lot of apps, modules, RSAT Tools, Sysinternals tools, and settings that need to be installed or configured. So… Let’s automate that 🙂

Below are my installation scripts which you can use to install everything, or just the programs or the PowerShell modules. Options are there to do so and you can easily add/remove things in the JSON file and let it run again. Just put the three files in one location, run install_apps.cmd as Administrator and lean back…

Shortcuts for newly installed software will be removed from your desktop, hate those ;), and programs that start automatically after installation will be closed. Be aware of the fact that your machine might restart during installation. Save your work, and you can re-run the script to continue installing the rest (It will check and skip things that are already installed)

Install_Apps.cmd

@echo off
TITLE Installing Apps, Features and PowerShell Modules...
echo Your computer might restart, please save your work before continuing!
echo After restart, you can restart this script to finish installation
pause
powershell -executionpolicy bypass -noprofile -file "%~dp0install_apps.ps1" -All
echo Logging is available in %Temp%\Install.log
echo Press any to update installed apps or CTRL-C to exit and then reboot your system manually
pause
winget upgrade --all --silent --force
echo. 
echo Done updating, press any key to quit and reboot your system manually
pause

Install_Apps.ps1

<#
.SYNOPSIS
 
Installs Programs, Modules, Features and settings using Winget and PowerShell
 
.DESCRIPTION
 
Installs/Configures all options from the .json file
 
.PARAMETER All
Install all Programs, Modules, Features and Settings
 
.PARAMETER Apps
Install all Software
 
.PARAMETER Features
Install all Windows Features
 
.PARAMETER MicrosftVCRuntime
Install all Microsoft VC++ Runtimes
 
.PARAMETER PowerShellCommands
Execute all PowerShell commands like Update-Help
 
.PARAMETER PowerShellModules
Install all PowerShell Modules
 
.PARAMETER PowerShellModulesUpdate
Update all installed PowerShell Modules to the latest version
 
.PARAMETER PowerShellProfile
Update the PowerShell Profile
 
.PARAMETER RSATTools
Install the Windows Remote System Administration Tools
 
.PARAMETER $SCCMTools
Install the System Center Configuration Manager tools like CMTrace
 
.PARAMETER SysInternalsSuite
Install the Windows Remote System Administration Tools
 
.PARAMETER IntuneWinAppUtil
Install the IntuneWinAppUtil in c:\windows\system32
 
.INPUTS
 
Defaults to -All if no other Parameters are specified
 
.OUTPUTS
 
Screen output and TransAction log which is available in %Temp%\Install.log
 
.EXAMPLE
 
PS> Install_Apps.ps1 -Apps
Installs all Applications
 
.EXAMPLE
 
PS> Install_Apps.ps1 -SCCMTools -PowerShellModule
Installs the System Center Configuration Manager Tools and installs all PowerShell Modules
 
.LINK
 
None
 
#>
 
#Parameters
[CmdletBinding(DefaultParameterSetName = "All")]
param (
    [Parameter(Mandatory = $False, HelpMessage = "Install all Software, Modules, Features and Settings", ParameterSetName = "All")][Switch]$All,
    [Parameter(Mandatory = $false, HelpMessage = "Install all Software", ParameterSetName = "Optional")][Switch]$Apps,
    [Parameter(Mandatory = $false, HelpMessage = "Install all Windows Features", ParameterSetName = "Optional")][Switch]$Features,
    [Parameter(Mandatory = $false, HelpMessage = "Install all Microsoft VC++ Runtimes", ParameterSetName = "Optional")][Switch]$MicrosftVCRuntime,
    [Parameter(Mandatory = $false, HelpMessage = "Execute all PowerShell commands like Update-Help", ParameterSetName = "Optional")][Switch]$PowerShellCommands,
    [Parameter(Mandatory = $false, HelpMessage = "Install all PowerShell Modules", ParameterSetName = "Optional")][Switch]$PowerShellModules,
    [Parameter(Mandatory = $false, HelpMessage = "Update all installed PowerShell Modules to the latest version", ParameterSetName = "Optional")][Switch]$PowerShellModulesUpdate,
    [Parameter(Mandatory = $false, HelpMessage = "Update the PowerShell Profile", ParameterSetName = "Optional")][Switch]$PowerShellProfile,
    [Parameter(Mandatory = $false, HelpMessage = "Install the Windows Remote System Administration Tools", ParameterSetName = "Optional")][Switch]$RSATTools,
    [Parameter(Mandatory = $false, HelpMessage = "Install the System Center Configuration Manager tools like CMTrace", ParameterSetName = "Optional")][Switch]$SCCMTools,
    [Parameter(Mandatory = $false, HelpMessage = "Install all SysInternals Suite tools and add them to the system path", ParameterSetName = "Optional")][Switch]$SysInternalsSuite,
    [Parameter(Mandatory = $false, HelpMessage = "Install the IntuneWinAppUtil to c:\windows\system32", ParameterSetName = "Optional")][Switch]$IntuneWinAppUtil
)
if ($PSCmdlet.ParameterSetName -eq 'All') {
    Write-Host ("No parameter was specified and using all options") -ForegroundColor Green
    $All = $True
}
 
#Requires -RunAsAdministrator
#Start Transcript logging in Temp folder
Start-Transcript $ENV:TEMP\install.log
 
#Set-Executionpolicy and no prompting
Set-ExecutionPolicy Bypass -Force:$True -Confirm:$false -ErrorAction SilentlyContinue
Set-Variable -Name 'ConfirmPreference' -Value 'None' -Scope Global
 
#Change invoke-webrequest progress bar to hidden for faster downloads
$ProgressPreference = 'SilentlyContinue'
 
#Import list of apps, features and modules that can be installed using json file
$json = Get-Content "$($PSScriptRoot)\Install_apps.json" | ConvertFrom-Json
 
#Check if Winget is installed, if not install it by installing VCLibs (Prerequisite) followed by Winget itself
if ($Apps -or $MicrosftVCRuntime -or $All) {
    if (!(Get-AppxPackage -Name Microsoft.Winget.Source)) {
        Write-Host ("Winget was not found and installing now") -ForegroundColor Yellow
        Invoke-Webrequest -uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -Outfile $ENV:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx
        Invoke-Webrequest -uri https://aka.ms/getwinget -Outfile $ENV:TEMP\winget.msixbundle    
        Add-AppxPackage $ENV:TEMP\Microsoft.VCLibs.x64.14.00.Desktop.appx -ErrorAction SilentlyContinue
        Add-AppxPackage -Path $ENV:TEMP\winget.msixbundle -ErrorAction SilentlyContinue
    }
}
 
if ($MicrosftVCRuntime -or $All) {
    #Install Microsoft Visual C++ Runtimes using WinGet
    Write-Host ("Installing Microsoft Visual C++ Runtime versions but skipping install if already present") -ForegroundColor Green
    $CurrentVC = Get-WmiObject -Class Win32_Product -Filter "Name LIKE '%Visual C++%'" -ErrorAction SilentlyContinue | Select-Object Name
    Foreach ($App in $json.MicrosftVCRuntime) {
        Write-Host ("Checking if {0} is already installed..." -f $App)
        if (!($CurrentVC | Select-String $App.split('+')[2].SubString(0, 4) | Select-String $App.split('-')[1])) {
            Write-Host ("{0} was not found and installing now" -f $App) -ForegroundColor Yellow
            winget.exe install $App --silent --force --source winget --accept-package-agreements --accept-source-agreements
        }
    }
}
 
if ($Apps -or $All) {
    #Install applications using WinGet
    Write-Host ("Installing Applications but skipping install if already present") -ForegroundColor Green
    Foreach ($App in $json.Apps) {
        Write-Host ("Checking if {0} is already installed..." -f $App)
        winget.exe list --id $App --accept-source-agreements | Out-Null
        if ($LASTEXITCODE -eq '-1978335212') {
            Write-Host ("{0} was not found and installing now" -f $App.Split('.')[1]) -ForegroundColor Yellow
            winget.exe install $App --silent --force --source winget --accept-package-agreements --accept-source-agreements
            Foreach ($Application in $json.ProcessesToKill) {
                get-process $Application -ErrorAction SilentlyContinue | Stop-Process -Force:$True -Confirm:$false
            }
        } 
    }
    #Clean-up downloaded Winget Packages
    Remove-Item $ENV:TEMP\Winget -Recurse -Force:$True -ErrorAction:SilentlyContinue
 
    #Cleanup shortcuts from installed applications
    Foreach ($File in $json.filestoclean) {
        Write-Host ("Cleaning {0} from personal ad public Windows Desktop" -f $File) -ForegroundColor Green
        $UserDesktop = ([Environment]::GetFolderPath("Desktop"))
        Get-ChildItem C:\users\public\Desktop\$File -ErrorAction SilentlyContinue | Where-Object LastWriteDate -LE ((Get-Date).AddHours( - 1)) | Remove-Item -Force:$True
        Get-ChildItem $UserDesktop\$File -ErrorAction SilentlyContinue | Where-Object LastWriteDate -LE ((Get-Date).AddHours( - 1)) | Remove-Item -Force:$True
        Get-ChildItem C:\users\public\Desktop\$File -Hidden -ErrorAction SilentlyContinue | Where-Object LastWriteDate -LE ((Get-Date).AddHours( - 1)) | Remove-Item -Force:$True
        Get-ChildItem $UserDesktop\$File -Hidden -ErrorAction SilentlyContinue | Where-Object LastWriteDate -LE ((Get-Date).AddHours( - 1)) | Remove-Item -Force:$True
    }
}
 
if ($SCCMTools -or $All) {
    #Download and install System Center 2012 R2 Configuration Manager Toolkit for CMTRACE tool
    Write-Host ("Checking if System Center 2012 R2 Configuration Manager Toolkit is already installed") -ForegroundColor Green
    if (!(Test-Path 'C:\Program Files (x86)\ConfigMgr 2012 Toolkit R2')) {
        Write-Host ("SCCM 2012 R2 Toolkit was not found and installing now") -ForegroundColor Yellow
        Invoke-Webrequest -uri https://download.microsoft.com/download/5/0/8/508918E1-3627-4383-B7D8-AA07B3490D21/ConfigMgrTools.msi -UseBasicParsing -Outfile $ENV:TEMP\ConfigMgrTools.msi
        msiexec.exe /i $ENV:TEMP\ConfigMgrTools.msi /qn    
    }
}
 
if ($SysInternalsSuite -or $All) {
    #Download and extract SysInternals Suite and add to system path
    Write-Host ("Checking if SysInternals Suite is present") -ForegroundColor Green
    if (!(Test-Path 'C:\Program Files (x86)\SysInterals Suite')) {
        Write-Host ("SysInternalsSuite was not found and installing now") -ForegroundColor Yellow
        Invoke-Webrequest -uri https://download.sysinternals.com/files/SysinternalsSuite.zip -Outfile $ENV:TEMP\SysInternalsSuite.zip
        Expand-Archive -LiteralPath $ENV:TEMP\SysInternalsSuite.zip -DestinationPath 'C:\Program Files (x86)\SysInterals Suite'
        $OldPath = (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH).Path
        $NewPath = $OldPath + ';C:\Program Files (x86)\SysInterals Suite\'
        Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $NewPath
    }
}
 
if ($IntuneWinAppUtil -or $All) {
    #Download IntuneWinAppUtil to c:\windows\system32
    Write-Host ("Checking if IntuneWinAppUtil Suite is present") -ForegroundColor Green
    if (!(Test-Path 'c:\windows\system32\IntuneWinAppUtil.exe')) {
        Write-Host ("IntuneWinAppUtil was not found and installing now") -ForegroundColor Yellow
        Invoke-Webrequest -uri https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool/raw/master/IntuneWinAppUtil.exe -Outfile c:\windows\system32\intunewinapputil.exe
    }
}
 
if ($Features -or $All) {
    #Install Features
    Write-Host ("Installing Features but skipping install if already present") -ForegroundColor Green
    Foreach ($Feature in $json.Features) {
        Write-Host ("Checking if {0} is already installed..." -f $Feature)
        if ((Get-WindowsOptionalFeature -Online -FeatureName:$Feature).State -ne 'Enabled') {
            Write-Host ("{0} was not found and installing now" -f $Feature) -ForegroundColor Yellow
            Enable-WindowsOptionalFeature -Online -FeatureName:$Feature -NoRestart:$True -ErrorAction SilentlyContinue | Out-Null
        }
    }
}
 
if ($PowerShellModules -or $All) {
    #Install PowerShell Modules
    Write-Host ("Installing Modules but skipping install if already present") -ForegroundColor Green
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
 
    Set-PSRepository PSGallery -InstallationPolicy Trusted
 
    Foreach ($Module in $json.PowerShellModules) {
        Write-Host ("Checking if the {0} is already installed..." -f $Module)
        if (!(Get-Module $Module -ListAvailable)) {
            Write-Host ("{0} PowerShell Module was not found and installing now" -f $Module) -ForegroundColor Yellow
            Install-Module -Name $Module -Scope AllUsers -Force:$True -AllowClobber:$True
        }
    }
}
 
if ($RSATTools -or $All) {
    #Install selected RSAT Tools
    Write-Host ("Installing RSAT components but skipping install if already present") -ForegroundColor Green
    Foreach ($Tool in $json.RSATTools) {
        Write-Host ("Checking if {0} is already installed..." -f $Tool.Split('~')[0])
        if ((Get-WindowsCapability -Online -Name:$Tool).State -ne 'Installed') {
            Write-Host ("{0} was not found and installing now" -f $Tool.Split('~')[0]) -ForegroundColor Yellow
            DISM.exe /Online /add-capability /CapabilityName:$Tool /NoRestart /Quiet | Out-Null
        }
    }
}
 
if ($PowerShellProfile -or $All) {
    #Add settings to PowerShell Profile (Creating Profile if not exist)
    Write-Host ("Adding settings to PowerShell Profile but skipping setting if already present") -ForegroundColor Green
    Foreach ($Setting in $json.PowerShellProfile) {
        Write-Host ("Checking if {0} is already added..." -f $Setting)
        if (!(Test-Path $profile)) {
            New-Item -Path $profile -ItemType:File -Force:$True | out-null
        }
        if (!(Get-Content $profile | Select-String -Pattern $Setting -SimpleMatch)) {
            Write-Host ("{0} was not found and adding now" -f $Setting) -ForegroundColor Yellow
            Add-Content $profile "`n$($Setting)"
        }
    }
}
 
if ($PowerShellModulesUpdate -or $All) {
    #Update PowerShell Modules if needed
    Write-Host ("Checking for older versions of PowerShell Modules and removing those if present") -ForegroundColor Green
    Set-PSRepository PSGallery -InstallationPolicy Trusted
 
    Foreach ($Module in Get-InstalledModule | Select-Object Name) {
        Write-Host ("Checking for older versions of the {0} PowerShell Module" -f $Module.Name)
        $AllVersions = Get-InstalledModule -Name $Module.Name -AllVersions -ErrorAction:SilentlyContinue
        $AllVersions = $AllVersions | Sort-Object PublishedDate -Descending
        $MostRecentVersion = $AllVersions[0].Version
        if ($AllVersions.Count -gt 1 ) {
            Foreach ($Version in $AllVersions) {
                if ($Version.Version -ne $MostRecentVersion) {
                    Write-Host ("Uninstalling previous version {0} of Module {1}" -f $Version.Version, $Module.Name) -ForegroundColor Yellow
                    Uninstall-Module -Name $Module.Name -RequiredVersion $Version.Version -Force:$True
                }
            }
        }
    }
}
 
if ($PowerShellCommands -or $All) {
    #Run PowerShell commandline options
    Write-Host ("Running Commandline options and this could take a while") -ForegroundColor Green
    Foreach ($Command in $json.PowerShellCommands) {
        Write-Host ("Running {0}" -f $Command) -ForegroundColor Yellow
        Powershell.exe -Executionpolicy Bypass -Command $Command
    }
}
 
#Stop Transcript logging
Stop-Transcript

Install_Apps.json

{
   "Apps": [
      "Microsoft.SQLServerManagementStudio",
      "7zip.7zip",
      "Adobe.Acrobat.Reader.64-bit",
      "AnyConnect",
      "BraveSoftware.BraveBrowser",
      "Canonical.Ubuntu",
      "Cisco.CiscoWebexMeetings",
      "Citrix.Workspace",
      "Famatech.AdvancedIPScanner",
      "Fortinet.FortiClientVPN",
      "Git.Git",
      "GitHub.GitHubDesktop",
      "Google.Chrome",
      "Graphviz.Graphviz",
      "Insecure.Nmap",
      "JAMSoftware.TreeSize.Free",
      "Microsoft.AzureCLI",
      "Microsoft.AzureStorageExplorer",
      "Microsoft.DeploymentToolkit",
      "Microsoft.dotnetRuntime.3.1",
      "Microsoft.dotnetRuntime.5",
      "Microsoft.EdgeWebView2Runtime",
      "Microsoft.PowerBI",
      "Microsoft.Powershell",
      "Microsoft.PowerToys",
      "Microsoft.RemoteDesktopClient",
      "Microsoft.VisualStudioCode",
      "Microsoft.WindowsAdminCenter",
      "Microsoft.WindowsADK",
      "Microsoft.WindowsTerminal",
      "Mozilla.FireFox",
      "Notepad++.Notepad++",
      "Obsidian.Obsidian",
      "Ookla.Speedtest",
      "OpenVPNTechnologies.OpenVPNConnect",
      "Oracle.JavaRuntimeEnvironment",
      "PuTTY.PuTTY",
      "Python.Python.3",
      "Rufus.Rufus",
      "Teamviewer.Teamviewer",
      "Telerik.Fiddler",
      "WinSCP.WinSCP",
      "WiresharkFoundation.Wireshark",
      "Zoom.Zoom"
   ],
   "Features": [
      "Containers-DisposableClientVM",
      "Microsoft-Windows-Subsystem-Linux",
      "TelnetClient",
      "TFTP"
   ],
   "FilesToClean": [
      "*.lnk",
      "desktop.ini"
   ],
   "MicrosftVCRuntime": [
      "Microsoft.VC++2008Redist-x86",
      "Microsoft.VC++2008Redist-x64",
      "Microsoft.VC++2010Redist-x64",
      "Microsoft.VC++2010Redist-x86",
      "Microsoft.VC++2012Redist-x64",
      "Microsoft.VC++2012Redist-x86",
      "Microsoft.VC++2013Redist-x64",
      "Microsoft.VC++2013Redist-x86"
   ],
   "ProcessesToKill": [
      "advanced_ip_scanner",
      "OpenVPNConnect",
      "PowerToys.Settings",
      "PtSrv",
      "PtSrv"
   ],
   "PowerShellCommands": [
      "Update-Help -Confirm:$False -Force:$True -ErrorAction:SilentlyContinue"
   ],
   "PowerShellModules": [
      "AIPService",
      "Az",
      "Azure.AnalysisServices",
      "Azure.Storage",
      "AzureAD",
      "AzureADPreview",
      "AzureRM",
      "AZViz",
      "CredentialManager",
      "ExchangeOnlineManagement",
      "IntuneBackupAndRestore",
      "IntuneDocumentation",
      "IntuneWin32App",
      "M365Documentation",
      "Microsoft.Graph",
      "Microsoft.Online.SharePoint.PowerShell",
      "Microsoft.PowerApps.Administration.Powershell",
      "Microsoft.PowerShell.Crescendo",
      "Microsoft.PowerShell.SecretManagement",
      "Microsoft.PowerShell.SecretStore",
      "Microsoft.RDInfra.RDPowershell",
      "MicrosoftTeams",
      "MSAL.PS",
      "MSCommerce",
      "MSGraph",
      "MSGraphFunctions",
      "MSOnline",
      "oh-my-posh",
      "ORCA",
      "PackageManagement",
      "PnP.PowerShell",
      "Posh-Git",
      "PowerShellGet",
      "Powershell-yaml",
      "ps2exe",
      "PSFramework",
      "PSGraph",
      "PSParseHTML",
      "PSReadLine",
      "PSSlack",
      "PSWindowsUpdate",
      "PSWord",
      "PSWriteWord",
      "SharePointPnPPowerShellOnline",
      "SQLServer",
      "Terminal-Icons",
      "VMware.CloudServices",
      "VMware.DeployAutomation",
      "VMware.ImageBuilder",
      "VMware.PowerCLI",
      "VMware.Sdk.Runtime",
      "VMware.Sdk.vSphere",
      "VMware.Vim",
      "VMware.VumAutomation",
      "WindowsAutoPilotIntune",
      "WindowsConsoleFonts"
   ],
   "PowerShellProfile": [
      "Import-Module Posh-Git",
      "Set-PSReadlineKeyHandler -Key Tab -Function Complete",
      "Set-PSReadLineOption -PredictionSource History",
      "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12"
   ],
   "RSATTools": [
      "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0",
      "Rsat.BitLocker.Recovery.Tools~~~~0.0.1.0",
      "Rsat.CertificateServices.Tools~~~~0.0.1.0",
      "Rsat.DHCP.Tools~~~~0.0.1.0",
      "Rsat.Dns.Tools~~~~0.0.1.0",
      "Rsat.FailoverCluster.Management.Tools~~~~0.0.1.0",
      "Rsat.FileServices.Tools~~~~0.0.1.0",
      "Rsat.GroupPolicy.Management.Tools~~~~0.0.1.0",
      "Rsat.IPAM.Client.Tools~~~~0.0.1.0",
      "Rsat.LLDP.Tools~~~~0.0.1.0",
      "Rsat.NetworkController.Tools~~~~0.0.1.0",
      "Rsat.NetworkLoadBalancing.Tools~~~~0.0.1.0",
      "Rsat.RemoteAccess.Management.Tools~~~~0.0.1.0",
      "Rsat.RemoteDesktop.Services.Tools~~~~0.0.1.0",
      "Rsat.ServerManager.Tools~~~~0.0.1.0",
      "Rsat.Shielded.VM.Tools~~~~0.0.1.0",
      "Rsat.StorageMigrationService.Management.Tools~~~~0.0.1.0",
      "Rsat.StorageReplica.Tools~~~~0.0.1.0",
      "Rsat.SystemInsights.Management.Tools~~~~0.0.1.0",
      "Rsat.VolumeActivation.Tools~~~~0.0.1.0",
      "Rsat.WSUS.Tools~~~~0.0.1.0"
   ]
}

The output

When running the script, it will report progress like this:

Download the script(s) from GitHub here

0 thoughts on “It’s a lot of work reinstalling your system… But not with PowerShell and Winget!

  1. Pingback: PowerShell is fun :) Using Run-in-Sandbox for testing scripts and Intune packages

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.