Skip to content

Instantly share code, notes, and snippets.

@LM1LC3N7
Last active May 28, 2024 15:32
Show Gist options
  • Save LM1LC3N7/272d134a7461f4eb83fe01ad1607d40b to your computer and use it in GitHub Desktop.
Save LM1LC3N7/272d134a7461f4eb83fe01ad1607d40b to your computer and use it in GitHub Desktop.
Automating application install on a fresh Windows 10/11 using Microsoft Store and winget

FASIC

Fast and Automatic Software Install and Configuration (on Windows)

Need

Install my common apps on Windows as fast as possible and mostly automatically, after a reimaging or reset of my OS. Also change some settings on Windows, like disabling the bing search in the start menu.

How it works?

This PowerShell script uses Microsoft Store and winget to download and install a list of application, configured at the top of the file.

Microsoft store should be privilegied for apps with a real good desktop version, like PowerToys, VScode, etc. Avoid lights versions specific to the store, like VLC. That way, those apps will be automatically updated in the backend.

For all other apps, find them in the winget repo, for example online using https://winget.run/.

For apps that are not in either of these locations, there is a variable where URL and filenames can be listed, for a download only. You will have the opportunity to install manually these files lately.

Configuration

Update variables at the top of the file.

  • destFolderName: Configure where installers that cannot be automatically installed will be downloaded
  • DownloadOnlyAppList: Filenames and related download URLs to be gathered in the folder configured in "destFolderName"
  • storeAppListID: List of IDs and names of apps in Microsoft Store (tip, use the online web version and URL to get the info)
  • wingetAppList_base: List of winget apps to install
  • wingetAppList_poweruser: Secondary list of winget apps to install. Can be empty.

Winget: Known errors and bugs

