From 44a403296d31cbda1444c715e4df43d035a9ba77 Mon Sep 17 00:00:00 2001 From: That-Annoying-Guy Date: Mon, 12 Sep 2022 16:05:16 -0400 Subject: [PATCH 1/2] handle .msu and .cab files (for MSEdge Updates) also simplify code by setting defaults in the parameters instead of code. --- MSCatalog/Private/Invoke-DownloadFile.ps1 | 23 ++++++++++--- MSCatalog/Public/Get-MSCatalogUpdate.ps1 | 41 +++++++++++++---------- MSCatalog/Public/Save-MSCatalogUpdate.ps1 | 18 ++++++---- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/MSCatalog/Private/Invoke-DownloadFile.ps1 b/MSCatalog/Private/Invoke-DownloadFile.ps1 index 9b9a41a..2ba6658 100644 --- a/MSCatalog/Private/Invoke-DownloadFile.ps1 +++ b/MSCatalog/Private/Invoke-DownloadFile.ps1 @@ -1,5 +1,14 @@ function Invoke-DownloadFile { - [CmdLetBinding()] +<# +.SYNOPSIS + Downloads a file as per URI and check its SHA1 hash embedded in $Path. + With v0.28.2, now able to handle .msu and .cab files (for MSEdge Updates) +.NOTES + Private Function +.LINK + https://github.com/ryan-jan/MSCatalog +#> + [CmdLetBinding()] param ( [uri] $Uri, [string] $Path, @@ -7,12 +16,15 @@ function Invoke-DownloadFile { ) try { + [String]$FileExt = [System.IO.Path]::GetExtension($Path) #extract extension + # Check to see if file is already downloaded and the hash matches, if it does, we do not need to re-download if (Test-Path $Path) { $Hash = Get-FileHash -Path $Path -Algorithm SHA1 - if ($Path -match "$($Hash.Hash)\.msu$") { + #now able to handle .msu and .cab files + if ( (($Path -match "$($Hash.Hash)\.msu$") -and ($FileExt -eq '.msu')) -or (($Path -match "$($Hash.Hash)\.cab$") -and ($FileExt -eq '.cab')) ) { return - } + } } Set-TempSecurityProtocol @@ -26,8 +38,9 @@ function Invoke-DownloadFile { } $Hash = Get-FileHash -Path $Path -Algorithm SHA1 - if ($Path -notmatch "$($Hash.Hash)\.msu$") { - throw "The hash of the downloaded file does not match the expected value." + #now able to handle .msu and .cab files + if ( (($Path -notmatch "$($Hash.Hash)\.msu$") -and ($FileExt -eq '.msu')) -or (($Path -notmatch "$($Hash.Hash)\.cab$") -and ($FileExt -eq '.cab')) ) { + throw "The hash of the downloaded file [$Path] does not match the expected value [$($Hash.Hash)]." } Set-TempSecurityProtocol -ResetToDefault diff --git a/MSCatalog/Public/Get-MSCatalogUpdate.ps1 b/MSCatalog/Public/Get-MSCatalogUpdate.ps1 index 09b4264..cb8a4c0 100644 --- a/MSCatalog/Public/Get-MSCatalogUpdate.ps1 +++ b/MSCatalog/Public/Get-MSCatalogUpdate.ps1 @@ -46,6 +46,12 @@ function Get-MSCatalogUpdate { .EXAMPLE Get-MSCatalogUpdate -Search "Cumulative for Windows Server, version 1903" -AllPages + + .EXAMPLE + Get-MSCatalogUpdate -Search "Microsoft Edge-Stable Channel x64" + + .LINK + https://github.com/ryan-jan/MSCatalog #> [CmdLetBinding()] @@ -53,12 +59,13 @@ function Get-MSCatalogUpdate { [Parameter(Mandatory = $true)] [string] $Search, - [Parameter(Mandatory = $false)] + + [Parameter(Mandatory = $false, HelpMessage="Default is sort by LastUpdated")] [ValidateSet("Title", "Products", "Classification", "LastUpdated", "Size")] - [string] $SortBy, + [string] $SortBy = "LastUpdated", - [Parameter(Mandatory = $false)] - [switch] $Descending, + [Parameter(Mandatory = $false, HelpMessage="Default is descending order.")] + [switch] $Descending = $true, [Parameter(Mandatory = $false)] [switch] $Strict, @@ -80,18 +87,18 @@ function Get-MSCatalogUpdate { $Uri = "https://www.catalog.update.microsoft.com/Search.aspx?q=$([uri]::EscapeDataString($Search))" $Res = Invoke-CatalogRequest -Uri $Uri - if ($PSBoundParameters.ContainsKey("SortBy")) { - $SortParams = @{ - Uri = $Uri - SortBy = $SortBy - Descending = $Descending - EventArgument = $Res.EventArgument - EventValidation = $Res.EventValidation - ViewState = $Res.ViewState - ViewStateGenerator = $Res.ViewStateGenerator - } - $Res = Sort-CatalogResults @SortParams - } else { +# if ($PSBoundParameters.ContainsKey("SortBy")) { +# $SortParams = @{ +# Uri = $Uri +# SortBy = $SortBy +# Descending = $Descending +# EventArgument = $Res.EventArgument +# EventValidation = $Res.EventValidation +# ViewState = $Res.ViewState +# ViewStateGenerator = $Res.ViewStateGenerator +# } +# $Res = Sort-CatalogResults @SortParams +# } else { # Default sort is by LastUpdated and in descending order. $SortParams = @{ Uri = $Uri @@ -103,7 +110,7 @@ function Get-MSCatalogUpdate { ViewStateGenerator = $Res.ViewStateGenerator } $Res = Sort-CatalogResults @SortParams - } +# } $Rows = $Res.Rows diff --git a/MSCatalog/Public/Save-MSCatalogUpdate.ps1 b/MSCatalog/Public/Save-MSCatalogUpdate.ps1 index fb92a81..3574476 100644 --- a/MSCatalog/Public/Save-MSCatalogUpdate.ps1 +++ b/MSCatalog/Public/Save-MSCatalogUpdate.ps1 @@ -1,7 +1,7 @@ function Save-MSCatalogUpdate { <# .SYNOPSIS - Download an update file from catalog.update.micrsosoft.com. + Download an update file from catalog.update.microsoft.com. .PARAMETER Update Specify the update to be downloaded. @@ -40,8 +40,11 @@ function Save-MSCatalogUpdate { .EXAMPLE $Update = Get-MSCatalogUpdate -Search "KB4515384" Save-MSCatalogUpdate -Update $Update -Destination C:\Windows\Temp\ -UseBits + + .LINK + https://github.com/ryan-jan/MSCatalog #> - + [CmdLetBinding()] param ( [Parameter( Mandatory = $true, @@ -66,9 +69,10 @@ function Save-MSCatalogUpdate { [Parameter( Mandatory = $false, Position = 1, - ParameterSetName = "ByGuid" + ParameterSetName = "ByGuid", + HelpMessage="Defaults to %temp%" )] - [String] $Destination, + [String] $Destination = $env:TEMP, [Parameter( Mandatory = $false, @@ -98,9 +102,9 @@ function Save-MSCatalogUpdate { [switch] $AcceptMultiFileUpdates ) - if (-not $Destination) { - $Destination = $env:TEMP - } +# if (-not $Destination) { +# $Destination = $env:TEMP +# } if ($Update) { $Guid = $Update.Guid From d092706fba83892e14ad132c9d7982d2acd45dab Mon Sep 17 00:00:00 2001 From: That-Annoying-Guy Date: Fri, 16 Sep 2022 15:14:48 -0400 Subject: [PATCH 2/2] New Get-MSOfficeBuildNumber function Tries to retrieve a *VALID* Office 365 build number so that OfficeC2RClient.exe can be used to force the existing Office 365 installation to that specified BuildNumber. --- MSCatalog/MSCatalog.psd1 | 3 +- MSCatalog/Public/Get-MSCatalogUpdate.ps1 | 4 +- MSCatalog/Public/Get-MSOfficeBuildNumber.ps1 | 172 +++++++++++++++++++ 3 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 MSCatalog/Public/Get-MSOfficeBuildNumber.ps1 diff --git a/MSCatalog/MSCatalog.psd1 b/MSCatalog/MSCatalog.psd1 index 6cecf7b..11ed341 100644 --- a/MSCatalog/MSCatalog.psd1 +++ b/MSCatalog/MSCatalog.psd1 @@ -69,7 +69,8 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Get-MSCatalogUpdate', - 'Save-MSCatalogUpdate' + 'Save-MSCatalogUpdate', + 'Get-MSOfficeBuildNumber' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/MSCatalog/Public/Get-MSCatalogUpdate.ps1 b/MSCatalog/Public/Get-MSCatalogUpdate.ps1 index cb8a4c0..385729c 100644 --- a/MSCatalog/Public/Get-MSCatalogUpdate.ps1 +++ b/MSCatalog/Public/Get-MSCatalogUpdate.ps1 @@ -60,11 +60,11 @@ function Get-MSCatalogUpdate { [string] $Search, - [Parameter(Mandatory = $false, HelpMessage="Default is sort by LastUpdated")] + [Parameter(Mandatory = $false)] [ValidateSet("Title", "Products", "Classification", "LastUpdated", "Size")] [string] $SortBy = "LastUpdated", - [Parameter(Mandatory = $false, HelpMessage="Default is descending order.")] + [Parameter(Mandatory = $false)] [switch] $Descending = $true, [Parameter(Mandatory = $false)] diff --git a/MSCatalog/Public/Get-MSOfficeBuildNumber.ps1 b/MSCatalog/Public/Get-MSOfficeBuildNumber.ps1 new file mode 100644 index 0000000..70073be --- /dev/null +++ b/MSCatalog/Public/Get-MSOfficeBuildNumber.ps1 @@ -0,0 +1,172 @@ +function Get-MSOfficeBuildNumber { + <# + .SYNOPSIS + Query https://docs.microsoft.com/en-us/officeupdates/update-history-microsoft365-apps-by-date for *Valid* Office 365 build numbers + + .DESCRIPTION + Given that there is currently no public API available for optaining the Office 365 build numbers, this + command makes HTTP requests to the site and parses the returned HTML for the required data. + + NOTE: Unlike the Get-MSCatalogUpdate function, this function is currently limited to return ONE BuildNumber or none at all. + + .PARAMETER PTYear + Patch Tuesday year - Defaults to current year + + .PARAMETER PTMonthDay + Patch Tuesday Month and Day (the second Tuesday of each month) + + .PARAMETER ExcludePreview + Exclude preview updates from the search results. + + .PARAMETER UpdateChannel + Must be "Current Channel", "Monthly Enterprise Channel", "Semi-Annual Enterprise Channel (Preview)" + This should match your current Channel of your Office products (Unless you are changing channels...) + + + .EXAMPLE + Get-MSOfficeBuildNumber -PTMonthDay 'July 12' -UpdateChannel "Monthly Enterprise Channel" + #Once a *VALID* Office 365 build number is obtained, OfficeC2RClient.exe can be used to force the existing Office 365 installation to a specified BuildNumber. + $arguments = "/update user displaylevel=False forceappshutdown=True updatetoversion=$M365UpdateFullBuildNumber" + Start-Process -FilePath "C:\Program Files\Common Files\microsoft shared\ClickToRun\OfficeC2RClient.exe" $arguments -Wait + + CAVEAT: For this to work, CDNBaseUrl and UpdateChannel must be set in the registry before you run OfficeC2RClient.exe + [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration] + "CDNBaseUrl"="http://officecdn.microsoft.com/pr/ABCDEF00-0000-0000-0000-CHANGE00ME00" + "UpdateChannel"="http://officecdn.microsoft.com/pr/ABCDEF00-0000-0000-0000-CHANGE00ME00" + + + .NOTES + This is the initial version of Get-MSOfficeBuildNumber. + It can only return ONE Build number (not a whole array) due to *MY* lack of skills with HtmlAgilityPack + HTML. + Any help/updates is greatly encouraged/appreciated. + + CAVEAT: If MS changes the structure of the web page, This script and/or PS modules might have to be updated. + Based on Original concept from Mark Garcia, Toronto, Canada + + .LINK + https://github.com/ryan-jan/MSCatalog + #> + + [CmdLetBinding()] + param ( + [Parameter(Mandatory = $false)] + [string]$PTYear = $(Get-Date -Format yyyy), + + [Parameter(Mandatory = $true)] #Release Date + [string]$PTMonthDay = $(Get-Date -Format 'MMMM dd'), + + [Parameter(Mandatory = $false)] + [ValidateSet("Current Channel", "Monthly Enterprise Channel", "Semi-Annual Enterprise Channel (Preview)")] + [string]$UpdateChannel = "Monthly Enterprise Channel" + ) + + try { +#[String]$MSCatalogPsmPath = "$scriptDirectory\mscatalog.0.28.2\MSCatalog.psm1" +#[String]$MSCatalogPsmPath = "C:\Admutils\ScriptDevSandbox\mscatalog.0.28.2\MSCatalog.psm1" +#Import-Module -Name $MSCatalogPsmPath #dev only!!! +#."C:\Admutils\ScriptDevSandbox\MSCatalog.0.28.2\Private\Invoke-CatalogRequest.ps1" +#."C:\Admutils\ScriptDevSandbox\MSCatalog.0.28.2\Private\Set-TempSecurityProtocol.ps1" +#."C:\Admutils\ScriptDevSandbox\MSCatalog.0.28.2\Private\Invoke-ParseDate.ps1" +#."C:\Admutils\ScriptDevSandbox\MSCatalog.0.28.2\Private\Sort-CatalogResults.ps1" +#."C:\Admutils\ScriptDevSandbox\MSCatalog.0.28.2\Private\Get-UpdateLinks.ps1" + + +# $ProgPref = $ProgressPreference +# $ProgressPreference = "SilentlyContinue" + + # Query M365 Update History site to get M365 build numbers + $Uri = "https://docs.microsoft.com/en-us/officeupdates/update-history-microsoft365-apps-by-date" + #$Res = Invoke-CatalogRequest -Uri $Uri #Fails with "Unable to find type [MSCatalogResponse]" so we have to do it differently + + try { + $HtmlDoc = [HtmlAgilityPack.HtmlDocument]::new() + $Web = [HtmlAgilityPack.HtmlWeb]::new() + $doc = $Web.Load($Uri) #Scrape the web page + + $HTMLTableTRList = $doc.DocumentNode.SelectNodes("//table") ##Select ALL tables on the page + + #Select the second table (index 1) - aka History Table. Extract the Text only + #Note: Each Cell becomes a row in the table + $M365RawTableData = $HTMLTableTRList[1] | Select-Object -ExpandProperty InnerText + #$M365RawTableData | Set-Content c:\Temp\O365UpdatesInnerText.txt -Force #For DEV. Export the table we captured + } catch { + throw $_ + } + + [Array]$M365QualityUpdateInfo = @() + #Loop through each line of the Version History table on the page (as text in $M365RawTableArray) + [Array]$M365RawTableArray = $M365RawTableData.split("`r`n") + for ($index = 0; $index -lt $M365RawTableArray.count; $index++) { + $entry = $M365RawTableArray[$index] + if($entry -eq "2021") { + break #out of foreach loop, we went too far + } + + #in the Original version we could detect "2022July 12" all in one line, but MS changed IE or the web page. + #With the HTMLAgilityPack, we need to detect "2022July 12" but we can only compare ONE line at a time so... + if($entry -ne "") { + #If the current row matches the $PTYear and the next row(s) matches $PTMonthDay + if(($entry -like "$PTYear*") -and ($M365RawTableArray[$index+1] -like "$PTMonthDay*")) { + $M365RawTableArray[$index+1] | Out-File "$env:systemdrive\Temp\Logs\M365AppsUpdate_InstallTS.log" -Append -Encoding UTF8 + #grab current row and the next four + $M365QualityUpdateInfo += $entry #Year + $M365QualityUpdateInfo += $M365RawTableArray[$index+1] #Release Date [MonthDay] + $M365QualityUpdateInfo += $M365RawTableArray[$index+2] #Current Channel + $M365QualityUpdateInfo += $M365RawTableArray[$index+3] #Monthly Enterprise Channel + $M365QualityUpdateInfo += $M365RawTableArray[$index+4] #Semi-Annual Enterprise Channel (Preview) + break #out of foreach loop + } + } + } + + Write-host "`r`nThis is the row we have scraped from table:" + Write-host "[$($M365QualityUpdateInfo| out-string)]`r`n" + + If ($M365QualityUpdateInfo.Count -lt 1) { + Write-Warning "No build numbers found for date: $PTyear $PTMonthDay" + return $null + } + + switch ($UpdateChannel) { + "Current Channel" { + [Int]$UpdateChannelIndex = 2 + break + } + "Monthly Enterprise Channel" { + [Int]$UpdateChannelIndex = 3 + break + } + "Semi-Annual Enterprise Channel (Preview)" { + [Int]$UpdateChannelIndex = 4 + break + } + default { + Write-Error "ERROR: -UpdateChannel [$UpdateChannel] is not supported" + Throw "-UpdateChannel [$UpdateChannel] is not supported" + } + } + + + Write-Host "$UpdateChannel build number [$($M365QualityUpdateInfo[$UpdateChannelIndex])]" + [String]$M365UpdateBuildNumber = $($M365QualityUpdateInfo[$UpdateChannelIndex]).trim().replace(')','') + #grab the last build number of the line in-case we have multiple updates + $M365UpdateBuildNumber = $M365UpdateBuildNumber.Substring($M365UpdateBuildNumber.Length - 11) + + #Add 16.0. to the Build number so that OfficeC2RClient.exe can use it. + Write-host "Selected Microsoft 365 Apps Build Number: [$M365UpdateBuildNumber]" + [String]$M365UpdateFullBuildNumber = "16.0." + $M365UpdateBuildNumber + + return $M365UpdateFullBuildNumber + } catch { + if ($_.Exception.Message -like "We did not find*") { + Write-Warning $_.Exception.Message + } else { + throw $_ + } + } +} + + +#Get-MSOfficeBuildNumber -PTMonthDay 'July 12' -UpdateChannel "Monthly Enterprise Channel" +Get-MSOfficeBuildNumber -PTMonthDay 'July 12' -UpdateChannel "Current Channel" +#Get-MSOfficeBuildNumber -PTMonthDay 'July 13' -UpdateChannel "Current Channel" \ No newline at end of file