Skip to main content

Winget in Windows 11: Say Goodbye to Outdated Software

Fed up with outdated software? Discover how Winget brings Linux-style package management to Windows 11. Learn how to install, list, and automate app updates using PowerShell scripts and Intune for a professional, 'set and forget' system experience today!
Contents

I have been a fan of Linux since I was young. My favourite branches are these Debian-based. Even though I never officially go full on it, Linux is a part of my daily life, and yours as well.

In daily life, I am using Windows 11, and my main driver is macOS, who derived from Unix. The macOS shares a lot of similarities with Linux, and thanks to the power of the terminal, I can do more than just rely on apps with a GUI (Graphical User Interface).

On the other hand, my other devices all rely on Linux. My router is Linux-based (OpenWrt), I got small Ubuntu server helping me explore stuff and a few Raspberry Pis here and there.

The best feature that I admire in Linux based operating system is the option of managing packages (software) installed in the system. With a few commands using apt (Debian) or opkg (currently in OpenWrt), I can quickly update all that I am currently using in the system.

This is a feature that I miss the most on Windows. To support myself in having always up-to-date apps installed, I used various software. Some are crap, some are doing their job, like UCheck, but none of them are doing everything.

Since Microsoft started to take its Microsoft Store on Windows to their advantage, more and more apps appear there. Most of the apps that are installed directly from their developer website appear there as well, even as a direct link to the installers.

In opposition to what I said, from a Mac user perspective, I am disappointed by how Apple is utilising its AppStore on macOS. Where iPhones rely on AppStore in 99% of the time, with macOS, there is plenty of staff missing, and that’s disappointing.

Thanks to the existence of more and more apps through the Microsoft Store, they can be quickly updated through it without any hustles and that great. However, this does not sort out all the problems.

We still have some other software installed in our system that would need updating, and here the package winget (Windows Package Manager) comes to the rescue.

Forget about expensive software to keep your apps up-to-date and try this solution first.

Spoiler alert. Some packages provided through Winget may be a version or two behind the current latest, but sometimes that’s fine, as it relies more on stability rather than on always the latest, but not always the most edgy version.

WinGet is a command-line tool enabling users to discover, install, upgrade, remove and configure applications on Windows.

It’s so useful, and reminds me use of sudo apt update && sudo apt upgrade in Linux, that I even decided to implement this as an automation on all computers in my workplace.

In a business environment, keeping the operating system up-to-date is not a problem, but keeping apps in the latest version is a different story. As a global administrator in Microsoft 365 infrastructure, help from winget will be a game-changer to keep users updated.

Get WinGet

In the latest version of Windows 11, winget can already be available by default, but it’s worth running Microsoft Store and, by heading to Downloads, checking for any updates and installing them. This may pull and update to an App Installer, which is part of winget that we will be using.

If, for some reason command winget does not exist (in older Windows 11 versions), you need install it. Just head to official Winget CLI GitHub Repository and from Assets section download one with .msixbundle extension (_ Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle_).

Winget in Terminal (Admin)

Let’s right-click on our Start menu and run Terminal (Admin) to try winget and see how powerful it is.

When running WinGet without administrator privileges, in the user Terminal, some applications may require elevation to install. You will be prompted to do so wherever required.

To see what software is installed on our computer, you just need to run the following command:

winget list

Notice that the list may be long, and not all the apps can be upgraded through Winget; most of the software can, and we will concentrate on that.

To quickly see outdated apps that can be updated, just run:

winget upgrade

If you want to update a specific app from the list you generated, just add the app name to the above command, but if you want to run an upgrade on all apps, just use the following:

winget upgrade --all

This is the quickest method to get all upgraded.

If, after running --all, you still have some apps not updated, this may be due to the way you install the app, but even for that, there is a solution.

In that case, uninstalling it (winget uninstall) and installing it back (winget install) will solve the issue for the future, so it can be updated through it.

Winget got multiple commands available, but I will not concentrate on this here. You can explore all Winget commands by learning them from the official Microsoft source.

What I want to concentrate on is to keep apps updated through an automated task (in Task Scheduler), so we can set it and forget about it.

This is very useful, as it can be deployed through the Intune portal in the Microsoft 365 business environment, to all computers running Windows 11. This is something that I will concentrate on below.

For some of you, running manually winget upgrade --all will be something that you may prefer, but for most ordinary users, set and forget approach may be better.

If you are running winget upgrade for the first time, you may be asked to accept source agreements. When you want to run it automatically through the task scheduler, you will also need to amend your system to allow running scripts. We will cover all of this in one command.

Deploy Winget Update Script and Schedule Task

I’ve been working on some sort of script that will deploy on a user’s computer, set everything what needed, schedule a task and execute it on specified time.

I needed to consider a few things before it starts working as intended, but let’s analyse what the script includes step by step.

If you just want the final PowerShell script, head to the Final Script

Step 1. Ensure Execution Policy allows script execution

The script, deployed through Intune in a business environment, or run manually, will require enabling the ability for users to execute scripts. This is not enabled by default and needs to be done by executing the following lines in the PowerShell terminal.

Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser -Force
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope LocalMachine -Force

Step 2: Check if Winget is installed

If your environment does not have winget and you didn’t manage to install it earlier, as described, the script will check that and, when required, will download the latest version and install it.

Step 3: Write the full update script content

In the user profile folder (%userprofile%), the script will write a file that will be later used by Task Scheduler to run and update apps. The file will be named winget_upgrade_all.ps1 and will run in silent mode by default.