# Last Update : 28/05/2024
# LM1LC3NT
# Script version 2.5
#
# Changelog:
#
# v2.5
# Adding W11Debloat project with default configuration. Bug solved when installing gsudo and not being able to call it
# in the same terminal.
# Also solve a bug where the powershell $PROFILE file cannot be executed as it should be at terminal startup.
#
# v2.0
# Switching from mainly chocolatey to winget, as this tool is included to Windows 11 and seems more powerfull.
# Finaly, wingetUI allows automatic and background checks and updates. A lot better than the Microsoft Store
# than does not really update apps (or every months, not days).
#
# Execute the script NOT as admin. Elevated prompts will be generated when needed.
# Note: a folder will be created on the same path, see variable "$destFolderName" bellow.
# Command:
# powershell -ExecutionPolicy Bypass -File Setup_app_new_windows.ps1
#
# Microsoft Store vs Standard apps?
# Some apps in the MS Store are the same than their standard installer version.
# In that case, prefer to use the MS Store, as it will constantly and in background update them (in theory at least).
# In other cases, UWP app are just lighter or PWA versions, so way less interesing / performant than
# their standard alternative versions (for example, VLC).
#
#
# Automating software updates using winget, without WingetUI:
# winget update --all
#
# OTHER TOOLS NICE TO HAVE, but can't apply it now in one file script
# Context Menu for Hash Calculation with PS: https://www.tenforums.com/tutorials/78681-add-file-hash-context-menu-windows-8-10-a.html
# -----
# CONFIGURATION
# -----
### MANUAL APP TO INSTALL : Download to folder
$destFolderName = "_Manual_Install_Softwares"
$DownloadOnlyAppList = @(
@("Outline-Client.exe", "https://s3.amazonaws.com/outline-releases/client/windows/stable/Outline-Client.exe") # OutlineVPN Client
)
### APPLICATION INSTALLED FROM MICROSOFT STORE
# Get ID from Microsoft Store Online (check URL). Example: https://apps.microsoft.com/store/detail/powershell/9MZ1SNWT0N5D
$storeAppListID = @(
"9N0DX20HK701", # Terminal
"xp8k0hkjfrxgck", # Oh My Posh
"XP89DCGQ3K6VLD", # PowerToys
"9MZ1SNWT0N5D", # PowerShell
"9NBLGGH516XP", # EarTrumpet
"XP9KHM4BK9FZ7Q", # VSCode
"XP8C9QZMS2PC1T" # Brave Browser
)
$wingetAppList_base = @(
"Notepad++.Notepad++",
"mcmilk.7zip-zstd", # (alternative avec plus de lib de compression, sinon " 7zip.7zip")
"NextDNS.NextDNS.Desktop",
"Mobatek.MobaXterm",
"VideoLAN.VLC",
"Foxit.FoxitReader",
"undergroundwires.privacy.sexy", # enforce privacy & security best-practices on Windows
"TeamSophia.SophiApp" # open source tweaker for fine-tuning of W10 + W11
)
$wingetAppList_poweruser = @(
"ShareX.ShareX", # (ou FastStone.Capture)
"FilesCommunity.Files",
"Git.Git",
"Safing.Portmaster",
"Microsoft.Powershell"
)
# "JanDeDobbeleer.OhMyPosh"
# "Microsoft.VisualStudioCode",
# -------------------------------------------------------------
# -----
# FUNCTIONS
# -----
function print.err {
Param(
[Parameter()]
[String]
$Message
)
Write-Host -ForegroundColor Red "[ERROR] $Message"
}
function print.warn {
Param(
[Parameter()]
[String]
$Message
)
Write-Host -ForegroundColor Yellow "$Message"
}
function print.success {
Param(
[Parameter()]
[String]
$Message
)
Write-Host -ForegroundColor Green "$Message"
}
function print.info {
Param(
[Parameter()]
[String]
$Message
)
Write-Host "$Message"
}
function wingetInstallFromListFromMsstore($list, $existingApps){
foreach ($app in $list) {
# Check if app exist or install it
if (!($existingApps | findstr $app)) {
print.info "Installing $app from Microsoft Store..."
try { winget install --exact --id $app --source msstore --silent --accept-package-agreements --accept-package-agreements }
catch { print.err "Error: $_" }
} else {
print.info "$app is already installed"
}
}
print.info
}
function wingetInstallFromListFromWinget($list, $existingApps){
foreach ($app in $list) {
# Check if app exist or install it
if (!($existingApps | findstr $app)) {
print.info "Installing $app from Winget..."
try { winget install --exact --id $app --source winget --silent --accept-source-agreements --accept-package-agreements }
catch { print.err "Error: $_" }
} else {
print.info "$app is already installed"
}
}
print.info
}
# -------------------------------------------------------------
# -----
# CHECK DEPENCIES
# -----
# Check for winget
# https://github.com/microsoft/winget-cli/releases/
$wingetPath = Get-Command -Name winget
if ($wingetPath) {
print.success "[OK] Winget is installed."
} else {
print.err "Winget is not installed."
print.info "Download and install the last release from GitHub or azure:"
print.info "https://github.com/microsoft/winget-cli/releases/ or https://winget.azureedge.net/cache/source.msix"
print.info
# TODO Install winget + restart script
Exit-PSSession
}
print.info
# Check for gsudo
# This will be used then to install apps using admin rights when they need to
$sudoPath = Get-Command -Name gsudo
if ($sudoPath) {
print.success "[OK] gsudo is installed."
} else {
print.warn "gsudo is not installed."
print.info "Starting installation using winget..."
winget install -e --id gerardog.gsudo --silent --accept-source-agreements --accept-package-agreements
# Test error
if ($?) {
print.warn "gsudo installed."
} else {
print.err "gsudo installation error, exiting"
print.info
Exit 1
}
}
# Reload Path
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
print.info
# Refreshing installed local packages list
print.success "[INFO] Refreshing list of installed packages."
$existingApps = winget list
print.info
# Ask once to elevate
print.success "[INFO] Temporary caching admin privileges."
gsudo cache on
print.info
# Update winget cache and solve problems
# known issue: https://github.com/microsoft/winget-cli/issues/2686
print.success "[INFO] Updating winget source"
Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.Winget.Source_8wekyb3d8bbwe
winget source reset
winget source update
#winget source update --name winget
# Other solution: https://github.com/microsoft/winget-cli/issues/3652#issuecomment-1956699129
# winget install -s msstore --id 9NBLGGH4NNS1
print.info
# -------------------------------------------------------------
# -----
# Export bitlocker key
# -----
# Check if bitlocker is enabled and export recovery key on the desktop
print.success "[INFO] Checking bitlocker for C:"
$blinfo = sudo { Get-BitlockerVolume -MountPoint "C:" }
$BLexportPath = "$env:USERPROFILE\Desktop"
if($blinfo.ProtectionStatus -eq 1){
print.info "Exporting Bitlocker key for disk C: to $BLexportPath\bitlocker_recovery.txt"
(sudo { Get-BitLockerVolume -MountPoint C}).KeyProtector.KeyProtectorId >> $BLexportPath\bitlocker_recovery.txt
(sudo { Get-BitLockerVolume -MountPoint C}).KeyProtector.recoverypassword >> $BLexportPath\bitlocker_recovery.txt
# Test error
if ($?) {
print.success "[OK] Bitlocker recovery key for disk C: exported to $BLexportPath\bitlocker_recovery.txt."
print.info
} else {
print.err "Error while exporting the bitlocker recovery key for disk C: to $BLexportPath\bitlocker_recovery.txt."
print.info
}
} else {
print.warn "Bitlocker not detected for disk C:"
print.info
}
# -------------------------------------------------------------
# -----
# Disable Cortana + Bing in start menu
# -----
# Now handled by W11Debloat (https://github.com/Raphire/Win11Debloat)
print.success "[INFO] Windows11 Debloat!"
& ([scriptblock]::Create((irm "https://raw.githubusercontent.com/Raphire/Win11Debloat/master/Get.ps1"))) -RunDefaults -Silent
print
# -------------------------------------------------------------
# -----
# Downloading apps manually
# -----
print.success "[INFO] Downloading apps that can't be automatically installed using winget or Microsoft Store."
$destinationFolder = $PSScriptRoot + "\" + $destFolderName + "\"
$validUrlPattern = "^(https?|ftp)://[^\s/$.?#].[^\s]*$"
# Folder creation
print.info "Creating folder to download installers: $destinationFolder"
New-Item -Path $destinationFolder -ItemType Directory -Force -ErrorAction SilentlyContinue > $null
# DL files
foreach ($row in $DownloadOnlyAppList) {
$url = $row[1]
$filename = $row[0]
$destinationPath = $destinationFolder + $filename
if ($url -match $validUrlPattern) {
print.info "Downloading $filename from $url ..."
try { (New-Object System.Net.WebClient).DownloadFile($url, $destinationPath) }
catch { print.err "Error: $_" }
} else {
print.err "Invalid URL: $url"
}
}
print.info
# -------------------------------------------------------------
# -----
# Installing & Activating Hyper-V
# -----
print.success "[INFO] Installing and activating optional feature: Microsoft-Hyper-V"
$featureInstalled = sudo { Get-WmiObject -query "select * from win32_optionalfeature where installstate= 1 and name = 'Microsoft-Hyper-V'" }
If (-not $featureInstalled) {
# Feature not installed, installing
# Hyper-V
try {
sudo dism /online /enable-feature /all /featurename:Microsoft-Hyper-V /norestart
} catch {
print.err "Error: $_"
print.info
}
try {
sudo dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
print.success "[OK] Hyper-V installed"
} catch {
print.err "Error: $_"
}
} else {
# Feature already installed
print.info "Feature is already installed."
}
print.info
# -------------------------------------------------------------
# -----
# Updating and installing Microsoft Store apps
# -----
# Updating Microsoft Store apps
# Source: https://github.com/microsoft/winget-cli/issues/2854#issuecomment-1435369342
print.success "[INFO] Updating apps from Microsoft Store (using winget)"
sudo { Get-CimInstance -Namespace "Root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName UpdateScanMethod }
# Installing Microsoft Store apps
print.success "[INFO] Installing apps from Microsoft Store (using winget)"
wingetInstallFromListFromMsstore $storeAppListID $existingApps
# -------------------------------------------------------------
# -----
# Installing apps with winget
# -----
print.success "[INFO] Installing winget apps using list: wingetAppList_base"
wingetInstallFromListFromWinget $wingetAppList_base $existingApps
print.info
print.success "[INFO] Installing winget apps using list: wingetAppList_poweruser"
wingetInstallFromListFromWinget $wingetAppList_poweruser $existingApps
print.info
# Update all
# Source: https://github.com/microsoft/winget-cli/issues/2854#issuecomment-1386272357
print.success "[INFO] Updating all local packages using winget."
try { winget upgrade --all --include-unknown }
catch { print.err "Error: $_" }
print.info
# -------------------------------------------------------------
# -----
# Installing Sandbox
# -----
print.success "[INFO] Installing and activating Windows Sandbox."
dism /online /enable-feature /featurename:"Containers-DisposableClientVM" /all /norestart
print.info
# -------------------------------------------------------------
# -----
# Installing WSL
# -----
print.success "[INFO] Installing and activating WSL 2."
# Activate WSL
## * https://docs.microsoft.com/fr-fr/windows/wsl/install-win10)
## * https://support.rstudio.com/hc/en-us/articles/360049776974-Using-RStudio-Server-in-Windows-WSL2
# Manual installation of WSL 2.
# Sometimes this does not work with an elevated CMD, it will prompt again a password admin
# Which is a problem sometimes, when using "BeyondTrust" for example
# wsl --install --no-launch
# alternative that works well:
## WSL 2
sudo dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
# Test error
if (!$?) {
print.err "Error: $_"
}
print.info
## restart
## update kernel : https://docs.microsoft.com/fr-fr/windows/wsl/wsl2-kernel
## Install ubuntu from winstore and run it to install
## Source: https://learn.microsoft.com/en-us/windows/wsl/install-manual
print.success "[INFO] Installing wsl 2 with default Linux distribution"
wsl --set-default-version 2
# Test error
if (!$?) {
print.err "Error: $_"
print.info
}
print.info
print.success "[INFO] Downloading and installing Ubuntu 22.04 LTS"
wsl --install --distribution Ubuntu-22.04
# Test error
if (!$?) {
print.warn "[ERROR] A reboot is probably needed. After a reboot, run the following command to install a WSL distro."
print.info "wsl --install --distribution Ubuntu-22.04"
print.err "Error: $_"
print.info
}
print.info
# -------------------------------------------------------------
# -----
# OTHER: Configuring long path, intializing git, installing oh my posh and fonts,
# configuring explorer to show extensions by default
# -----
print.success "[INFO] Configuring long path & intialising git "
# Long path
sudo Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
print.info
# Git init
print.success "[INFO] Setting up git config globaly"
$name = Read-Host "What is your name :"
$email = Read-Host "What is your email :"
sudo git config --global user.name $name
sudo git config --global user.email $email
print.info
# Create the profile file and set the script policy to signed
if (-not (Test-Path -Path $PROFILE)) {
# Create the directory for the profile if it doesn't exist
$profileDir = Split-Path -Path $PROFILE
if (-not (Test-Path -Path $profileDir)) {
New-Item -Path $profileDir -ItemType Directory -Force
}
# Create an empty profile script file
New-Item -Path $PROFILE -ItemType File -Force
Write-Host "Profile file created at $PROFILE"
} else {
Write-Host "Profile file already exists at $PROFILE"
}
# Set the execution policy to RemoteSigned for the current user
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Installing oh-my-posh
# Source: https://ohmyposh.dev/docs/installation/windows
print.success "[INFO] Insatalling Oh-my-posh and setting it up as default Powershell config."
Set-ExecutionPolicy Bypass -Scope Process -Force; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://ohmyposh.dev/install.ps1'))
# Add prompt on powershell startup for current user and admin
Add-Content -Path $PROFILE "`noh-my-posh init pwsh --config ""$env:POSH_THEMES_PATH\jandedobbeleer.omp.json"" | Invoke-Expression"
sudo Add-Content -Path $PROFILE "`noh-my-posh init pwsh --config ""$env:POSH_THEMES_PATH\jandedobbeleer.omp.json"" | Invoke-Expression"
print.info
# Installing fonts using oh-my-posh in a new powershell session (to reload env and use oh-my-posh)
print.success "[INFO] Insatalling fonts globally using Oh-my-posh."
print.info "Installing font 1/4"
start powershell { sudo oh-my-posh font install RobotoMono }
print.info "Installing font 2/4"
start powershell { sudo oh-my-posh font install Hack }
print.info "Installing font 3/4"
start powershell { sudo oh-my-posh font install UbuntuMono }
print.info "Installing font 4/4"
start powershell { sudo oh-my-posh font install Meslo }
# Configuring explorer to show file extensions by default
$key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced'
Set-ItemProperty $key Hidden 1
Set-ItemProperty $key HideFileExt 0
Set-ItemProperty $key ShowSuperHidden 1
Stop-Process -processname explorer
# -------------------------------------------------------------
# stop sudo cache
sudo cache off
print.info
print.success "Script end!"
exit
@LM1LC3N7
Copy link
Author

LM1LC3N7 commented May 28, 2024

Improvements:

  • Add a terminal reload or open in a new tab, to be able to use the newly install sudo module
  • Add Win11Debloat and execute it automatically: https://github.com/Raphire/Win11Debloat
  • Bug when opening a new powershell session, because $PROFILE is a script not allowed by the system (solved)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment