Files
WindowsUpdateHistory/Get-WindowsReleaseHistory.ps1
2024-10-31 13:04:40 -04:00

732 lines
71 KiB
PowerShell

## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx
#region Function Get-WindowsReleaseHistory
Function Get-WindowsReleaseHistory
{
<#
.SYNOPSIS
Retrieves the Windows release history by scraping the release information from the internet.
.DESCRIPTION
This function dynamically navigates the HTML document returned from each web request using XPath, and the HTMLAgilityPack library. All prerequisite requirements are handled within the function.
.EXAMPLE
Get-WindowsReleaseHistory
.EXAMPLE
Get-WindowsReleaseHistory -Verbose
.EXAMPLE
Get-WindowsReleaseHistory | Where-Object {($_.Type -ieq 'Client') -and ($_.HasLongTermServicingBuild -eq $True)}
.EXAMPLE
Get-WindowsReleaseHistory | Where-Object {($_.Type -ieq 'Server')}
.EXAMPLE
Get-WindowsReleaseHistory | Where-Object {($_.Releases.KBArticle -ieq 'KB5044384')}
.NOTES
OperatingSystem : Windows 11
Type : Client
ReleaseID : 24H2
InitialReleaseVersion : 10.0.26100.1742
HasLongTermServicingBuild : True
Releases : {@{ServicingOptions=System.String[]; AvailabilityDate=10/24/2024 12:00:00 AM; Version=10.0.26100.2161; KBArticle=KB5044384;
KBArticleURL=https://support.microsoft.com/help/5044384}, @{ServicingOptions=System.String[]; AvailabilityDate=10/8/2024 12:00:00 AM; Version=10.0.26100.2033;
KBArticle=KB5044284; KBArticleURL=https://support.microsoft.com/help/5044284}, @{ServicingOptions=System.String[]; AvailabilityDate=10/1/2024 12:00:00 AM;
Version=10.0.26100.1742; KBArticle=; KBArticleURL=https://support.microsoft.com/help/5044284}}
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$False)]
[Switch]$ContinueOnError
)
Begin
{
[ScriptBlock]$ErrorHandlingDefinition = {
$ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$ErrorMessageList.Add('Message', $_.Exception.Message)
$ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID)
$ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName)
$ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber)
$ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine)
$ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim())
ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator())
{
$LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)"
Write-Warning -Message ($LoggingDetails.ErrorMessage)
}
Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False))
{
{($_ -eq $True)}
{
Throw
}
}
}
Try
{
$DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM###
[ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)}
$DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347###
[ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)}
$DateFileFormat = 'yyyyMMdd' ###20190403###
[ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)}
$DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354###
[ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)}
$TextInfo = (Get-Culture).TextInfo
$LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$LoggingDetails.LogMessage = $Null
$LoggingDetails.WarningMessage = $Null
$LoggingDetails.ErrorMessage = $Null
$CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]'
$CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters)
$CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters)
$DateTimeProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$DateTimeProperties.FormatList = New-Object -TypeName 'System.Collections.Generic.List[String]'
$DateTimeProperties.FormatList.AddRange(([System.Globalization.DateTimeFormatInfo]::CurrentInfo.GetAllDateTimePatterns()))
$DateTimeProperties.FormatList.AddRange(([System.Globalization.DateTimeFormatInfo]::InvariantInfo.GetAllDateTimePatterns()))
$DateTimeProperties.FormatList.Add('yyyyMM')
$DateTimeProperties.FormatList.Add('yyyyMMdd')
$DateTimeProperties.Culture = $Null
$DateTimeProperties.Styles = New-Object -TypeName 'System.Collections.Generic.List[System.Globalization.DateTimeStyles]'
$DateTimeProperties.Styles.Add([System.Globalization.DateTimeStyles]::AssumeLocal)
$DateTimeProperties.Styles.Add([System.Globalization.DateTimeStyles]::AllowWhiteSpaces)
$RegexOptionList = New-Object -TypeName 'System.Collections.Generic.List[System.Text.RegularExpressions.RegexOptions]'
$RegexOptionList.Add('IgnoreCase')
$RegexOptionList.Add('Multiline')
$XPathExpressionDictionary = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Xml.XPath.XPathExpression]]'
$XPathExpressionDictionary.ServicingChannelNodes = [System.Xml.XPath.XPathExpression]::Compile("descendant::table[not(contains(@id,'historyTable_'))]")
$XPathExpressionDictionary.HistoryTableNodes = [System.Xml.XPath.XPathExpression]::Compile("descendant::table[contains(@id,'historyTable_')]")
$XPathExpressionDictionary.OperatingSystemInfoNode = [System.Xml.XPath.XPathExpression]::Compile("preceding::strong[position()=1]")
$XPathExpressionDictionary.TableRowHeaderDefinitionNode = [System.Xml.XPath.XPathExpression]::Compile("descendant::tr[position()=1]")
$XPathExpressionDictionary.TableRowHeaderNodes = [System.Xml.XPath.XPathExpression]::Compile("descendant::th")
$XPathExpressionDictionary.TableRowDataNodes = [System.Xml.XPath.XPathExpression]::Compile("descendant::tr[position()>1]")
$XPathExpressionDictionary.TableRowDataValueNodes = [System.Xml.XPath.XPathExpression]::Compile("descendant::td")
#Determine the date and time we executed the function
$FunctionStartTime = (Get-Date)
[String]$FunctionName = $MyInvocation.MyCommand
[System.IO.FileInfo]$FunctionPath = $PSCommandPath
[System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory.FullName)"
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is beginning. Please Wait..."
Write-Verbose -Message ($LoggingDetails.LogMessage)
#Define Default Action Preferences
$ErrorActionPreference = 'Stop'
[String[]]$AvailableScriptParameters = (Get-Command -Name ($FunctionName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"}
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')"
Write-Verbose -Message ($LoggingDetails.LogMessage)
[String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"}
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')"
Write-Verbose -Message ($LoggingDetails.LogMessage)
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))"
Write-Verbose -Message ($LoggingDetails.LogMessage)
#region Load any required libraries
$PackageDictionary = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Object]]'
$PackageDictionary.GetSources = {Get-PackageSource -Force -ErrorAction SilentlyContinue}
$PackageDictionary.RequiredSources = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Collections.Hashtable]]'
$PackageDictionary.RequiredSources.NuGetPackages = @{Location = 'https://www.nuget.org/api/v2'; ProviderName = 'NuGet'; Trusted = $True}
$PackageDictionary.GetPackages = {Get-Package -Scope AllUsers}
$PackageDictionary.RequiredPackages = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Collections.Hashtable]]'
$PackageDictionary.RequiredPackages.HtmlAgilityPack = @{DotNetVersion = 'Net45'; ProviderName = $PackageDictionary.RequiredSources.NuGetPackages.ProviderName}
ForEach ($PackageDictionaryKVP In $PackageDictionary.GetEnumerator())
{
Switch ($PackageDictionaryKVP.Key)
{
{($_ -iin @('RequiredSources'))}
{
$PackageSources = $PackageDictionary.GetSources.Invoke()
ForEach ($RequiredSourceKVP In $PackageDictionary.RequiredSources.GetEnumerator())
{
Switch ($PackageSources.Name -inotcontains $RequiredSourceKVP.Key)
{
{($_ -eq $True)}
{
$Null = Register-PackageSource -Name ($RequiredSourceKVP.Key) -Location ($RequiredSourceKVP.Value.Location) -ProviderName ($RequiredSourceKVP.Value.ProviderName) -Trusted:$($RequiredSourceKVP.Value.Trusted) -Force -Confirm:$False
}
}
}
}
{($_ -iin @('RequiredPackages'))}
{
$Packages = $PackageDictionary.GetPackages.Invoke()
ForEach ($RequiredPackageKVP In $PackageDictionary.RequiredPackages.GetEnumerator())
{
$PackageInfoLocal = Try {Get-Package -Name ($RequiredPackageKVP.Key) -ProviderName ($RequiredPackageKVP.Value.ProviderName)} Catch {$Null}
Switch ($Null -ieq $PackageInfoLocal)
{
{($_ -eq $True)}
{
$PackageInfoRemote = Find-Package -Name ($RequiredPackageKVP.Key) -ProviderName ($RequiredPackageKVP.Value.ProviderName) -Force | Select-Object -First 1
Switch ($Null -ine $PackageInfoRemote)
{
{($_ -eq $True)}
{
$PackageInfoRemote = Install-Package -Name ($RequiredPackageKVP.Key) -ProviderName ($RequiredPackageKVP.Value.ProviderName) -SkipDependencies -Force -Confirm:$False
$PackageInfoLocal = Try {Get-Package -Name ($RequiredPackageKVP.Key) -ProviderName ($RequiredPackageKVP.Value.ProviderName)} Catch {$Null}
}
}
}
}
[System.IO.FileInfo]$PackageSource = $PackageInfoLocal.Source
[System.IO.FileInfo]$PackageLibraryPath = "$($PackageSource.Directory.FullName)\lib\$($RequiredPackageKVP.Value.DotNetVersion)\$($RequiredPackageKVP.Key).dll"
$Null = [System.Reflection.Assembly]::LoadFrom($PackageLibraryPath.FullName)
}
}
}
}
#endregion
#Create an object that will contain the functions output.
$OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
#region Adjust security protocol type(s)
[System.Net.SecurityProtocolType]$DesiredSecurityProtocol = [System.Net.SecurityProtocolType]::TLS12
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to set the desired security protocol to `"$($DesiredSecurityProtocol.ToString().ToUpper())`". Please Wait..."
Write-Verbose -Message ($LoggingDetails.LogMessage)
$Null = [System.Net.ServicePointManager]::SecurityProtocol = ($DesiredSecurityProtocol)
#endregion
}
Catch
{
$ErrorHandlingDefinition.Invoke()
}
Finally
{
}
}
Process
{
Try
{
#Instantiate an instance of the WebClient .NET class
$WebClient = New-Object -TypeName 'System.Net.WebClient'
#Define the update history URL list
[String]$URLListSourceURL = "https://raw.githubusercontent.com/freedbygrace/WindowsUpdateHistory/refs/heads/main/Get-WindowsReleaseHistory.txt"
#Process the update history URL list
$URLList = Try {$WebClient.DownloadString($URLListSourceURL).Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)} Catch {$Null}
$URLListCount = ($URLList | Measure-Object).Count
Switch ($URLListCount -gt 0)
{
{($_ -eq $True)}
{
For ($URLListIndex = 0; $URLListIndex -lt $URLListCount; $URLListIndex++)
{
Try
{
[System.URI]$URL = $ExecutionContext.InvokeCommand.ExpandString(($URLList[$URLListIndex]))
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to retrieve data from `"$($URL.OriginalString)`". Please Wait..."
Write-Verbose -Message ($LoggingDetails.LogMessage)
$HTMLWebRequest = New-Object -TypeName 'HtmlAgilityPack.HtmlWeb'
$HTMLWebRequestObject = $HTMLWebRequest.Load($URL.OriginalString)
Switch ($URL.OriginalString)
{
{($_ -imatch '(^.*Server.*$)')}
{
$RegularExpressionTable.OSName = New-Object -TypeName 'System.Text.RegularExpressions.Regex' -ArgumentList @('(Windows\s+Server\s+\d+)', $RegexOptionList)
$RegularExpressionTable.OSInfo = New-Object -TypeName 'System.Text.RegularExpressions.Regex' -ArgumentList @('(?<OSName>.+\s+.+\s+\d{4,})(?:\s+)?(?:.+)(?<BuildNumber>\d{5,})(?:.+)?', $RegexOptionList)
}
Default
{
$RegularExpressionTable.OSName = New-Object -TypeName 'System.Text.RegularExpressions.Regex' -ArgumentList @('(Windows\s+\d+)', $RegexOptionList)
$RegularExpressionTable.OSInfo = New-Object -TypeName 'System.Text.RegularExpressions.Regex' -ArgumentList @('(?:.+)?(?:Version)(?:\s+)?(?<ReleaseID>[a-z0-9]{4,})(?:\s+)?(?:.+)(?<BuildNumber>\d{5,})(?:.+)?', $RegexOptionList)
}
}
$HTMLDocumentObject = $HTMLWebRequestObject.DocumentNode
$ServicingChannelNodes = $HTMLDocumentObject.SelectNodes($XPathExpressionDictionary.ServicingChannelNodes.Expression)
$ServicingChannelNodeCount = ($ServicingChannelNodes | Measure-Object).Count
$ServicingChannelObjectList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
For ($ServicingChannelNodeIndex = 0; $ServicingChannelNodeIndex -lt $ServicingChannelNodeCount; $ServicingChannelNodeIndex++)
{
$ServicingChannelNode = $ServicingChannelNodes[$ServicingChannelNodeIndex]
$TableRowHeaderDefinitionNode = $ServicingChannelNode.SelectSingleNode($XPathExpressionDictionary.TableRowHeaderDefinitionNode.Expression)
Switch ($Null -ine $TableRowHeaderDefinitionNode)
{
{($_ -eq $True)}
{
$TableRowHeaderNodes = $TableRowHeaderDefinitionNode.SelectNodes($XPathExpressionDictionary.TableRowHeaderNodes.Expression)
Switch ($Null -ine $TableRowHeaderNodes)
{
{($_ -eq $True)}
{
$TableRowDataNodes = $ServicingChannelNode.SelectNodes($XPathExpressionDictionary.TableRowDataNodes.Expression)
$TableRowDataNodeCount = ($TableRowDataNodes | Measure-Object).Count
For ($TableRowDataNodeIndex = 0; $TableRowDataNodeIndex -lt $TableRowDataNodeCount; $TableRowDataNodeIndex++)
{
$TableRowDataNode = $TableRowDataNodes[$TableRowDataNodeIndex]
$ServicingChannelObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$ServicingChannelObjectProperties.OperatingSystem = $Null
$ServicingChannelObjectProperties.ReleaseID = $Null
$TableRowDataValueNodes = $TableRowDataNode.SelectNodes($XPathExpressionDictionary.TableRowDataValueNodes)
$TableRowDataValueNodeCount = ($TableRowDataValueNodes | Measure-Object).Count
For ($TableRowDataValueNodeIndex = 0; $TableRowDataValueNodeIndex -lt $TableRowDataValueNodeCount; $TableRowDataValueNodeIndex++)
{
$TableRowHeaderNode = $TableRowHeaderNodes[$TableRowDataValueNodeIndex]
$OriginalTableRowHeaderName = Try {$TableRowHeaderNode.InnerText} Catch {$Null}
Try
{
$TableRowHeaderName = $TextInfo.ToTitleCase($OriginalTableRowHeaderName) -ireplace '(\s+)', ''
$TableRowDataValueNode = $TableRowDataValueNodes[$TableRowDataValueNodeIndex]
$TableRowDataValue = Try {$TableRowDataValueNode.InnerText} Catch {$Null}
Switch (([String]::IsNullOrEmpty($TableRowDataValue) -eq $False) -and ([String]::IsNullOrWhiteSpace($TableRowDataValue) -eq $False))
{
{($_ -eq $True)}
{
Switch ($TableRowHeaderName)
{
{($_ -imatch '(^.*Date$)')}
{
$DateTime = New-Object -TypeName 'DateTime'
$DateTimeProperties.Input = $TableRowDataValue
$DateTimeProperties.Successful = [DateTime]::TryParseExact($DateTimeProperties.Input, $DateTimeProperties.FormatList, $DateTimeProperties.Culture, $DateTimeProperties.Styles.ToArray(), [Ref]$DateTime)
$DateTimeProperties.DateTime = $DateTime
$DateTimeObject = New-Object -TypeName 'PSObject' -Property ($DateTimeProperties)
Switch ($DateTimeObject.Successful)
{
{($_ -eq $True)}
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $DateTimeObject.DateTime.Date
}
Default
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $TextInfo.ToTitleCase($DateTimeProperties.Input)
}
}
}
{($_ -imatch '(^.*Release$)|(^Version$)')}
{
$ServicingChannelObjectProperties.OperatingSystem = $TableRowDataValue
Switch ($TableRowDataValue -imatch '(^.*\(Version.*\)$)')
{
{($_ -eq $True)}
{
$ServicingChannelObjectProperties.ReleaseID = Try {[Regex]::Match($TableRowDataValue, '(?:.+\(version\s+)(.+)(?:\))').Groups['1'].Value} Catch {$Null}
}
Default
{
$ServicingChannelObjectProperties.ReleaseID = Try {[Regex]::Match($TableRowDataValue, '[a-z0-9]{4,4}').Value} Catch {$Null}
}
}
}
{($_ -imatch '(^.*Build$)')}
{
Try
{
$TableRowDataValue = New-Object -TypeName 'System.Version' -ArgumentList @(10, 0, ($TableRowDataValue.Split('.')[0]), ($TableRowDataValue.Split('.')[1]))
}
Catch
{
$TableRowDataValue = New-Object -TypeName 'System.Version' -ArgumentList @(10, 0, ($TableRowDataValue.Split('.')[0]), 0)
}
$TableRowHeaderName = 'Version'
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $TableRowDataValue
}
{($_ -imatch '(^.*Servicing.*Option.*$)')}
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)s" = $TableRowDataValue
}
{($_ -imatch '(^Editions$)')}
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $TableRowDataValueNode.InnerText.Split(',', [System.StringSplitOptions]::RemoveEmptyEntries).Trim()
}
Default
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $TableRowDataValue
}
}
}
Default
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $Null
}
}
}
Catch
{
$ServicingChannelObjectProperties."$($TableRowHeaderName)" = $Null
}
Finally
{
}
}
$ServicingChannelObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($ServicingChannelObjectProperties)
$ServicingChannelObjectList.Add($ServicingChannelObject)
}
}
}
}
}
}
$HistoryTableNodes = $HTMLDocumentObject.SelectNodes($XPathExpressionDictionary.HistoryTableNodes.Expression)
$HistoryTableNodeCount = ($HistoryTableNodes | Measure-Object).Count
For ($HistoryTableNodeIndex = 0; $HistoryTableNodeIndex -lt $HistoryTableNodeCount; $HistoryTableNodeIndex++)
{
$HistoryTableNode = $HistoryTableNodes[$HistoryTableNodeIndex]
$OperatingSystemInfoNode = $HistoryTableNode.SelectSingleNode($XPathExpressionDictionary.OperatingSystemInfoNode.Expression)
Switch ($Null -ine $OperatingSystemInfoNode)
{
{($_ -eq $True)}
{
$BuildNumber = Try {$RegularExpressionTable.OSInfo.Match($OperatingSystemInfoNode.GetDirectInnerText()).Groups['BuildNumber'].Value} Catch {$Null}
$OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$OutputObjectProperties.OperatingSystem = Try {$RegularExpressionTable.OSName.Match($HTMLDocumentObject.OuterHtml).Value} Catch {$Null}
$OutputObjectProperties.Type = 'Client'
$OutputObjectProperties.ReleaseID = Try {$RegularExpressionTable.OSInfo.Match($OperatingSystemInfoNode.GetDirectInnerText()).Groups['ReleaseID'].Value} Catch {$Null}
$OutputObjectProperties.InitialReleaseVersion = New-Object -TypeName 'System.Version'
$OutputObjectProperties.HasLongTermServicingBuild = $False
$OutputObjectProperties.Releases = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
Switch ($URL.OriginalString)
{
{($_ -imatch '(^.*Server.*$)')}
{
$OutputObjectProperties.OperatingSystem = Try {$RegularExpressionTable.OSName.Match($OperatingSystemInfoNode.GetDirectInnerText()).Value} Catch {$Null}
$OutputObjectProperties.ReleaseID = Try {[System.Text.RegularExpressions.Regex]::Match($OutputObjectProperties.OperatingSystem, '\d{4,4}').Value} Catch {$Null}
$OutputObjectProperties.Type = "Server"
}
}
Switch ($OutputObjectProperties.OperatingSystem)
{
{($_ -imatch '(^SomeOtherOperatingSystemName$)')}
{
#$OutputObjectProperties.InitialReleaseVersion = New-Object -TypeName 'System.Version' -ArgumentList @(10, 0, $BuildNumber)
}
Default
{
$OutputObjectProperties.InitialReleaseVersion = New-Object -TypeName 'System.Version' -ArgumentList @(10, 0, $BuildNumber, 0)
}
}
$OutputObjectProperties.HasLongTermServicingBuild = Try {[Boolean]($ServicingChannelObjectList | Where-Object {($_.Version.Build -eq $OutputObjectProperties.InitialReleaseVersion.Build) -and ($_.ServicingOptions -imatch '(^LTSB|LTSC|.*Long.*Term.*$)')})} Catch {$False}
$TableRowHeaderDefinitionNode = $HistoryTableNode.SelectSingleNode($XPathExpressionDictionary.TableRowHeaderDefinitionNode.Expression)
Switch ($Null -ine $TableRowHeaderDefinitionNode)
{
{($_ -eq $True)}
{
$TableRowHeaderNodes = $TableRowHeaderDefinitionNode.SelectNodes($XPathExpressionDictionary.TableRowHeaderNodes.Expression)
Switch ($Null -ine $TableRowHeaderNodes)
{
{($_ -eq $True)}
{
$HistoryTableObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$TableRowDataNodes = $HistoryTableNode.SelectNodes($XPathExpressionDictionary.TableRowDataNodes.Expression)
$TableRowDataNodeCount = ($TableRowDataNodes | Measure-Object).Count
For ($TableRowDataNodeIndex = 0; $TableRowDataNodeIndex -lt $TableRowDataNodeCount; $TableRowDataNodeIndex++)
{
$TableRowDataNode = $TableRowDataNodes[$TableRowDataNodeIndex]
$TableRowDataValueNodes = $TableRowDataNode.SelectNodes($XPathExpressionDictionary.TableRowDataValueNodes)
$TableRowDataValueNodeCount = ($TableRowDataValueNodes | Measure-Object).Count
For ($TableRowDataValueNodeIndex = 0; $TableRowDataValueNodeIndex -lt $TableRowDataValueNodeCount; $TableRowDataValueNodeIndex++)
{
$TableRowHeaderNode = $TableRowHeaderNodes[$TableRowDataValueNodeIndex]
$OriginalTableRowHeaderName = Try {$TableRowHeaderNode.GetDirectInnerText()} Catch {$TableRowHeaderNode.InnerText}
Switch (([String]::IsNullOrEmpty($OriginalTableRowHeaderName) -eq $False) -and ([String]::IsNullOrWhiteSpace($OriginalTableRowHeaderName) -eq $False))
{
{($_ -eq $True)}
{
Try
{
$TableRowHeaderName = $TextInfo.ToTitleCase($OriginalTableRowHeaderName) -ireplace '(\s+)', ''
$TableRowDataValueNode = $TableRowDataValueNodes[$TableRowDataValueNodeIndex]
$TableRowDataValue = Try {$TableRowDataValueNode.InnerText} Catch {$Null}
Switch (([String]::IsNullOrEmpty($TableRowDataValue) -eq $False) -and ([String]::IsNullOrWhiteSpace($TableRowDataValue) -eq $False))
{
{($_ -eq $True)}
{
Switch ($TableRowHeaderName)
{
{($_ -imatch '(^AvailabilityDate$)')}
{
$DateTime = New-Object -TypeName 'DateTime'
$DateTimeProperties.Input = Try {$TableRowDataValueNode.InnerText} Catch {$Null}
$DateTimeProperties.Successful = [DateTime]::TryParseExact($DateTimeProperties.Input, $DateTimeProperties.FormatList, $DateTimeProperties.Culture, $DateTimeProperties.Styles.ToArray(), [Ref]$DateTime)
$DateTimeProperties.DateTime = $DateTime
$DateTimeObject = New-Object -TypeName 'PSObject' -Property ($DateTimeProperties)
Switch ($DateTimeObject.Successful)
{
{($_ -eq $True)}
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $DateTimeObject.DateTime.Date
}
Default
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $DateTimeObject.DateTime.Date
}
}
}
{($_ -imatch '(^Build|OSBuild$)')}
{
Try
{
$TableRowDataValue = New-Object -TypeName 'System.Version' -ArgumentList @($OutputObjectProperties.InitialReleaseVersion.Major, $OutputObjectProperties.InitialReleaseVersion.Minor, $OutputObjectProperties.InitialReleaseVersion.Build, ($TableRowDataValue.Split('.')[1]))
}
Catch
{
$TableRowDataValue = New-Object -TypeName 'System.Version' -ArgumentList @($OutputObjectProperties.InitialReleaseVersion.Major, $OutputObjectProperties.InitialReleaseVersion.Minor, $OutputObjectProperties.InitialReleaseVersion.Build, $OutputObjectProperties.InitialReleaseVersion.Revision)
}
$TableRowHeaderName = 'Version'
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $TableRowDataValue
}
{($_ -imatch '(^KBArticle$)')}
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $TableRowDataValue
$HistoryTableObjectProperties."$($TableRowHeaderName)URL" = Try {New-Object -TypeName 'System.URI' -ArgumentList ($TableRowDataValueNode.SelectSingleNode('descendant::a[1]').Attributes['href'].Value)} Catch {$Null}
}
{($_ -imatch '(^.*Servicing.*Option.*$)')}
{
$TableRowDataValueList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
$TableRowDataValueString = Try {$TableRowDataValueNode.InnerText -ireplace '[^a-z0-9\s]', '' -ireplace '\s{2,}', ','} Catch {$Null}
Switch (([String]::IsNullOrEmpty($TableRowDataValueString) -eq $False) -and ([String]::IsNullOrWhiteSpace($TableRowDataValueString) -eq $False))
{
{($_ -eq $True)}
{
$TableRowDataValueListEntries = $TableRowDataValueString.Split(',', [System.StringSplitOptions]::RemoveEmptyEntries)
$TableRowDataValueListEntryCount = ($TableRowDataValueListEntries | Measure-Object).Count
For ($TableRowDataValueListEntryIndex = 0; $TableRowDataValueListEntryIndex -lt $TableRowDataValueListEntryCount; $TableRowDataValueListEntryIndex++)
{
$TableRowDataValueListEntry = $TableRowDataValueListEntries[$TableRowDataValueListEntryIndex]
Switch (([String]::IsNullOrEmpty($TableRowDataValueListEntry) -eq $False) -and ([String]::IsNullOrWhiteSpace($TableRowDataValueListEntry) -eq $False))
{
{($_ -eq $True)}
{
$TableRowDataValueList.Insert($TableRowDataValueListEntryIndex, $TableRowDataValueListEntry)
}
}
}
}
}
$HistoryTableObjectProperties."$($TableRowHeaderName)s" = $TableRowDataValueList.ToArray()
}
Default
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $TableRowDataValue
}
}
}
Default
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $Null
}
}
}
Catch
{
$HistoryTableObjectProperties."$($TableRowHeaderName)" = $Null
}
Finally
{
}
}
}
}
Switch ($HistoryTableObjectProperties.Keys.Count -gt 0)
{
{($_ -eq $True)}
{
$HistoryTableObject = New-Object -TypeName 'PSObject' -Property ($HistoryTableObjectProperties)
Switch ($OutputObjectProperties.Releases.Contains($HistoryTableObject))
{
{($_ -eq $False)}
{
$OutputObjectProperties.Releases.Add($HistoryTableObject)
}
}
}
}
}
}
}
}
}
$InitialReleaseVersion = $OutputObjectProperties.Releases.Version | Sort-Object -Descending | Select-Object -Last 1
Switch ($Null -ine $InitialReleaseVersion)
{
{($_ -eq $True)}
{
$OutputObjectProperties.InitialReleaseVersion = $InitialReleaseVersion
}
}
$OutputObjectProperties.Releases = $OutputObjectProperties.Releases.ToArray()
$OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties)
$OutputObjectList.Add($OutputObject)
}
}
}
}
Catch
{
$ErrorHandlingDefinition.Invoke()
}
Finally
{
}
}
}
}
}
Catch
{
$ErrorHandlingDefinition.Invoke()
}
Finally
{
}
}
End
{
Try
{
#Determine the date and time the function completed execution
$FunctionEndTime = (Get-Date)
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))"
Write-Verbose -Message ($LoggingDetails.LogMessage)
#Log the total script execution time
$FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime)
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)"
Write-Verbose -Message ($LoggingDetails.LogMessage)
$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is completed."
Write-Verbose -Message ($LoggingDetails.LogMessage)
}
Catch
{
$ErrorHandlingDefinition.Invoke()
}
Finally
{
Write-Output -InputObject ($OutputObjectList.ToArray())
}
}
}
#endregion
<#
Get-WindowsReleaseHistory -Verbose
#>