This module is an alternative to Evergreen, and allows you to find the latest version and download URL for various Windows apps. Evergreen uses API queries to obtain its data whereas this module is more focussed on web scraping. This is more prone to breaking when websites are changed, hence the name.
only returns the latest new full version in 'English (United States)' language, but does not return any earlier versions.
Earlier versions are sometimes still required as Microsoft may still update earlier versions to fix security issues.
Earlier versions are required to work with specific components of certain SQL server releases (SSIS for example requires v18.x)
Therefore I have re-written the Get-MicrosoftSSMS.ps1 script to webscrape the release notes page, to return all current released versions.
# Get-SSMS.ps1
# Define AppName
$AppName = "Microsoft SQL Server Management Studio"
# Main script to fetch and process links
$ReleaseURL = "https://learn.microsoft.com/en-us/sql/ssms/release-notes-ssms?view=sql-server-ver16" # Replace with the URL of the web page you want to process
Write-Verbose "Obtaining $($AppName) Release Versions...`n=============================================================================`n"
# Fetch the HTML content of the webpage
$htmlContent = Invoke-WebRequest -Uri $ReleaseUrl
# Define the RegEx pattern to match the links and capture the version number and BaseURI
$pattern = '<a[^>]*href="([^"]*)"[^>]*>\s*Download SSMS ([\d.]+)\s*</a>'
$SearchCount = ([regex]::Matches($htmlContent.RawContent, $pattern)).Count
Write-Verbose "$($AppName) Release Versions found: $($SearchCount)`n-------------------------------------------------------------------------`n"
do {
# Use RegEx to find matches
foreach ($patternmatch in [regex]::Matches($htmlContent.Content, $pattern)) {
# Extract the URI and version number
$ThisVersionUri = $patternmatch.Groups[1].Value
[version]$ThisVersion = $patternmatch.Groups[2].Value
$BaseVersion = $($ThisVersion.Major)
# Regular expression to match the release information
$regex = "<li>Release number: $($ThisVersion)</li>`n<li>Build number: (.*?)</li>`n<li>Release date: (.*?)</li>"
$BuildCount = ([regex]::Matches($htmlContent.RawContent, $regex)).Count
foreach ($regexmatch in [regex]::Matches($htmlContent.RawContent, $regex)) {
#$ReleaseVersion = $regexmatch.Groups[1].Value
[version]$BuildNumber = $regexmatch.Groups[1].Value
$ReleaseDate = $regexmatch.Groups[2].Value
do {
$AppVersions = @(
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Chinese (Simplified)'; Pattern = '&clcid=0x804'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Chinese (Traditional)'; Pattern = '&clcid=0x404'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'English'; Pattern = '&clcid=0x409'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'French'; Pattern = '&clcid=0x40c'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'German'; Pattern = '&clcid=0x407'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Italian'; Pattern = '&clcid=0x410'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Japanese'; Pattern = '&clcid=0x411'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Korean'; Pattern = '&clcid=0x412'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Portuguese (Brazil)'; Pattern = '&clcid=0x416'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Russian'; Pattern = '&clcid=0x419'}
@{Version = $ThisVersion; BuildNumber = $BuildNumber; ReleaseDate = $ReleaseDate; Language = 'Spanish'; Pattern = '&clcid=0x40a'}
)
foreach ($AppVersion in $AppVersions) {
if ($ThisVersionUri) {
#Build each link with Language specific versions
$URL = (Resolve-Uri -Uri "$($ThisVersionUri)$($AppVersion.Pattern)").Uri
New-NevergreenApp -Name $AppName -Ring $BaseVersion -Version $AppVersion.Version -BuildNumber $AppVersion.BuildNumber -ReleaseDate $AppVersion.ReleaseDate -Uri $URL -Architecture 'x64' -Language $AppVersion.Language -Type 'Exe'
#break
}
}
$BuildCount--
} until ($BuildCount -eq 0)
}
$SearchCount--
}
} until ($SearchCount -eq 0)
This modified script has also required the modification of the New-NeverGreenApp.ps1 script to specifically handle the BuildNumber and ReleaseDate parameters (To give similar functionality as Evergreen), so here is my updated version:
function New-NevergreenApp {
<#
.SYNOPSIS
Returns a PSCustomObject to output.
.DESCRIPTION
Returns a PSCustomObject to output.
.NOTES
Site: https://packageology.com
Author: Dan Gough
Twitter: @packageologist
.LINK
https://github.com/DanGough/Nevergreen
.PARAMETER Name
Mandatory. The name of the application.
.PARAMETER Uri
Mandatory. The download URI for the application.
.PARAMETER Version
Mandatory. The application version.
.PARAMETER Architecture
Mandatory. Must match x86, x64, ARM32, ARM64 or Multi.
.PARAMETER Type
Mandatory. Must match Msi, Msp, Exe, Zip, Msix, AppX, AppV, 7z if supplied.
.PARAMETER Language
Optional. The language of the application installer, e.g. 'en'.
.PARAMETER Ring
Optional. The deployment ring, e.g. 'General', 'Preview'.
.PARAMETER Channel
Optional. The channel, e.g. 'Enterprise'.
.PARAMETER Platform
Optional. The platform, e.g. 'Windows Server'.
.PARAMETER BuildNumber
Optional. The Build Version (used where available - example SSMS).
.PARAMETER ReleaseDate
Optional. The Date the version was released.
.PARAMETER MD5
Optional. The MD5 hash of the file.
.PARAMETER SHA256
Optional. The SHA256 hash of the file.
.EXAMPLE
New-NevergreenApp -Uri 'http://somewhere.com/something.exe' -Version '1.0' -Architecture 'x64' -Type 'Exe'
Description:
Outputs a PSCustomObject with the chosen properties.
#>
[CmdletBinding(SupportsShouldProcess = $False)]
param (
[Parameter(
Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String] $Name,
[Parameter(
Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[String] $Version,
[Parameter(
Mandatory = $true)]
[ValidatePattern('^(http|https)://')]
[Alias('Url')]
[String] $Uri,
[Parameter(
Mandatory = $true)]
[ValidateSet('x86', 'x64', 'ARM32', 'ARM64', 'Multi')]
[String] $Architecture,
[Parameter(
Mandatory = $true)]
[ValidateSet('Msi', 'Msp', 'Exe', 'Zip', 'Msix', 'AppX', 'AppV', '7z')]
[String] $Type,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $Language,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $Ring,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $Channel,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $Platform,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $BuildNumber,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $ReleaseDate,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $MD5,
[Parameter(
Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String] $SHA256
)
$Output = [PSCustomObject]@{
Name = $Name
}
if ($Ring) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Ring' -Value $Ring
}
if ($Channel) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Channel' -Value $Channel
}
if ($Language) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Language' -Value $Language
}
if ($BuildNumber) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'BuildNumber' -Value $BuildNumber
}
if ($ReleaseDate) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'ReleaseDate' -Value $ReleaseDate
}
if ($Platform) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Platform' -Value $Platform
}
if ($Architecture) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Architecture' -Value $Architecture
}
if ($Type) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Type' -Value $Type
}
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Version' -Value $Version
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'Uri' -Value $Uri
if ($MD5) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'MD5' -Value $MD5
}
if ($SHA256) {
Add-Member -InputObject $Output -MemberType NoteProperty -Name 'SHA256' -Value $SHA256
}
$Output
}
Thanks, I can take a look and add to the next release - I have a few apps to fix and a couple of PRs to review, which I should get around to doing some time this weekend!
The current
Get-MicrosoftSSMS.ps1
script:only returns the latest new full version in 'English (United States)' language, but does not return any earlier versions. Earlier versions are sometimes still required as Microsoft may still update earlier versions to fix security issues. Earlier versions are required to work with specific components of certain SQL server releases (SSIS for example requires v18.x)
Therefore I have re-written the
Get-MicrosoftSSMS.ps1
script to webscrape the release notes page, to return all current released versions.This modified script has also required the modification of the
New-NeverGreenApp.ps1
script to specifically handle the BuildNumber and ReleaseDate parameters (To give similar functionality as Evergreen), so here is my updated version: