diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1
index 1554d87..f7d16f8 100644
--- a/FFUDevelopment/BuildFFUVM.ps1
+++ b/FFUDevelopment/BuildFFUVM.ps1
@@ -816,78 +816,11 @@ function Get-MicrosoftDrivers {
[int]$WindowsRelease
)
- $url = "https://support.microsoft.com/en-us/surface/download-drivers-and-firmware-for-surface-09bb2e09-2a4b-cb69-0951-078a7739e120"
-
- ### DOWNLOAD DRIVER PAGE CONTENT
- WriteLog "Getting Surface driver information from $url"
- $OriginalVerbosePreference = $VerbosePreference
- $VerbosePreference = 'SilentlyContinue'
- $webContent = Invoke-WebRequest -Uri $url -UseBasicParsing -Headers $Headers -UserAgent $UserAgent
- $VerbosePreference = $OriginalVerbosePreference
+ # Use the shared Learn-based Microsoft model index so the CLI stays aligned with the UI.
+ WriteLog "Getting Surface driver information from the shared Microsoft model index"
+ $models = @(Get-SurfaceDriverModelIndex -DriversFolder $DriversFolder | Select-Object Model, Link)
WriteLog "Complete"
-
- ### PARSE THE DRIVER PAGE CONTENT FOR MODELS AND DOWNLOAD LINKS
- WriteLog "Parsing web content for models and download links"
- $html = $webContent.Content
-
- # Regex to match divs with selectable-content-options__option-content classes
- $divPattern = '
]*class="selectable-content-options__option-content(?: ocHidden)?"[^>]*>(.*?)
'
- $divMatches = [regex]::Matches($html, $divPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- $models = @()
-
- foreach ($divMatch in $divMatches) {
- $divContent = $divMatch.Groups[1].Value
-
- # Find all tables within the div
- $tablePattern = ''
- $tableMatches = [regex]::Matches($divContent, $tablePattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- foreach ($tableMatch in $tableMatches) {
- $tableContent = $tableMatch.Groups[1].Value
-
- # Find all rows in the table
- $rowPattern = ']*>(.*?)
'
- $rowMatches = [regex]::Matches($tableContent, $rowPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- foreach ($rowMatch in $rowMatches) {
- $rowContent = $rowMatch.Groups[1].Value
-
- # Extract cells from the row
- $cellPattern = ']*>\s*(?: ]*>)?(.*?)(?: )?\s* | '
- $cellMatches = [regex]::Matches($rowContent, $cellPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- if ($cellMatches.Count -ge 2) {
- # Model name in the first TD
- $modelName = ($cellMatches[0].Groups[1].Value).Trim()
-
- # # Remove and
tags if present
- # $modelName = $modelName -replace ']*>', '' -replace '
', ''
- # $modelName = $modelName.Trim()
-
-
- # The second TD might contain a link or just text
- $secondTdContent = $cellMatches[1].Groups[1].Value.Trim()
-
- # Look for a link in the second TD
- $linkPattern = ']+href="([^"]+)"[^>]*>'
- $linkMatch = [regex]::Match($secondTdContent, $linkPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
-
- if ($linkMatch.Success) {
- $modelLink = $linkMatch.Groups[1].Value
- }
- else {
- # No link, just text instructions
- $modelLink = $secondTdContent
- }
-
- $models += [PSCustomObject]@{ Model = $modelName; Link = $modelLink }
- }
- }
- }
- }
-
- WriteLog "Parsing complete"
+ WriteLog "Loaded $($models.Count) Surface models from the shared Microsoft model index."
### FIND THE MODEL IN THE LIST OF MODELS
$selectedModel = $models | Where-Object { $_.Model -eq $Model }
diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Drivers.Microsoft.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Drivers.Microsoft.psm1
index 2151752..60b76ff 100644
--- a/FFUDevelopment/FFU.Common/FFU.Common.Drivers.Microsoft.psm1
+++ b/FFUDevelopment/FFU.Common/FFU.Common.Drivers.Microsoft.psm1
@@ -161,12 +161,181 @@ function ConvertTo-SurfaceComparableName {
# Cleanup: remove orphaned "with" left behind by earlier removals (e.g., "Surface Pro 9 with Intel Processor")
$value = $value -replace '(?i)\bwith\b', ''
- $value = $value -replace '\s+', ' '
-
- return $value.Trim().ToUpperInvariant()
-}
-
-function Get-SurfaceSystemSkuReferenceIndex {
+ $value = $value -replace '\s+', ' '
+
+ return $value.Trim().ToUpperInvariant()
+ }
+
+ function ConvertTo-SurfaceHtmlText {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyString()]
+ [string]$HtmlFragment
+ )
+
+ # Normalize HTML fragments from the Learn table into plain text values.
+ $textValue = $HtmlFragment -replace '
', ' '
+ $textValue = $textValue -replace '<[^>]+>', ' '
+ $textValue = [System.Net.WebUtility]::HtmlDecode($textValue)
+ $textValue = $textValue -replace '\s+', ' '
+
+ return $textValue.Trim()
+ }
+
+ function ConvertTo-SurfaceDownloadCenterLink {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$LinkValue
+ )
+
+ # Normalize Learn links down to the canonical Download Center details URL.
+ $decodedLink = [System.Net.WebUtility]::HtmlDecode($LinkValue).Trim()
+ if ([string]::IsNullOrWhiteSpace($decodedLink)) {
+ return $null
+ }
+
+ if ($decodedLink.StartsWith('/')) {
+ $decodedLink = "https://www.microsoft.com$decodedLink"
+ }
+
+ $downloadCenterMatch = [regex]::Match(
+ $decodedLink,
+ 'https://www\.microsoft\.com(?:/en-us)?/download/details\.aspx\?id=\d+',
+ [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
+ )
+
+ if (-not $downloadCenterMatch.Success) {
+ return $null
+ }
+
+ return ($downloadCenterMatch.Value -replace '/en-us/', '/')
+ }
+
+ function Get-SurfaceDriverModelIndex {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [string]$DriversFolder
+ )
+
+ $url = 'https://learn.microsoft.com/en-us/surface/manage-surface-driver-and-firmware-updates'
+ $minimumExpectedModelCount = 10
+
+ # Load the cached model list first to keep Microsoft model discovery fast.
+ try {
+ $cache = Import-SurfaceDriverIndexCache -DriversFolder $DriversFolder
+ if (@($cache.ModelIndex).Count -gt 0) {
+ WriteLog "Surface cache: Using cached Microsoft model list ($(@($cache.ModelIndex).Count) models)."
+ return @($cache.ModelIndex)
+ }
+ }
+ catch {
+ WriteLog "Surface cache: Failed to load cached Microsoft model list. Falling back to the Learn source. Error: $($_.Exception.Message)"
+ }
+
+ try {
+ # Download the Learn article that now contains the authoritative Surface package table.
+ WriteLog "Surface cache: Downloading Microsoft model index from $url"
+ $headers = @{
+ 'User-Agent' = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
+ }
+ $webContent = Invoke-WebRequest -Uri $url -UseBasicParsing -Headers $headers
+ $html = $webContent.Content
+
+ # Parse each table row and keep only Download Center package links.
+ $rowMatches = [regex]::Matches($html, ']*>(.*?)
', [System.Text.RegularExpressions.RegexOptions]::Singleline)
+ $models = [System.Collections.Generic.List[pscustomobject]]::new()
+ $seenModelKeys = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
+
+ foreach ($rowMatch in $rowMatches) {
+ $rowContent = $rowMatch.Groups[1].Value
+ $cellMatches = [regex]::Matches($rowContent, ']*>\s*(.*?)\s* | ', [System.Text.RegularExpressions.RegexOptions]::Singleline)
+ if ($cellMatches.Count -lt 2) {
+ continue
+ }
+
+ $rowLabel = ConvertTo-SurfaceHtmlText -HtmlFragment $cellMatches[0].Groups[1].Value
+ if ([string]::IsNullOrWhiteSpace($rowLabel) -or $rowLabel -notmatch '(?i)^Surface') {
+ continue
+ }
+
+ $downloadCellContent = $cellMatches[1].Groups[1].Value
+ $linkMatches = [regex]::Matches(
+ $downloadCellContent,
+ ']+href="([^"]+)"[^>]*>(.*?)',
+ [System.Text.RegularExpressions.RegexOptions]::Singleline -bor [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
+ )
+
+ foreach ($linkMatch in $linkMatches) {
+ $modelName = ConvertTo-SurfaceHtmlText -HtmlFragment $linkMatch.Groups[2].Value
+ $modelLink = ConvertTo-SurfaceDownloadCenterLink -LinkValue $linkMatch.Groups[1].Value
+
+ if ([string]::IsNullOrWhiteSpace($modelName) -or [string]::IsNullOrWhiteSpace($modelLink)) {
+ continue
+ }
+
+ $modelKey = "$modelName`n$modelLink"
+ if (-not $seenModelKeys.Add($modelKey)) {
+ continue
+ }
+
+ $models.Add([pscustomobject]@{
+ Make = 'Microsoft'
+ Model = $modelName
+ Link = $modelLink
+ })
+ }
+ }
+
+ if ($models.Count -eq 0) {
+ throw "No Microsoft driver models were found in the Learn table."
+ }
+
+ if ($models.Count -lt $minimumExpectedModelCount) {
+ WriteLog "Surface cache: Warning - Learn parsing returned only $($models.Count) Microsoft model entries."
+ }
+ else {
+ WriteLog "Surface cache: Parsed $($models.Count) Microsoft model entries from Learn."
+ }
+
+ # Save the refreshed model list into the shared cache for both UI and CLI use.
+ try {
+ $cache = Import-SurfaceDriverIndexCache -DriversFolder $DriversFolder
+ $cache.ModelIndex = @($models)
+ Save-SurfaceDriverIndexCache -Cache $cache -DriversFolder $DriversFolder
+ WriteLog "Surface cache: Saved Microsoft model list to cache."
+ }
+ catch {
+ WriteLog "Surface cache: Failed to save Microsoft model list. Error: $($_.Exception.Message)"
+ }
+
+ return @($models)
+ }
+ catch {
+ WriteLog "Surface cache: Failed to build Microsoft model list from Learn. Error: $($_.Exception.Message)"
+
+ # Fall back to the last cached model list even if it is stale when the live request fails.
+ try {
+ $cachePath = Get-SurfaceDriverIndexCachePath -DriversFolder $DriversFolder
+ if (Test-Path -Path $cachePath -PathType Leaf) {
+ $staleCache = Get-Content -Path $cachePath -Raw | ConvertFrom-Json -ErrorAction Stop
+ if (@($staleCache.ModelIndex).Count -gt 0) {
+ WriteLog "Surface cache: Using stale Microsoft model list ($(@($staleCache.ModelIndex).Count) models) because the live Learn request failed."
+ return @($staleCache.ModelIndex)
+ }
+ }
+ }
+ catch {
+ WriteLog "Surface cache: Failed to load stale Microsoft model list fallback. Error: $($_.Exception.Message)"
+ }
+
+ throw "Failed to retrieve Microsoft Surface models."
+ }
+ }
+
+ function Get-SurfaceSystemSkuReferenceIndex {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
@@ -586,6 +755,9 @@ Export-ModuleMember -Function `
Import-SurfaceDriverIndexCache, `
Save-SurfaceDriverIndexCache, `
ConvertTo-SurfaceComparableName, `
+ ConvertTo-SurfaceHtmlText, `
+ ConvertTo-SurfaceDownloadCenterLink, `
+ Get-SurfaceDriverModelIndex, `
Get-SurfaceSystemSkuReferenceIndex, `
Get-SurfaceDownloadCenterDetails, `
Get-SurfaceSystemSkuListForMicrosoftDriver
\ No newline at end of file
diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Microsoft.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Microsoft.psm1
index 3fc216b..cce2a95 100644
--- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Microsoft.psm1
+++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Microsoft.psm1
@@ -15,100 +15,8 @@ function Get-MicrosoftDriversModelList {
[string]$DriversFolder
)
- $url = "https://support.microsoft.com/en-us/surface/download-drivers-and-firmware-for-surface-09bb2e09-2a4b-cb69-0951-078a7739e120"
- $models = @()
-
- # Load cached model list first (Source B) to keep the UI fast.
- # The cache is refreshed automatically when missing or invalid.
- try {
- $cachePath = Get-SurfaceDriverIndexCachePath -DriversFolder $DriversFolder
- if (Test-Path -Path $cachePath -PathType Leaf) {
- $cacheAgeDays = ((Get-Date) - (Get-Item -Path $cachePath).LastWriteTime).TotalDays
- if ($cacheAgeDays -lt 7) {
- $cache = Import-SurfaceDriverIndexCache -DriversFolder $DriversFolder
- if ($cache.ModelIndex -and $cache.ModelIndex.Count -gt 0) {
- WriteLog "Surface cache: Using cached Microsoft model list ($($cache.ModelIndex.Count) models)."
- return @($cache.ModelIndex)
- }
- }
- }
- }
- catch {
- WriteLog "Surface cache: Failed to load cached Microsoft model list. Falling back to online parse. Error: $($_.Exception.Message)"
- }
-
- try {
- WriteLog "Getting Surface driver information from $url"
- $OriginalVerbosePreference = $VerbosePreference
- $VerbosePreference = 'SilentlyContinue'
- # Use passed-in UserAgent and Headers
- $webContent = Invoke-WebRequest -Uri $url -UseBasicParsing -Headers $Headers -UserAgent $UserAgent
- $VerbosePreference = $OriginalVerbosePreference
- WriteLog "Complete"
-
- WriteLog "Parsing web content for models and download links"
- $html = $webContent.Content
- $divPattern = ']*class="selectable-content-options__option-content(?: ocHidden)?"[^>]*>(.*?)
'
- $divMatches = [regex]::Matches($html, $divPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- foreach ($divMatch in $divMatches) {
- $divContent = $divMatch.Groups[1].Value
- $tablePattern = ''
- $tableMatches = [regex]::Matches($divContent, $tablePattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- foreach ($tableMatch in $tableMatches) {
- $tableContent = $tableMatch.Groups[1].Value
- $rowPattern = ']*>(.*?)
'
- $rowMatches = [regex]::Matches($tableContent, $rowPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- foreach ($rowMatch in $rowMatches) {
- $rowContent = $rowMatch.Groups[1].Value
- $cellPattern = ']*>\s*(?: ]*>)?(.*?)(?: )?\s* | '
- $cellMatches = [regex]::Matches($rowContent, $cellPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
-
- if ($cellMatches.Count -ge 2) {
- $modelName = ([System.Net.WebUtility]::HtmlDecode(($cellMatches[0].Groups[1].Value).Trim()))
- $secondTdContent = $cellMatches[1].Groups[1].Value.Trim()
- # $linkPattern = ']+href="([^"]+)"[^>]*>'
- # Change linkPattern to match https://www.microsoft.com/download/details.aspx?id=
- $linkPattern = ']+href="(https://www\.microsoft\.com/download/details\.aspx\?id=\d+)"[^>]*>'
- $linkMatch = [regex]::Match($secondTdContent, $linkPattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
-
- if ($linkMatch.Success) {
- $modelLink = $linkMatch.Groups[1].Value
- }
- else {
- continue
- }
-
- $models += [PSCustomObject]@{
- Make = 'Microsoft'
- Model = $modelName
- Link = $modelLink
- }
- }
- }
- }
- }
- WriteLog "Parsing complete. Found $($models.Count) models."
-
- # Persist model list (Source B) into the local cache for fast UI population.
- try {
- $cache = Import-SurfaceDriverIndexCache -DriversFolder $DriversFolder
- $cache.ModelIndex = @($models)
- Save-SurfaceDriverIndexCache -Cache $cache -DriversFolder $DriversFolder
- WriteLog "Surface cache: Saved Microsoft model list to cache."
- }
- catch {
- WriteLog "Surface cache: Failed to save Microsoft model list. Error: $($_.Exception.Message)"
- }
-
- return $models
- }
- catch {
- WriteLog "Error getting Microsoft models: $($_.Exception.Message)"
- throw "Failed to retrieve Microsoft Surface models."
- }
+ # Keep the UI signature unchanged while using the shared Learn-based source.
+ return @(Get-SurfaceDriverModelIndex -DriversFolder $DriversFolder)
}
# Function to download and extract drivers for a specific Microsoft model (Modified for ForEach-Object -Parallel)
function Save-MicrosoftDriversTask {