Death by a thousand updates
I don’t know if you noticed, but Microsoft broke Windows Update back in April. I didn’t, at least not until it was October.
Luckily by then the Internetz had found a solution. Not that it is an easy one, or an elegant one.
It involves downloading 3 specific MS hotfixes. If you look around the net you will find a different set of hotfixes required, depending on when the posts where written.
It seems this Windows Update thing broke, got patched, broke again, got patched again and then broke again between April and September, so here is the set of hotfixes that works as of December 2016. It might change in a few months.
Also, it seems this is not your final solution if you want working Bluetooth on an Intel chipset.
Windows 7
But, it is EOL you will say. It is not available through OEMs you will say. To this I answer: Industry is sluggish, embedded development more so, regulated software development even more so. So we get to enjoy Win7 for a while longer.
Also, anything after Win7 sucks big time as an embedded software development environment.
The 100% CPU bug afflicts Win10 as well, but for my scenarios it suffices to use the anniversary edition to work around it.
The solution
You need
- KB3020369: April 2015 rollup update. So that the convenience rollup update can install.
- KB3125574: the April 2016 convenience rollup, which is what MS calls whatever should have been Win7 SP2, but cannot be called a service pack since it came out way after MS said it will not support Win7 anymore.
and finally
- KB3172605 which is the actual fix for the “My CPU is pegged to 100% load for days while looking for windows updates” problem.
After that you can attempt to re-enable Windows Update.
Automation Hell
I am a proponent of vanila OS installations, especially for Windows and I have regular automated builds to produce fresh Vagrant baseboxes with Packer.
Getting the procedure described here with Packer involved a lot of trial and error and a lot of time.
Things I learned:
- You cannot install offline updates with the windows-shell provisioner since it uses WinRM and on Windows7 the update agent (wusa.exe) does not have to correct access rights.
This meant that the update installers had to be executed locally within the VM.
First approach
Copy the installer in the VM (using the ‘file’ provisioner) and use ‘shell-local’.
To get ‘shell-local’ to work you have to provide the shell executable (as it uses /bin/sh per default)
{
"type": "shell-local",
"command": "wusa.exe MyUPDATE.msu /quiet /norestart",
"execute_command":["cmd.exe","/C","{\{.Command\}}"]
}
Unfortunately .msu installers can exit with a variety of non-zero exit codes, 3010 being the one that indicates a reboot is needed. This messes up things.
Also, copying the large convenience rollup update with the ‘file’ provisioner failed consistently for some reason (it’s a big 500MB file - the rest all copied over with no issues), resulting in a 0 bytes entry and no error on the packer side.
So this didn’t work.
Second approach
It was more like the fifth try to be honest. Lots of dead ends.
The one that worked:
- Download the installer from within the VM.
- Create a schedule task to install it on logon.
- Reboot.
- Go into a loop till the hotfix shows up in the list of installed updates (or you timeout).
- Rinse and repeat.
Kludgy, complicated and slow, but it works reliably. The script I used to detect the hotfix is
#it looks for HOTFIX_ID (KBxxxxx) in the list of installed hotfixes
#and goes in a loop until it shows up or the counter runs out
$id=$env:HOTFIX_ID
$max_tries=[Int32]$env:HOTFIX_DETECT_TRIES
if ($max_tries -eq 0){
$max_tries=90
}
$counter=0
while($counter -lt $max_tries) {
if (get-hotfix -id $id -EA SilentlyContinue){
break
}
else{
$counter+=1
Start-Sleep 5
}
}
if ($counter -eq $max_tries)
{
echo "Timed out waiting for $id - tried $counter times"
exit 1
}
and this is how it’s called from Packer:
{
"type": "powershell",
"environment_vars": ["HOTFIX_ID=KB3125574","HOTFIX_DETECT_TRIES=240"],
"script": "script/detect-hotfix-install.ps1"
}
Groundhog Day: Chef
Like I said, I like vanila OS installs and deal with VMs and bare metal installations. Also with some old installations that have been working non-stop for years. And since they are not internet facing, nobody really bothered to update them. Well, everyone who tried recently gave up and went home crying.
So I can’t rely on a recently patched version or a freshly produced basebox all the time.
Which means I needed to re-implement the above as a Chef recipe:
msu_installers=node.fetch("msu_installers",{})
unless msu_installers.empty?
reboot 'Restart Computer' do
action :nothing
end
msu_sequence=node.fetch("msu_sequence",[])
msu_sequence.each do |msu_name|
pkg=msu_installers[msu_name]
if pkg["source"]
windev_cache_package pkg["save_as"] do
source pkg["source"]
depot node['software_depot']
end
installer= ::File.join(node["software_depot"],pkg['save_as'])
ruby_block "installer_exists" do
block do
raise "Installer #{File.expand_path(installer)} not found" unless File.exist?(File.expand_path(installer))
end
action :run
end
powershell_script "Install #{pkg["name"]}" do
code "wusa.exe #{pkg["save_as"]} /quiet /norestart"
action :run
not_if "get-hotfix -id #{pkg["name"]} -EA SilentlyContinue"
notifies :reboot_now, 'reboot[Restart Computer]', :immediately
end
end
end
end
The JSON with the attributes looks like:
"msu_installers":{
"KB3020369":{"source":"URL",
"save_as":"Windows6.1-KB3020369-x64.msu",
"name":"KB3020369"
},
"KB3125574":{"source":"URL",
"save_as":"windows6.1-kb3125574-foo.msu",
"name":"KB3125574"
},
"KB3172605":{"source":"URL",
"save_as":"Windows6.1-KB3172605-x64.msu",
"name":"KB3172605"
}
},
"msu_sequence":["KB3020369","KB3125574","KB3172605"]
To use this you will need the windev cookbook and you have to also use the auto_chef recipe to handle reboots (if you use Chef without a Chef Server, like I do).