# Windows Update module $VERSION = 0.0 # There is no manifest # This is version 20220513 of the module. Like any other piece of # software (and information generally), this module comes with NO WARRANTY. # Use with: # Import-Module -Force:$True -Name:"C:\Path\to\WU.psm1" # Better than: # New-Variable -Option:ReadOnly -Name:ascii -Value:(New-Object System.Text.ASCIIEncoding) New-Variable -Option:ReadOnly -Name:date_format_str -Value:'MMMM d, yyyy' New-Variable -Option:ReadOnly -Name:ms_catalog_url -Value:'https://www.catalog.update.microsoft.com' New-Variable -Option:ReadOnly -Name:os_build_str -Value:'(OS Build' # New-Variable -Option:ReadOnly -Name:RSSInfo -Value:@{ '10' = @{ 'Feed' = 'https://support.microsoft.com/en-us/feed/rss/6ae59d69-36fc-8e4d-23dd-631d98bf74a9'; } '11' = @{ 'Feed' = 'https://support.microsoft.com/en-us/feed/rss/4ec863cc-2ecd-e187-6cb3-b50c6545db92'; } } New-Variable -Option:ReadOnly -Name:WindowsOSBuildMap -Value:@{ # '22000' = @{ 'Id' = '5006099'; 'Name' = '21H2'; EndOfLife = '2023-10-10'; } # '19044' = @{ 'Id' = '5008339'; 'Name' = '21H2'; EndOfLife = '2023-06-13'; } '19043' = @{ 'Id' = '5003498'; 'Name' = '21H1'; EndOfLife = '2022-12-13'; } '19042' = @{ 'Id' = '4581839'; 'Name' = '20H2'; EndOfLife = '2023-05-09'; } '19041' = @{ 'Id' = '4557957'; 'Name' = '2004'; EndOfLife = '2021-12-14'; } '18363' = @{ 'Id' = '4529964'; 'Name' = '1909'; EndOfLife = '2022-05-10'; } '18362' = @{ 'Id' = '4498140'; 'Name' = '1903'; EndOfLife = '2020-12-08'; } '17763' = @{ 'Id' = '4464619'; 'Name' = '1809'; EndOfLife = '2029-01-09'; } '17134' = @{ 'Id' = '4099479'; 'Name' = '1803'; EndOfLife = '2021-05-11'; } '16299' = @{ 'Id' = '4043454'; 'Name' = '1709'; EndOfLife = '2020-10-13'; } '15063' = @{ 'Id' = '4018124'; 'Name' = '1703'; EndOfLife = '2019-10-08'; } '14393' = @{ 'Id' = '4000825'; 'Name' = '1607'; EndOfLife = '2026-10-13'; } '10586' = @{ 'Id' = '4000824'; 'Name' = '1511'; EndOfLife = '2018-04-10'; } '10240' = @{ 'Id' = '4000823'; 'Name' = '1507'; EndOfLife = '2025-10-14'; } } Function Get-CurrentKBUpdateList { Param( [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [String] $WindowsVersion = '10', [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [UInt16] $CacheTime = 3600 ) $out = @() $updates = @() $now = Get-Date $t = Get-SecondTuesday $fetch_flag = $True $request_properties = @{ 'Uri' = $RSSInfo[$WindowsVersion].Feed; 'OutFile' = [System.IO.Path]::Combine(${Env:UserProfile}, 'Desktop', ("win{0}_rss.xml" -f ($WindowsVersion))); }; if ($now -lt $t) { $t = $now.AddMonths(-1) $t = Get-SecondTuesday -Date:([System.DateTime]::new($t.Year,$t.Month,1)) } if (Test-Path -Path:$request_properties.OutFile) { $ts = $now.ToUniversalTime() - (Get-Item -Path:$request_properties.OutFile).LastWriteTimeUtc if ($CacheTime -ge $ts.TotalSeconds) { $fetch_flag = $False } } if ($fetch_flag) { Invoke-WebRequest @request_properties } [XML] $xml = Get-Content -Path:$request_properties.OutFile $xml.rss.channel.item | foreach { $date_pub = Get-Date $_.pubDate if ($date_pub.Date -ge $t.Date) { if ($_.title.StartsWith($t.ToString($date_format_str))) { $updates += New-Object -TypeName:PSObject -Property:@{ 'PublicationDate' = $date_pub.Date; 'Title' = $_.title; } } } } foreach ($update in $updates) { $line = $update.Title # parse date $d = $t.ToString($date_format_str) $ch1 = $line.IndexOf($d) if ((0 -gt $ch1) -or (1 -lt $ch1)) { Write-Error 'parse error' } # parse KB $ch1 = $line.IndexOf('KB') if (1 -gt $ch1) { Write-Error 'parse error' } $ch2 = $line.IndexOf(' ', $ch1) if ($ch1 -gt $ch2) { Write-Error 'parse error' } $kb = $line.Substring($ch1, ($ch2 - $ch1)) # parse "(OS Build" string $ch1 = $line.IndexOf($os_build_str) if (1 -gt $ch1) { Write-Error 'parse error' } $ch2 = $os_build_str.Length + $ch1 $ch1 = $line.IndexOf(' ', $ch2) if ($ch1 -lt $ch2) { Write-Error 'parse error' } $builds = $line.Substring(1 + $ch1) # parse version strings $version = '' for ($i = 0; $i -lt $builds.Length; $i++) { $ch1 = $ascii.GetBytes($builds[$i])[0] if ((46 -eq $ch1) -or ((47 -lt $ch1) -and (58 -gt $ch1))) { $version += $builds[$i] } else { if (-not ([String]::IsNullOrEmpty($version))) { $ch1 = $version.IndexOf('.') if (1 -gt $ch1) { Write-Error 'parse error' } $build = $version.Substring(0, $ch1) $out += New-Object -TypeName:PSObject -Property:@{ 'PublicationDate' = $update.PublicationDate.ToString('yyyy-MM-dd'); 'KB' = $kb; 'OSBuild' = $version; 'OSName' = $WindowsOSBuildMap[$build].Name; 'SupportId' = $WindowsOSBuildMap[$build].Id; 'EndOfLife' = $WindowsOSBuildMap[$build].EndOfLife; } $version = '' } } } } Return $out } Function Get-KBSource { Param( [Parameter(Mandatory=$True)] [ValidateNotNullOrEmpty()] [String] $KB, [Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [String] $Filter = 'x64' ) $uri_search = "{0}/Search.aspx?q={1}" -f ($ms_catalog_url, $KB) $uri_download = "{0}/DownloadDialog.aspx" -f ($ms_catalog_url) $wr = Invoke-WebRequest -Uri:$uri_search Set-StrictMode -Version:1 # some of these things won't exist $ids = $wr.InputFields | Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } | Select-Object -ExpandProperty:ID $guids = $wr.Links | Where-Object ID -match '_link' | Where-Object { $_.OuterHTML -match $Filter } | ForEach-Object { $_.id.replace('_link','') } | Where-Object { $_ -in $ids } Set-StrictMode -Version:2 $links = @() foreach ( $guid in $guids ) { $post_json = @{ size = 0; updateID = $guid; uidInfo = $guid } | ConvertTo-Json -Compress $request_properties = @{ 'Uri' = $uri_download; 'Method' = 'POST'; 'Body' = @{ updateIDs = '[' + $post_json + ']' }; } $links += Invoke-WebRequest @request_properties | Select-Object -ExpandProperty:Content | Select-String -AllMatches -Pattern:"(http[s]?\://catalog\.s\.download\.windowsupdate\.com\/[^\'\""]*)" | ForEach-Object { $_.matches.value } } Return ($links | Select-Object -Unique | ForEach-Object { [PSCustomObject] @{ Source = $_ } }) } Function Get-SecondTuesday { Param([Parameter(Mandatory=$False)] [ValidateNotNullOrEmpty()] [DateTime] $DateTime = (Get-Date)) $tz_redmond = [System.TimeZoneInfo]::FindSystemTimeZoneById('Pacific Standard Time') $d = New-Object -TypeName:System.DateTimeOffset $DateTime.Year,$DateTime.Month,8,10,0,0,0,$tz_redmond.BaseUtcOffset $num = [Byte] $d.DayOfWeek # 0 = Sun, 1 = Mon, 2 = Tue, etc. $delta = (9 - $num) % 7 # 9 = 7 + 2 (2 = Tue) $d = $d.AddDays($delta) Return $d.DateTime } Export-ModuleMember -Function Get-CurrentKBUpdateList Export-ModuleMember -Function Get-KBSource Export-ModuleMember -Function Get-SecondTuesday # END