When working on this file, I stumbled upon some issues with some apps that do not allow running in silence (in the background). In that case, after they are detected as failed, the task will re-run, and the user will be prompted to follow on-screen instructions.

If all goes well and no further failed app updated been found, the script will terminate, and the task will re-run next time when needed.

Inside that script, I considered accepting any source agreements and package agreements that appear for the first time when the user is running the winget command, as in other cases, it will fail to do anything, especially when running in silence.

Step 4: Create or update scheduled task (Weekly trigger for Mon–Fri)

The final step was to create a task in Task Scheduler to do automatic checks at 13:00 every day from Monday to Friday.

Here I stumbled upon some issues again that I need to consider. If the task was already there, I want to make sure that it will be updated. Even then, I faced some issues, so I need to think that if the update of the task fails, to remove it and re-add it.

Final Script

Finally, I got a script that can either be run manually through the terminal to create required update file and task in Task Scheduler, or deployed through the Intune portal.

In Intune portal, I put this into Devices > Scripts and remediations under Platform Scripts with following options selected:

  • Run this script using the logged-on credentials: Yes
  • Enforce script signature check: No
  • Run script in 64-bit PowerShell Host: Yes

I haven’t used the Remediations option, as not all licences, for all my users, allow that.

# ============================================
# Deploy Winget Update Script and Schedule Task
# ============================================

# Step 1: Ensure Execution Policy allows script execution
Write-Host "Setting Execution Policy to Bypass..."
powershell.exe Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser -Force
powershell.exe Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope LocalMachine -Force
powershell.exe Get-ExecutionPolicy -List

# Define paths
$UserFolder = $env:USERPROFILE
$ScriptPath = Join-Path $UserFolder "winget_upgrade_all.ps1"
$TaskName = "WingetAutoUpgrade"

# Step 2: Check if Winget is installed
if (-not (Get-Command winget.exe -ErrorAction SilentlyContinue)) {
    Write-Host "Winget not found. Downloading App Installer from GitHub..."
    $WingetUrl = "https://github.com/microsoft/winget-cli/releases/latest/download/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
    $WingetPath = "$env:TEMP\AppInstaller.msixbundle"

    try {
        Invoke-WebRequest -Uri $WingetUrl -OutFile $WingetPath -UseBasicParsing
        if (Test-Path $WingetPath) {
            Write-Host "Installing App Installer..."
            Add-AppxPackage -Path $WingetPath
            Write-Host "App Installer installed successfully."
        } else {
            Write-Host "Failed to download App Installer package."
        }
    } catch {
        Write-Host "Error installing App Installer: $($_.Exception.Message)"
    }
} else {
    Write-Host "Winget is already installed."
}

# Step 3: Write the full update script content
$ScriptContent = @'
# ============================================
# Winget Advanced Upgrade Script
# ============================================

$LogPath = "$env:TEMP\WingetAdvancedUpgradeLog.txt"
Write-Host "Starting Winget upgrade process..." -ForegroundColor Cyan
Write-Host "Logging to $LogPath" -ForegroundColor Cyan
Add-Content -Path $LogPath -Value "`n===== Upgrade Run: $(Get-Date) =====`n"

if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
    Write-Host "Winget is not installed or not in PATH." -ForegroundColor Red
    Add-Content -Path $LogPath -Value "Winget not found."
    exit 1
}

Write-Host "Running silent upgrade for all apps..." -ForegroundColor Yellow
$SilentResult = winget upgrade --all --silent --accept-source-agreements --accept-package-agreements
Add-Content -Path $LogPath -Value "`nSilent Upgrade Output:`n$SilentResult"

Write-Host "Checking for failed apps..." -ForegroundColor Yellow
$FailedApps = ($SilentResult | Select-String "Installer failed" | ForEach-Object {
    if ($_ -match '\[(.*?)\]') { $Matches[1] }
}) | Where-Object { $_ -ne "" }

if ($FailedApps.Count -gt 0) {
    Write-Host "Retrying failed apps interactively (no --silent)..." -ForegroundColor Yellow
    foreach ($App in $FailedApps) {
        Write-Host "Retrying $App..." -ForegroundColor Cyan
        Add-Content -Path $LogPath -Value "`nRetrying ${App}:`n"
        winget upgrade $App --accept-source-agreements --accept-package-agreements | Out-File -Append $LogPath
    }
} else {
    Write-Host "No failed apps detected." -ForegroundColor Green
    Add-Content -Path $LogPath -Value "`nNo failed apps detected.`n"
}
'@

# Save the script to user folder
Set-Content -Path $ScriptPath -Value $ScriptContent -Force

# Step 4: Create or update scheduled task (Weekly trigger for Mon–Fri)
$Action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`""
$Trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -At 13:00
$Principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest

if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue) {
    try {
        Write-Host "Scheduled task '$TaskName' already exists. Attempting update..."
        Set-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger
    } catch {
        Write-Host "Update failed. Removing and recreating task..."
        Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
        Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Principal $Principal
    }
} else {
    Write-Host "Creating new scheduled task '$TaskName'..."
    Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $Trigger -Principal $Principal
}

Write-Host "Deployment complete: Script saved to $ScriptPath and scheduled task '$TaskName' created or updated."

And in such a way, I have implemented a better way to keep my and my users’ devices software updated, thanks to winget.

Share on Threads
Share on Bluesky
Share on Linkedin
Share via WhatsApp
Share via Email

Comments & Reactions

Categories