Skip to content

Instantly share code, notes, and snippets.

@w00dbury
Forked from LM1LC3N7/Readme.md
Last active August 29, 2024 18:29
Show Gist options
  • Select an option

  • Save w00dbury/2c962e2783e74e4f93e690572e9b6bec to your computer and use it in GitHub Desktop.

Select an option

Save w00dbury/2c962e2783e74e4f93e690572e9b6bec 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/08/2024
# w00dbury inspired by LM1LC3NT
# 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 = @(
@("nvm-setup.exe", "https://github.com/coreybutler/nvm-windows/releases/download/1.1.12/nvm-setup.exe")
@("go1.23.0.windows-amd64.msi", "https://go.dev/dl/go1.23.0.windows-amd64.msi")
)
### 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
"XP89DCGQ3K6VLD", # PowerToys
"9P7KNL5RWT25", # Sysinternals Suite
"XP9KHM4BK9FZ7Q", # VSCode
"xp99c9g0krdz27", # 1Password
"9n2t6f9f5zdn", # Nightingale REST Client
"9np355qt2sqb", # Azure VPN Client
"xpffh613w8v6lv" # OBS Studio
)
$wingetAppList_base = @(
"7zip.7zip",
"NickeManarin.ScreenToGif"
)
$wingetAppList_poweruser = @(
"ShareX.ShareX",
"Git.Git",
"GitHub.GitHubDesktop",
"Oracle.VirtualBox",
"Hashicorp.Vagrant",
"suse.RancherDesktop",
"1password-cli"
)
$vsCodeExtList = @(
"ms-playwright.playwright",
"salesforce.salesforcedx-vscode-apex",
"GraphQL.vscode-graphql-syntax",
"mongodb.mongodb-vscode",
"GitLab.gitlab-workflow",
"pflannery.vscode-versionlens",
"AmazonWebServices.aws-toolkit-vscode",
"GitHub.vscode-github-actions",
"ms-edgedevtools.vscode-edge-devtools",
"usernamehw.errorlens",
"bradlc.vscode-tailwindcss",
"naumovs.color-highlight",
"mhutchie.git-graph",
"EditorConfig.EditorConfig",
"ms-vscode.PowerShell",
"golang.Go",
"redhat.vscode-yaml",
"formulahendry.auto-rename-tag",
"ms-dotnettools.csharp",
"eamodio.gitlens",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"VisualStudioExptTeam.vscodeintellicode",
"esbenp.prettier-vscode",
"ritwickdey.LiveServer",
"GitHub.copilot",
"dotenv.dotenv-vscode",
"1Password.op-vscode"
)
# -------------------------------------------------------------
# -----
# 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
}
function codeInstallFromList($list){
foreach ($app in $list) {
print.info "Installing $app in VSCode..."
try { code --install-extension $app }
catch { print.err "Error: $_" }
}
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
# -------------------------------------------------------------
# -----
# 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
# -------------------------------------------------------------
# -----
# 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 VS Code extensions
# -----
print.success "[INFO] Installing VS Code extensions using list: vsCodeExtList"
codeInstallFromList $vsCodeExtList
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
# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment