Worklet: Automox OS Upgrade to Latest Windows10 OS version


Userlevel 5
Badge

The following Worklet will ensure all of your devices are always on the most current release of Windows 10. Since the current version is 2004, that is the version this Worklet will upgrade you.

 

This Worklet supports multiple languages:

 

 

DISCLAIMER: THIS WILL AUTOMATICALLY REBOOT THE DEVICE WHEN THE UPGRADE IS COMPLETE WITHOUT USER NOTIFICATION

 

 

Thanks to @aescolastico for creating this!

 

 

Evaluation:

 

 



if ((Test-Path $iso) -eq $true)

{Remove-Item $iso

}



$osversion = (Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')



if (($osversion -lt "2004"))

{exit 1

}

else

{exit 0

}

 

Remediation:

 

 

function Get-Win10ISOLink {

<#

.SYNOPSIS

This function generates a fresh download link for a Windows 10 ISO

.NOTES

Version: 1.6

Author: Andy Escolastico

Creation Date: 10/11/2019

#>

[CmdletBinding()]

param (

[Parameter(Mandatory=$false)]

[ValidateSet("64-bit", "32-bit")]

[String] $Architecture = (Get-WmiObject Win32_OperatingSystem).OSArchitecture,

[Parameter(Mandatory=$false)]

[ValidateSet("fr-dz", "es-ar", "en-au", "nl-be", "fr-be", "es-bo", "bs-ba", "pt-br", "en-ca", "fr-ca", "cs-cz", "es-cl", "es-co", "es-cr", "sr-latn-me", "en-cy", "da-dk", "de-de", "es-ec", "et-ee", "en-eg", "es-sv", "es-es", "fr-fr", "es-gt", "en-gulf", "es-hn", "en-hk", "hr-hr", "en-in", "id-id", "en-ie", "is-is", "it-it", "en-jo", "lv-lv", "en-lb", "lt-lt", "hu-hu", "en-my", "en-mt", "es-mx", "fr-ma", "nl-nl", "en-nz", "es-ni", "en-ng", "nb-no", "de-at", "en-pk", "es-pa", "es-py", "es-pe", "en-ph", "pl-pl", "pt-pt", "es-pr", "es-do", "ro-md", "ro-ro", "en-sa", "de-ch", "en-sg", "sl-si", "sk-sk", "en-za", "sr-latn-rs", "en-lk", "fr-ch", "fi-fi", "sv-se", "fr-tn", "tr-tr", "en-gb", "en-us", "es-uy", "es-ve", "vi-vn", "el-gr", "ru-by", "bg-bg", "ru-kz", "ru-ru", "uk-ua", "he-il", "ar-iq", "ar-sa", "ar-ly", "ar-eg", "ar-gulf", "th-th", "ko-kr", "zh-cn", "zh-tw", "ja-jp", "zh-hk")]

[String] $Locale = (Get-WinSystemLocale).Name,

[Parameter(Mandatory=$false)]

[ValidateSet("Arabic", "Brazilian Portuguese", "Bulgarian", "Chinese (Simplified)", "Chinese (Traditional)", "Croatian", "Czech", "Danish", "Dutch", "English", "English International", "Estonian", "Finnish", "French", "French Canadian", "German", "Greek", "Hebrew", "Hungarian", "Italian", "Japanese", "Korean", "Latvian", "Lithuanian", "Norwegian", "Polish", "Portuguese", "Romanian", "Russian", "Serbian Latin", "Slovak", "Slovenian", "Spanish", "Spanish (Mexico)", "Swedish", "Thai", "Turkish", "Ukrainian")]

[String] $Language = "English",

[Parameter(Mandatory=$false)]

[ValidateSet("1909", "Latest")]

[String] $Version = "Latest"

)



# prefered architecture

if ($Architecture -eq "64-bit"){ $archID = "x64" } else { $archID = "x32" }



# prefered prodID

if ($Version -eq "Latest") {

# grabs latest id

$response = Invoke-WebRequest -UserAgent $userAgent -WebSession $session -Uri "https://www.microsoft.com/$Locale/software-download/windows10ISO" -UseBasicParsing

$prodID = ([regex]::Match((($response).RawContent), 'product-info-content.*option value="(.*)">Windows 10')).captures.groups[1].value

} else{

# uses hard-coded id

$prodID = "1429"

}



# variables you might not want to change (unless msft changes their schema)

$pgeIDs = @("a8f8f489-4c7f-463a-9ca6-5cff94d8d041", "cfa9e580-a81e-4a4b-a846-7b21bf4e2e5b")

$actIDs = @("getskuinformationbyproductedition", "getproductdownloadlinksbysku")

$hstParam = "www.microsoft.com"

$segParam = "software-download"

$sdvParam = "2"

$verID = "Windows10ISO"



# used to spoof a non-windows web request

$userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"



# used to maintain session in subsequent requests

$sessionID = [guid]::NewGuid()



# builds session request url

$uri = "https://www.microsoft.com/" + $Locale + "/api/controls/contentinclude/html"

$uri += "?pageId=" + $pgeIDs[0]

$uri += "&host=" + $hstParam

$uri += "&segments=" + $segParam + "," + $verID

$uri += "&query="

$uri += "&action=" + $actIDs[0]

$uri += "&sessionId=" + $sessionID

$uri += "&productEditionId=" + $prodID

$uri += "&sdvParam=" + $sdvParam



# requests user session

$response = Invoke-WebRequest -UserAgent $userAgent -WebSession $session -Uri $uri -UseBasicParsing



# prefered skuid

if ($Version -eq "Latest") {

# grabs latest id

$skuIDs = (($response.RawContent) -replace "&quot;" -replace '</div><script language=.*' -replace '</select></div>.*' -split '<option value="' -replace '">.*' -replace '{' -replace '}'| Select-String -pattern 'id:') -replace 'id:' -replace 'language:' -replace '\s' | ConvertFrom-String -PropertyNames SkuID, Language -Delimiter ','

$skuID = $skuIDs | Where-Object {$_.Language -eq "$Language"} | Select-Object -ExpandProperty SkuID

}

else{

# uses hard-coded id

$skuID = "9029"

}



# builds link request url

$uri = "https://www.microsoft.com/" + $Locale + "/api/controls/contentinclude/html"

$uri += "?pageId=" + $pgeIDs[1]

$uri += "&host=" + $hstParam

$uri += "&segments=" + $segParam + "," + $verID

$uri += "&query="

$uri += "&action=" + $actIDs[1]

$uri += "&sessionId=" + $sessionID

$uri += "&skuId=" + $skuID

$uri += "&lang=" + $Language

$uri += "&sdvParam=" + $sdvParam



# requests link data

$response = Invoke-WebRequest -UserAgent $userAgent -WebSession $session -Uri $uri -UseBasicParsing



# parses response data

$raw = ($response.Links).href

$clean = $raw.Replace('amp;','')



# stores download link

$dlLink = $clean | Where-Object {$_ -like "*$archID*"}



# outputs download link

Write-Output $dlLink

}



function Start-Win10UpgradeISO {

<#

.SYNOPSIS

Downloads the latest Windows 10 ISO, mounts it, and runs it silently.

.NOTES

Version: 1.1

Author: Andy Escolastico

Creation Date: 02/11/2020



Version 1.0 (2020-02-11)

Version 1.1 (2020-06-03) - Added handling for case where drive letter was not mounted.

Version 1.2 (2020-06-03) - Added ISO download functionality

#>

[CmdletBinding()]

param (

#THIS FLAG DOES NOT WORK FOR THIS FUNCTION

[Parameter(Mandatory=$false)]

[Boolean] $Reboot = $true,

[Parameter(Mandatory=$false)]

[ValidateSet("64-bit", "32-bit")]

[String] $Architecture = (Get-WmiObject Win32_OperatingSystem).OSArchitecture,

[Parameter(Mandatory=$false)]

[String] [String] $DLPath = (Get-Location).Path + "\" +"Win10_" + $Architecture + ".iso",

[Parameter(Mandatory=$false)]

[String] $LogPath = (Get-Location).Path

)



Write-Verbose "Attempting to generate a $Architecture windows 10 iso download link" -Verbose

try {

$DLLink = Get-Win10ISOLink -Architecture $Architecture

}

catch {

throw "Failed to generate windows 10 iso download link."

}



Write-Verbose "Attempting to download windows 10 iso to '$DLPath'" -Verbose

try {

(New-Object System.Net.WebClient).DownloadFile($DLLink, "$DLPath")

}

catch {

throw "Failed to download ISO at path specified."

}



$ISOPath = $DLPath



if (Test-Path $ISOPath) {

$DriveLetter = (Mount-DiskImage -ImagePath $ISOPath | Get-Volume).DriveLetter

} else {

throw "ISO could not be found under $($ISOPath)."

}



Write-Warning "The Upgrade will commence shortly. Your PC will be rebooted soon. Please save any work you do not want to lose."



if ($DriveLetter) {

if ($Reboot -eq $true){

Invoke-Expression "$($DriveLetter):\setup.exe /auto Upgrade /quiet /Compat IgnoreWarning /DynamicUpdate disable /copylogs $LogPath"

} else{

Invoke-Expression "$($DriveLetter):\setup.exe /auto Upgrade /quiet /NoReboot /NoRestartUI /NoRestart /Compat IgnoreWarning /DynamicUpdate disable /copylogs $LogPath"

}

} else {

throw "ISO could not be mounted on this system."

}



}

New-Alias -Name "Start-Win10FeatureUpdate" -Value "Start-Win10UpgradeISO" -ea 0



function Start-Win10UpgradeWUA {

<#

.SYNOPSIS

This function downloads the Windows update assistant tool and runs it silently.

.NOTES

Version: 1.0

Author: Andy Escolastico

Creation Date: 05/10/2020

#>

[CmdletBinding()]

param (

[Parameter(Mandatory=$false)]

[Boolean] $Reboot = $true,

#THIS FLAG DOES NOT WORK FOR THIS FUNCTION

[Parameter(Mandatory=$false)]

[String] $DLPath = (Get-Location).Path,

[Parameter(Mandatory=$false)]

[String] $LogPath = (Get-Location).Path

)

if(!(Test-Path -Path $DLPath)){$null = New-Item -ItemType directory -Path $DLPath -Force}

if(!(Test-Path -Path $LogPath)){$null = New-Item -ItemType directory -Path $LogPath -Force}

$DLLink = "https://go.microsoft.com/fwlink/?LinkID=799445"

$PackagePath = "$DLPath\Win10_WUA.exe"

$LogPath = "$LogPath\Win10_WUA.log"

(New-Object System.Net.WebClient).DownloadFile($DLLink, "$PackagePath")

Write-Host "The Upgrade will commence shortly. Your PC will be rebooted. Please save any work you do not want to lose."

if ($Reboot -eq $true){

Invoke-Expression "$PackagePath /copylogs $LogPath /auto upgrade /dynamicupdate /compat ignorewarning enable /skipeula /quietinstall"

} else{

Invoke-Expression "$PackagePath /NoReboot /NoRestartUI /NoRestart /copylogs $LogPath /auto upgrade /dynamicupdate /compat ignorewarning enable /skipeula /quietinstall"

}

}



function Start-Win10UpgradeCAB{

<#

.SYNOPSIS

This function downloads the feature enablement package cab file and runs it silently using dism.exe.

.NOTES

Version: 1.0

Author: Andy Escolastico

Creation Date: 06/11/2020

#>

[CmdletBinding()]

param (

[Parameter(Mandatory=$false)]

[ValidateSet("1909")]

[String] $Version = "1909",

[Parameter(Mandatory=$false)]

[Boolean] $Reboot = $true,

[Parameter(Mandatory=$false)]

[String] $DLPath = (Get-Location).Path,

[Parameter(Mandatory=$false)]

[String] $LogPath = (Get-Location).Path

)

if(!(Test-Path -Path $DLPath)){$null = New-Item -ItemType directory -Path $DLPath -Force}

if(!(Test-Path -Path $LogPath)){$null = New-Item -ItemType directory -Path $LogPath -Force}

if($Version -eq "1909"){

$DLLink = 'http://b1.download.windowsupdate.com/d/upgr/2019/11/windows10.0-kb4517245-x64_4250e1db7bc9468236c967c2c15f04b755b3d3a9.cab'

}

$PackagePath = "$DLPath\Win10_CAB.cab"

$LogPath = "$LogPath\Win10_CAB.log"

(New-Object System.Net.WebClient).DownloadFile($DLLink, "$PackagePath")

if ($Reboot -eq $true){

Invoke-Expression "DISM.exe /Online /Add-Package /Quiet /PackagePath:$PackagePath /LogPath:$LogPath"

} else{

Invoke-Expression "DISM.exe /Online /Add-Package /Quiet /NoRestart /PackagePath:$PackagePath /LogPath:$LogPath"

}

}



Start-Win10FeatureUpdate -DLPath 'C:\Windows\Temp\Windows10.iso' -LogPath 'C:\Windows\Temp\WindowsUpgradeLogs'

67 replies

Userlevel 4
Badge

sweet , the top check still refers to the 1909 iso? should that not be variable too?

Userlevel 5
Badge

@Maikel The 1909 was for removing the iso after the install. This is in the evaluation so it will remove it after remediation is complete and we auto re-evaulate. This is actually not needed anymore so I removed it.

What if there is an update in between this one and the one that the device is presently on? Will it step up to the one in between and then this one or just go straight to the latest?

This sounds wonderful, just want to make sure I understand it correctly. My patch window is tomorrow, and I’d like to give this a go on a test group.

The last line



[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3, [Net.SecurityProtocolType]::Tls, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls12; (new-object Net.WebClient).DownloadString('https://raw.githubusercontent.com/RFAInc/windows10-iso/master/win10-iso-functions.ps1') | Invoke-Expression; Start-Win10FeatureUpdate -DLPath 'C:\Windows\Temp\Windows10.iso' -LogPath 'C:\Windows\Temp\WindowsUpgradeLogs'



actually downloads the entire script from GitHub and runs the function. Its a cool technique for giving clients scripts that you can update after the fact.


Meaning you could replace the entirety of the remediation block with just that last line. With this setup, you get the latest code updates, but you have less control over what gets deployed. I wouldn’t recommend it. I would advise doing what you guys normally do, where you paste the actual code, and run the function. Right now you’re halfway there. You’re still just downloading the code directly from github and invoking that, instead of running what’s above it. ATM they are the same but that could change if we push new code.


It also means that if a device fails to download the script from github, the whole thing will fail. Despite the fact that you have the code downloaded from Automox servers already.


This is what the last line would look like instead:



Start-Win10FeatureUpdate -DLPath 'C:\Windows\Temp\Windows10.iso' -LogPath 'C:\Windows\Temp\WindowsUpgradeLogs'



Apologies if that explanation isnt very clear.

trying to upgrade win10 1909 and i get the following error:


Mount-DiskImage : The I/O operation has been aborted because of either a thread exit or an application request. At line:150 char:25 + $DriveLetter = (Mount-DiskImage -ImagePath $ISOPath | Get-Vol … + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (MSFT_DiskImage …torageType = 1):ROOT/Microsoft/…/MSFT_DiskImage) [Mou nt-DiskImage], CimException + FullyQualifiedErrorId : HRESULT 0x800703e3,Mount-DiskImage ISO could not be mounted on this system. At line:164 char:9 + throw “ISO could not be mounted on this system.” + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (ISO could not be mounted on this system.:String) [], RuntimeException + FullyQualifiedErrorId : ISO could not be mounted on this system. Error: 0x800703E3, -2147023901 Error Source: FACILITY_WIN32 Error Message: FACILITY_WIN32 is not currently supported by this translator.

Thanks @aescolastico, Nic just updated the last line as you defined in this update!

We had a few other customers with a similar issue, and we were also able to reproduce in our own testing. Our issue turned out to not be an actual issue with mounting the iso properly, but the drive letter was not returned. Here is what fixed it for me:


In the “Start-Win10UpgradeISO” function


Try replacing this:


if (Test-Path $ISOPath) {
$DriveLetter = (Mount-DiskImage -ImagePath $ISOPath | Get-Volume).DriveLetter

With this:


   if (Test-Path $ISOPath) {
$DriveLetter = (Mount-DiskImage -ImagePath $ISOPath | Get-Volume).DriveLetter
$DriveLetter = (Get-DiskImage -ImagePath $ISOPath |Get-Volume).DriveLetter
if (!$DriveLetter){
Mount-DiskImage -ImagePath $ISOPath -StorageType ISO
}

Hey I noticed a silly bug in my original code the other day.

The two lines that have:

if ($Version = “Latest”)


Are not using correct comparison syntax. Instead it’s assigning a value to the variable $Version.


It should be “-eq” instead of “=“. I’ve since update the code on GitHub. This would cause the script to always install the latest and never actually install version 1909. Apologies for not seeing this earlier. I would recommend updating the code in your worklet.

Userlevel 7

Thanks for catching that @aescolastico - I’ve changed those two lines; let me know if they look ok now.

Looks right.


@Nic You guys ever find out if there’s a way to suppress reboots with the new ISOs?

Userlevel 7

Not that I know of. Let me ping @awhitman and see if he’s found a way for that.

I can’t get this to work reliably at all. Ran it on 10 desktops and only 2-3 actually worked. Others just sit at pending and don’t ever do anything. My office has gigabit internet and computers running 7th gen i7s or newer so its not that.


This is a huge part of us investing in Automox and if we can’t get it to work reliably, that’s a big problem.

Hi SteveLord. You may want to open a help request with support for assistance.


If the Worklet is starting, but fails to run properly, as a test try running the scripts locally. Run the evaluation code to verify its return, then if it returns a non-zero, test the remediation code block locally on one of your devices where it is failing.

Test tips: Use an elevated ISE (x86) instance to run your tests. This will mimic the way the PS script will run via Automox. If you use a proxy, try running ISE as system rather than as a user to verify the download is working properly (as Automox agent would be making its internet connection as system rather thank your user).

Yeah I’ve done that before and it worked that day/ate up a bunch of time. Just seems like it isn’t reliable enough to be consistent. Guess I’ll carve time out for that again.


Whole point of me using this system is so I do not have to manually touch each machine and run these over and over. Otherwise I would just run Microsoft’s update assistant or ISO myself. Granted, I am not saying it isn’t part Microsoft’s fault either with their awful update architecture.

The success rate really depends on a lot of unpredictable factors that vary from environment to environment. I’m sure there are plenty of edge cases this script fails to address. But maybe you’ll have better luck with the function that uses the Update Assistant instead of an ISO.

Clone the Worklet and replace the last line in the remediation code with:



Start-Win10UpgradeWUA -DLPath ‘C:\Windows\Temp’ -LogPath ‘C:\Windows\Temp’


@awhitman @d.mccleskey @Nic


Gentlemen, I’m new to the Automox world, and I’m not very familiar with PowerShell, so please forgive me for my ignorance. At the risk of being flamed: I’ve noticed 2 things.




  1. Is the first line of the evaluation code missing on this page? It starts on line 2, and the code for the 1909 Automatic Upgrade is very similar, except the first line. Is it okay to leave it out and copy exactly as shown above?




  2. On the blog post related to this update, step 4 says:

    “Copy and paste the Evaluation code scripts…”




As a newbie, I was thrown by this for a while. I didn’t know for sure what the purpose of the remediation code above was. I also didn’t see a way to let anyone know that it seemed incorrect, so here I am. Based on the 1909 blog post, I think it should actually say:

“Copy and paste the Evaluation and Remediation code scripts…”


Thank you so much for your time and graciousness.


~TB

We are currently using this for updates, but have been running into an issue with the 2004 update and drive mappings. Is there a way to use this same worklet but only get them to the 1909 version until the drive mappings issue is fixed?

Hi @WGraham,

I had a problem with mounting too, and added an alternative mounting snippet above. Have you tried that?


Hi d.mccleskey,

I may have misspoke. The script works just fine and mounts the ISO perfect and does the update. The issue I am running into is when computers that are upgraded to 2004, they lose all network drives after being updated. That is why i was hoping for something similar but upgrades the computer to 1909 instead.

Hi @TBastidas. Thank you for the feedback. I Believe you are correct with item 1. The iso check makes no sense if you don’t have the $iso variable defined. @awhitman


I would suggest removing everything before $osversion in your Evaluation Code. Any change should be handled in the Remediation Code.


For item 2, I believe the text should state “Copy and paste the Evaluation and Remediation Code Scripts” @ValB


@Nic, any chance we could get @TBastidas some swag for the input?

Userlevel 7

Definitely! I’ll send you a link to redeem one of the Automox Yeti mugs @TBastidas.

Userlevel 5
Badge +1

Heads up. The Get-Win10ISOLink function is now pulling a 20H2 link rather the 2004 link

What values need to be changed to specify a different version other than “latest” you want to download?


ie 1903->1909 or 1903->2004 not 20H2

Userlevel 5
Badge +1

Hey Steve. I’ve been looking at that function trying to figure out that very answer. I believe it has something to do with understanding both ProdID and the SKU. When I set the ProdID variable in that function to 1429 (found that ID already in the function) it returned release 1909. What I’m trying to figure out how is how to understand the SKU ID.

@jack.smith The skuID and prodID are both required. Microsoft’s download website references these ids in the client side code. You can see them using web dev tools. They only go back 1 or 2 versions tho. You can get the ids of the older ISOs from the previous worklets. I would imagine all of these values are archived somewhere online by someone.


Keep in mind these are the values for Home/Pro:en-us ISOs.


#1903
$prodID = "1384"
$skuID = "8829"
#1909
$prodID = "1429"
$skuID = "9029"
#2004H1
$prodID = "1626"
$skuID = "9959"

@Steve It should be said, that if you’re using the ISO install method, you don’t have to follow any upgrade path. Any version of windows 10 can upgrade to any newer version. This script would need to be modified in order to handle various version choices. When I wrote it, I only added 1909 and latest.

Reply