Install Windows 10 Upgrade 2004/20H2 from command line / using a PowerShell Script

Usually we deploy Windows 10 feature and cummulative updates using a deployment solution like Microsoft Endpoint Configuration Manager or Ivanti DSM or LANDesk or lately using the new Infrastructure Manager which is only available in beta by request. And then, sometimes we need to update notebooks which are not ‘well-connected’. First we used to ship/send a USB stick with a current Windows 10 ISO on it, plus a PowerShell script, plus a batch file etc. so the user had to login with his admin account, start the batch file, the batch started to PowerShell (hello to PowerShell execution-policy) script and the script started the Windows 10 Setup using some smart arguments to make sure the user does/did not have to answer any questions. But also we created a log file, just in case the update didn’t succeed – which happend/happens MANY times because of compatibility issue.. anyway.

No we added a new procedure, especially for our small and medium business clients. If the customer has Windows 10 Pro installed, this procedure is quite simple. Just send this PowerShell script, execute it -> dont’ forget about the execution policy..

# Solvia GmbH, Christian Casutt
# Update Windows using Windows Update Assistant
# Link https://go.microsoft.com/fwlink/?LinkID=799445 -> delivered the latest WUA Version as of today, May 28th of 2020 -> 2004!
# Script Version 1.0 - 2020-05-28

function Write-Log { 
    [CmdletBinding()] 
    param ( 
        [Parameter(Mandatory)] 
        [string]$Message
    ) 
     
    try { 
        if (!(Test-Path -path ([System.IO.Path]::GetDirectoryName($LogFilePath)))) {
            New-Item -ItemType Directory -Path ([System.IO.Path]::GetDirectoryName($LogFilePath))
        }
        $DateTime = Get-Date -Format ‘yyyy-MM-dd HH:mm:ss’ 
        Add-Content -Value "$DateTime - $Message" -Path $LogFilePath 
    } 
    catch { 
        Write-Error $_.Exception.Message 
    } 
}
Function CheckIfElevated() {
    Write-Log "Info: Checking for elevated permissions..."
    if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
                [Security.Principal.WindowsBuiltInRole] "Administrator")) {
        Write-Log "ERROR: Insufficient permissions to run this script. Open the PowerShell console as an administrator and run this script again."
        return $false
    }
    else {
        Write-Log "Info: Code is running as administrator — go on executing the script..."
        return $true
    }
}

# Main

try {
    # Declarations
    [string]$DownloadDir = 'C:\Solvia\Windows_FU\packages'
    [string]$LogDir = 'C:\Solvia\Logs'
    [string]$LogFilePath = [string]::Format("{0}\{1}_{2}.log", $LogDir, "$(get-date -format `"yyyyMMdd_hhmmsstt`")", $MyInvocation.MyCommand.Name.Replace(".ps1", ""))
    [string]$Url = 'https://go.microsoft.com/fwlink/?LinkID=799445'
    [string]$UpdaterBinary = "$($DownloadDir)\Win10Upgrade.exe"
    [string]$UpdaterArguments = '/quietinstall /skipeula /auto upgrade /copylogs $LogDir'
    [System.Net.WebClient]$webClient = New-Object System.Net.WebClient

    # Here the music starts playing .. 
    Write-Log -Message ([string]::Format("Info: Script init - User: {0} Machine {1}", $env:USERNAME, $env:COMPUTERNAME))
    Write-Log -Message ([string]::Format("Current Windows Version: {0}", [System.Environment]::OSVersion.ToString()))
    
    # Check if script is running as admin and elevated  
    if (!(CheckIfElevated)) {
        Write-Log -Message "ERROR: Will terminate!"
        break
    }

    # Check if folders exis
    if (!(Test-Path $DownloadDir)) {
        New-Item -ItemType Directory -Path $DownloadDir
    }
    if (!(Test-Path $LogDir)) {
        New-Item -ItemType Directory -Path $LogDir
    }
    if (Test-Path $UpdaterBinary) {
        Remove-Item -Path $UpdaterBinary -Force
    }
    # Download the Windows Update Assistant
    Write-Log -Message "Will try to download Windows Update Assistant.."
    $webClient.DownloadFile($Url, $UpdaterBinary)

    # If the Update Assistant exists -> create a process with argument to initialize the update process
    if (Test-Path $UpdaterBinary) {
        Start-Process -FilePath $UpdaterBinary -ArgumentList $UpdaterArguments -Wait
        Write-Log "Fired and forgotten?"
    }
    else {
        Write-Log -Message ([string]::Format("ERROR: File {0} does not exist!", $UpdaterBinary))
    }
}
catch {
    Write-Log -Message $_.Exception.Message 
    Write-Error $_.Exception.Message 
}

Update, January 7th 2021

User Patrice Martin asked if it’s possible to suppress the reboot. Sure it is. Just add /NoReboot /NoRestartUI /NoRestart to the arguments. Basically only /NoRestart is needed, /NoReboot /NoRestartUI is beeing used to suppress the UI and don’t give the user the choice to reboot. Anyway, didn’t try the swiches separately but together they worked like a charm.

Another Update, January 8th 2021

unlike the setup.exe that comes with the Windows ISO, the windows update assistant ignores the reboot suppression parameters. So the only viable solution is to kill the task/process?!

Feel free to comment.