2020-05-14

Patching Windows: Part 3

I have a scheduled task that runs every Tuesday after 10 AM Pacific time to check for new updates. Historically, Microsoft posts its monthly patches between 10 AM and 11 AM on the second Tuesday of the month. This script computes if it's Patch Tuesday, or up to a week after Patch Tuesday, and only continues to run during that period of time.

My slow-as-hell-in-checking-for-updates LSTB machine won't find the May Cumulative Update until June, so I fetch and install it myself with 2ndtuesday.ps1:

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [DateTime]  $Date    = (Get-Date),
  [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [String]    $Path    = [System.IO.Path]::Combine(${Env:UserProfile}, 'Desktop'),
  [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [Version[]] $OSBuild = [Environment]::OSVersion.Version,
  [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [String]    $Filter  = 'x64',
  [Parameter(Mandatory=$False)]                            [Switch]    $Force   = $False,
  [Parameter(Mandatory=$False)]                            [Switch]    $Fetch   = $True
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 2

Import-Module -Force -Name ([System.IO.Path]::Combine($PSScriptRoot, 'WU.psm1'))

Function Do-Start {
  Param()
  $tuesday = Get-SecondTuesday -DT $Date

  if (-not $Force) {
    if ( 8 -gt $Date.Day)              { Return $False }
    if (15 -lt $Date.Day)              { Return $False }
    if ($Date.Date -lt $tuesday.Date)  { Return $False }
    if ($Date -ge $tuesday.AddDays(7)) { Return $False }
  }

  Write-Host ("Date: {0}" -f ($Date.ToLongDateString()))
  Write-Host ("Second Tuesday of month: {0}`n" -f ($tuesday.ToLongDateString()))
  Return $True
}

Function Do-End {
  Param( [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [PSObject[]] $Updates )

  Write-Host "`nSSU updates: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001`n"

  foreach ($build in $OSBuild) {
    $build_str = Get-BuildString -Version $build
    $kb_obj    = $Updates | Where-Object { $_.OSBuild -match $build_str }
    if ([String]::IsNullOrEmpty($kb_obj)) {
      Write-Error 'KB not found'
    }

    $release_date = Get-Date $kb_obj.ReleaseDate
    $source       = Get-KBSource -Filter $Filter -KB $kb_obj.KB
    $uri          = New-Object -TypeName System.Uri -ArgumentList $source.Source
    $kb_name      = "{0:0000}-{1:00} Cumulative Update for Windows 10 Version {2} for {3}-based Systems ({4})" -f ($release_date.Year, $release_date.Month, $kb_obj.OSName, $Filter, $kb_obj.KB)

    # <URL:https://arstechnica.com/civis/viewtopic.php?t=1330043>
    $dst_path  = '\\?\' + [System.IO.Path]::Combine($Path, $kb_name)
    $file_name = $uri.Segments[-1]
    $tmp_path  = [System.IO.Path]::Combine(([System.IO.Path]::GetTempPath()), $file_name)

    mkdir $dst_path -ErrorAction SilentlyContinue | Out-Null

    if ($Fetch) {
      Start-BitsTransfer -Destination $tmp_path -Source $uri
      Move-Item -Verbose:$False -Force:$True -Path $tmp_path -Destination $dst_path
      Write-Host "Fetched:`n"
    }

    Write-Host ("OS Build: {0}`n" -f ($build))
    Write-Host ("KB Name: `"{0}`"`n" -f ($kb_name))
    Write-Host ("Uri: {0}" -f ($uri))
  }

  Read-Host -Prompt "`nok?"
  Return
}

$count               = 0
$global:LASTEXITCODE = 0
if (Do-Start) {
  do {
    $count++
    try {
      $updates = Get-PublishedKBUpdates
      $updates | Format-Table -Property ReleaseDate,KB,OSBuild,OSName,SupportID,ReleaseWeek -AutoSize
      Do-End -Updates $updates
      Write-Host ("run: {0}, exitcode: {1}" -f ($count, $global:LASTEXITCODE))
    } catch { Start-Sleep -Seconds 300 }
  } while (0 -ne $global:LASTEXITCODE)
}
# Start-Sleep -Seconds 10 # I like to read things before the window closes
Exit

# END

I don't mind fetching the .MSU file myself because I like to keep it around for my "Updates" folder, which I sometimes feed to a different script that builds a from-scratch Windows VM with an install ISO and can patch the base image for me with dism.exe.

Output looks a little like this:

PS C:\> .\2ndtuesday.ps1 -Fetch:$False
Date: Tuesday, May 12, 2020
Second Tuesday of month: Tuesday, May 12, 2020


ReleaseDate KB        OSBuild          OSName SupportId ReleaseWeek
----------- --        -------          ------ --------- -----------
2020-05-12  KB4556799 10.0.18363.836   1909   4529964             B
2020-05-12  KB4556799 10.0.18362.836   1903   4498140             B
2020-05-12  KB4551853 10.0.17763.1217  1809   4464619             B
2020-05-12  KB4556807 10.0.17134.1488  1803   4099479             B
2020-05-12  KB4556812 10.0.16299.1868  1709   4043454             B
2020-05-12  KB4556804 10.0.15063.2375  1703   4018124             B
2020-05-12  KB4556813 10.0.14393.3686  1607   4000825             B
2018-04-10  KB4093109 10.0.10586.1540  1511   4000824             B
2020-05-12  KB4556826 10.0.10240.18575 1507   4000823             B



SSU updates: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/ADV990001

OS Build: 10.0.17763.0

KB Name: "2020-05 Cumulative Update for Windows 10 Version 1809 for x64-based Systems (KB4551853)"

Uri: http://download.windowsupdate.com/c/msdownload/update/software/secu/2020/05/windows10.0-kb4551853x64_ce1ea7def481ee2eb8bba6db49ddb42e45cba54f.msu

ok?:

run: 1, exitcode: 0

The URL for self-servicing updates is there because Microsoft doesn't make those easily accessible in a way DeploySharedLibrary can find. So far as I know you either (a) walk the Microsoft Update Catalog for a known SSU update for your precise build and look if it's been obsoleted, or (b) open a browser and go to the portal.msrc.microsoft.com link and click the link that's right for you. (b) is quick and you don't have to do it every single month. Every other month is fine. (Don't like it? Don't blame me. I voted for Kodos.)

This script puts a new directory on your Desktop called "YYYY-MM Cumulative Update for Windows 10 Version VVVV for FILTER-based Systems (KBXXXXXX)" and will store the update there. Change your architecture with -Filter as needed... but if you do you need to ask yourself why you're still on x86 or trying to act cool with ARM. I honestly have never tested this because I have one x86 machine and it's a tablet that I use as an e-reader and I leave it in airplane mode constantly.

No comments: