mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Completely refactored Dell driver downloads
- Client OSes will now use CatalogIndexPC.xml to identify which ProductLine_SystemID.xml to use to identify which drivers to download. This is inline with how DCU works. - In the UI, Dell Model names now show the full product line, model number, and system ID in the model column. - There are many more models now shown due to breaking each model out by systemID (one model will have many systemIDs). - Downloads per model should be much smaller as prior code was downloading drivers for models that Dell had reused their model number (e.g. Precision/Inspiron/Latitude/Vostro 3520 would result in a very large driver download) - Dell driver downloads are best effort based on the data from the XML files. In some cases the Dell support website may show a newer driver than what is downloaded. This is rare, but in testing I've seen one or two drivers per model where the XML doesn't have what's listed on Dell's website. Again, rare, but not unexpected.
This commit is contained in:
+89
-183
@@ -1428,128 +1428,121 @@ function Get-LenovoDrivers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Get-DellDrivers {
|
function Get-DellDrivers {
|
||||||
param (
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$Model,
|
[string]$Model,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[ValidateSet("x64", "x86", "ARM64")]
|
[ValidateSet('x64','x86','ARM64')]
|
||||||
[string]$WindowsArch,
|
[string]$WindowsArch,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[int]$WindowsRelease
|
[int]$WindowsRelease
|
||||||
)
|
)
|
||||||
|
|
||||||
if (-not (Test-Path -Path $DriversFolder)) {
|
if (-not (Test-Path -Path $DriversFolder)) {
|
||||||
WriteLog "Creating Drivers folder: $DriversFolder"
|
|
||||||
New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null
|
New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null
|
||||||
WriteLog "Drivers folder created"
|
}
|
||||||
|
$DriversFolder = Join-Path $DriversFolder $Make
|
||||||
|
if (-not (Test-Path $DriversFolder)) {
|
||||||
|
New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
$DriversFolder = "$DriversFolder\$Make"
|
# Client pathway (<=11): use CatalogIndexPC + per-model cab.
|
||||||
WriteLog "Creating Dell Drivers folder: $DriversFolder"
|
|
||||||
New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null
|
|
||||||
WriteLog "Dell Drivers folder created"
|
|
||||||
|
|
||||||
#CatalogPC.cab is the catalog for Windows client PCs, Catalog.cab is the catalog for Windows Server
|
|
||||||
if ($WindowsRelease -le 11) {
|
if ($WindowsRelease -le 11) {
|
||||||
$catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab"
|
$indexXml = Get-DellCatalogIndex -DriversFolder (Split-Path $DriversFolder -Parent)
|
||||||
$DellCabFile = "$DriversFolder\CatalogPC.cab"
|
$allModels = Get-DellClientModels -CatalogIndexXmlPath $indexXml
|
||||||
$DellCatalogXML = "$DriversFolder\CatalogPC.XML"
|
$target = $allModels | Where-Object { $_.ModelDisplay -eq $Model }
|
||||||
|
if (-not $target) { throw "Requested Dell model '$Model' not found in index." }
|
||||||
|
|
||||||
|
$cabUrl = $target.CabUrl
|
||||||
|
$modelCabName = [IO.Path]::GetFileName($cabUrl)
|
||||||
|
$modelCabPath = Join-Path $DriversFolder $modelCabName
|
||||||
|
$modelXmlPath = Join-Path $DriversFolder ($modelCabName -replace '\.cab$','.xml')
|
||||||
|
|
||||||
|
if (Test-Path $modelCabPath) { Remove-Item $modelCabPath -Force -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path $modelXmlPath) { Remove-Item $modelXmlPath -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
|
Start-BitsTransferWithRetry -Source $cabUrl -Destination $modelCabPath
|
||||||
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$modelCabPath"" ""$modelXmlPath""" | Out-Null
|
||||||
|
Remove-Item $modelCabPath -Force -ErrorAction SilentlyContinue
|
||||||
|
if (-not (Test-Path $modelXmlPath)) { throw "Failed to extract model cab XML: $modelXmlPath" }
|
||||||
|
|
||||||
|
$pkgs = Get-DellLatestDriverPackages -ModelXmlPath $modelXmlPath -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease
|
||||||
|
if (-not $pkgs) {
|
||||||
|
WriteLog "No drivers found for '$Model'."
|
||||||
|
return
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
foreach ($pkg in $pkgs) {
|
||||||
|
$categorySafe = ($pkg.Category -replace '[\\\/\:\*\?\"\<\>\| ]','_')
|
||||||
|
$downloadFolder = Join-Path $DriversFolder (Join-Path $Model $categorySafe)
|
||||||
|
if (-not (Test-Path $downloadFolder)) { New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
$driverFilePath = Join-Path $downloadFolder $pkg.DriverFileName
|
||||||
|
$extractFolder = Join-Path $downloadFolder ($pkg.DriverFileName.TrimEnd($pkg.DriverFileName[-4..-1]))
|
||||||
|
|
||||||
|
if (Test-Path $extractFolder) {
|
||||||
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
if ($sz -gt 1KB) { continue }
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $driverFilePath)) {
|
||||||
|
try { Start-BitsTransferWithRetry -Source $pkg.DownloadUrl -Destination $driverFilePath }
|
||||||
|
catch { WriteLog "Download failed: $($pkg.DownloadUrl) $($_.Exception.Message)"; continue }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $extractFolder)) { New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
|
||||||
|
$arg1 = "/s /e=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
||||||
|
$arg2 = "/s /drivers=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
||||||
|
$ok = $false
|
||||||
|
try {
|
||||||
|
Invoke-Process -FilePath $driverFilePath -ArgumentList $arg1 | Out-Null
|
||||||
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
if ($sz -gt 1KB) { $ok = $true }
|
||||||
|
if (-not $ok) {
|
||||||
|
Remove-Item $extractFolder -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
||||||
|
Invoke-Process -FilePath $driverFilePath -ArgumentList $arg2 | Out-Null
|
||||||
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
if ($sz -gt 1KB) { $ok = $true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
WriteLog "Extraction error: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
if ($ok) { Remove-Item $driverFilePath -Force -ErrorAction SilentlyContinue }
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Server pathway (unchanged legacy)
|
||||||
$catalogUrl = "https://downloads.dell.com/catalog/Catalog.cab"
|
$catalogUrl = "https://downloads.dell.com/catalog/Catalog.cab"
|
||||||
$DellCabFile = "$DriversFolder\Catalog.cab"
|
$DellCabFile = Join-Path $DriversFolder 'Catalog.cab'
|
||||||
$DellCatalogXML = "$DriversFolder\Catalog.xml"
|
$DellCatalogXML = Join-Path $DriversFolder 'Catalog.xml'
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Url -Url $catalogUrl)) {
|
|
||||||
WriteLog "Dell Catalog cab URL is not accessible: $catalogUrl Exiting"
|
|
||||||
if ($VerbosePreference -ne 'Continue') {
|
|
||||||
Write-Host "Dell Catalog cab URL is not accessible: $catalogUrl Exiting"
|
|
||||||
}
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Downloading Dell Catalog cab file: $catalogUrl to $DellCabFile"
|
|
||||||
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $DellCabFile
|
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $DellCabFile
|
||||||
WriteLog "Dell Catalog cab file downloaded"
|
|
||||||
|
|
||||||
WriteLog "Extracting Dell Catalog cab file to $DellCatalogXML"
|
|
||||||
Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML" | Out-Null
|
Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML" | Out-Null
|
||||||
WriteLog "Dell Catalog cab file extracted"
|
|
||||||
|
|
||||||
$xmlContent = [xml](Get-Content -Path $DellCatalogXML)
|
$xmlContent = [xml](Get-Content -Path $DellCatalogXML)
|
||||||
$baseLocation = "https://" + $xmlContent.manifest.baseLocation + "/"
|
$baseLocation = "https://" + $xmlContent.manifest.baseLocation + "/"
|
||||||
$latestDrivers = @{}
|
$latestDrivers = @{}
|
||||||
|
$softwareComponents = $xmlContent.Manifest.SoftwareComponent | Where-Object { $_.ComponentType.value -eq 'DRVR' }
|
||||||
|
|
||||||
$softwareComponents = $xmlContent.Manifest.SoftwareComponent | Where-Object { $_.ComponentType.value -eq "DRVR" }
|
|
||||||
foreach ($component in $softwareComponents) {
|
foreach ($component in $softwareComponents) {
|
||||||
$models = $component.SupportedSystems.Brand.Model
|
$models = $component.SupportedSystems.Brand.Model
|
||||||
foreach ($item in $models) {
|
foreach ($item in $models) {
|
||||||
if ($item.Display.'#cdata-section' -match $Model) {
|
if ($item.Display.'#cdata-section' -match $Model) {
|
||||||
|
|
||||||
if ($WindowsRelease -le 11) {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch }
|
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch }
|
||||||
}
|
if (-not $validOS) { continue }
|
||||||
elseif ($WindowsRelease -eq 2016) {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W14") }
|
|
||||||
}
|
|
||||||
elseif ($WindowsRelease -eq 2019) {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W19") }
|
|
||||||
}
|
|
||||||
elseif ($WindowsRelease -eq 2022) {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") }
|
|
||||||
}
|
|
||||||
elseif ($WindowsRelease -eq 2025) {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W25") }
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") }
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validOS) {
|
|
||||||
$driverPath = $component.path
|
$driverPath = $component.path
|
||||||
$downloadUrl = $baseLocation + $driverPath
|
$downloadUrl = $baseLocation + $driverPath
|
||||||
$driverFileName = [System.IO.Path]::GetFileName($driverPath)
|
$driverFileName = [System.IO.Path]::GetFileName($driverPath)
|
||||||
$name = $component.Name.Display.'#cdata-section'
|
$name = $component.Name.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_' -replace '[\,]','-'
|
||||||
$name = $name -replace '[\\\/\:\*\?\"\<\>\| ]', '_'
|
$category = $component.Category.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_'
|
||||||
$name = $name -replace '[\,]', '-'
|
|
||||||
$category = $component.Category.Display.'#cdata-section'
|
|
||||||
$category = $category -replace '[\\\/\:\*\?\"\<\>\| ]', '_'
|
|
||||||
$version = [version]$component.vendorVersion
|
$version = [version]$component.vendorVersion
|
||||||
$namePrefix = ($name -split '-')[0]
|
$namePrefix = ($name -split '-')[0]
|
||||||
|
if (-not $latestDrivers[$category]) { $latestDrivers[$category] = @{} }
|
||||||
# Use hash table to store the latest driver for each category to prevent downloading older driver versions
|
if (-not $latestDrivers[$category][$namePrefix] -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
|
||||||
if ($latestDrivers[$category]) {
|
$latestDrivers[$category][$namePrefix] = [pscustomobject]@{
|
||||||
if ($latestDrivers[$category][$namePrefix]) {
|
Name = $name; DownloadUrl = $downloadUrl; DriverFileName = $driverFileName; Version = $version; Category = $category
|
||||||
if ($latestDrivers[$category][$namePrefix].Version -lt $version) {
|
|
||||||
$latestDrivers[$category][$namePrefix] = [PSCustomObject]@{
|
|
||||||
Name = $name;
|
|
||||||
DownloadUrl = $downloadUrl;
|
|
||||||
DriverFileName = $driverFileName;
|
|
||||||
Version = $version;
|
|
||||||
Category = $category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$latestDrivers[$category][$namePrefix] = [PSCustomObject]@{
|
|
||||||
Name = $name;
|
|
||||||
DownloadUrl = $downloadUrl;
|
|
||||||
DriverFileName = $driverFileName;
|
|
||||||
Version = $version;
|
|
||||||
Category = $category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$latestDrivers[$category] = @{}
|
|
||||||
$latestDrivers[$category][$namePrefix] = [PSCustomObject]@{
|
|
||||||
Name = $name;
|
|
||||||
DownloadUrl = $downloadUrl;
|
|
||||||
DriverFileName = $driverFileName;
|
|
||||||
Version = $version;
|
|
||||||
Category = $category
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1558,102 +1551,15 @@ function Get-DellDrivers {
|
|||||||
|
|
||||||
foreach ($category in $latestDrivers.Keys) {
|
foreach ($category in $latestDrivers.Keys) {
|
||||||
foreach ($driver in $latestDrivers[$category].Values) {
|
foreach ($driver in $latestDrivers[$category].Values) {
|
||||||
$downloadFolder = "$DriversFolder\$Model\$($driver.Category)"
|
$downloadFolder = Join-Path $DriversFolder (Join-Path $Model $driver.Category)
|
||||||
$driverFilePath = Join-Path -Path $downloadFolder -ChildPath $driver.DriverFileName
|
if (-not (Test-Path $downloadFolder)) { New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
$driverFilePath = Join-Path $downloadFolder $driver.DriverFileName
|
||||||
if (Test-Path -Path $driverFilePath) {
|
if (Test-Path $driverFilePath) { continue }
|
||||||
WriteLog "Driver already downloaded: $driverFilePath skipping"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Downloading driver: $($driver.Name)"
|
|
||||||
if (-not (Test-Path -Path $downloadFolder)) {
|
|
||||||
WriteLog "Creating download folder: $downloadFolder"
|
|
||||||
New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null
|
|
||||||
WriteLog "Download folder created"
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Downloading driver: $($driver.DownloadUrl) to $driverFilePath"
|
|
||||||
try {
|
|
||||||
Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath
|
|
||||||
Start-BitsTransferWithRetry -Source $driver.DownloadUrl -Destination $driverFilePath
|
Start-BitsTransferWithRetry -Source $driver.DownloadUrl -Destination $driverFilePath
|
||||||
Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath
|
$extractFolder = Join-Path $downloadFolder ($driver.DriverFileName.TrimEnd($driver.DriverFileName[-4..-1]))
|
||||||
WriteLog "Driver downloaded"
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Failed to download driver: $($driver.DownloadUrl) to $driverFilePath"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$extractFolder = $downloadFolder + "\" + $driver.DriverFileName.TrimEnd($driver.DriverFileName[-4..-1])
|
|
||||||
# WriteLog "Creating extraction folder: $extractFolder"
|
|
||||||
# New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
|
||||||
# WriteLog "Extraction folder created"
|
|
||||||
|
|
||||||
# $arguments = "/s /e /f `"$extractFolder`""
|
|
||||||
$arguments = "/s /drivers=`"$extractFolder`""
|
$arguments = "/s /drivers=`"$extractFolder`""
|
||||||
WriteLog "Extracting driver: $driverFilePath $arguments"
|
try { Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null } catch {}
|
||||||
try {
|
Remove-Item $driverFilePath -Force -ErrorAction SilentlyContinue
|
||||||
#If Category is Chipset, must add -wait $false to the Invoke-Process command line to prevent the script from hanging on the Intel chipset driver which leaves a Window open
|
|
||||||
if ($driver.Category -eq "Chipset") {
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false
|
|
||||||
|
|
||||||
#Wait 5 seconds to allow for the extraction process to finish
|
|
||||||
Start-Sleep -Seconds 5
|
|
||||||
|
|
||||||
$childProcesses = Get-ChildProcesses $process.Id
|
|
||||||
|
|
||||||
# Find and stop the last created child process
|
|
||||||
if ($childProcesses) {
|
|
||||||
$latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1
|
|
||||||
Stop-Process -Id $latestProcess.ProcessId -Force
|
|
||||||
# Sleep 1 second to let process finish exiting so its installer can be removed
|
|
||||||
Start-Sleep -Seconds 1
|
|
||||||
}
|
|
||||||
#If Category is Network and $isServer is $false, must add -wait $false to the Invoke-Process command line to prevent the script from hanging on the Intel network driver which leaves a Window open
|
|
||||||
}
|
|
||||||
elseif ($driver.Category -eq "Network" -and $isServer -eq $false) {
|
|
||||||
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false
|
|
||||||
|
|
||||||
#Sometimes the network drivers will extract on client OS, wait 5 seconds and check if the process is still running
|
|
||||||
Start-Sleep -Seconds 5
|
|
||||||
if ($process.HasExited -eq $false) {
|
|
||||||
$childProcesses = Get-ChildProcesses $process.Id
|
|
||||||
|
|
||||||
# Find and stop the last created child process
|
|
||||||
if ($childProcesses) {
|
|
||||||
$latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1
|
|
||||||
Stop-Process -Id $latestProcess.ProcessId -Force
|
|
||||||
#Move on to the next driver and skip this one - it won't extract on a client OS even with /s /e switches
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null
|
|
||||||
}
|
|
||||||
# If $extractFolder is empty, try alternative extraction method
|
|
||||||
if (!(Get-ChildItem -Path $extractFolder -Recurse | Where-Object { -not $_.PSIsContainer })) {
|
|
||||||
WriteLog 'Extraction with /drivers= switch failed. Removing folder and retrying with /s /e switches'
|
|
||||||
Remove-Item -Path $extractFolder -Force -Recurse -ErrorAction SilentlyContinue
|
|
||||||
$arguments = "/s /e=`"$extractFolder`""
|
|
||||||
WriteLog "Extracting driver: $driverFilePath $arguments"
|
|
||||||
Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog 'Extraction with /drivers= switch failed. Retrying with /s /e switches'
|
|
||||||
$arguments = "/s /e=`"$extractFolder`""
|
|
||||||
WriteLog "Extracting driver: $driverFilePath $arguments"
|
|
||||||
Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null
|
|
||||||
}
|
|
||||||
WriteLog "Driver extracted"
|
|
||||||
|
|
||||||
WriteLog "Deleting driver file: $driverFilePath"
|
|
||||||
Remove-Item -Path $driverFilePath -Force
|
|
||||||
WriteLog "Driver file deleted"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ function Start-BitsTransferWithRetry {
|
|||||||
$VerbosePreference = 'SilentlyContinue'
|
$VerbosePreference = 'SilentlyContinue'
|
||||||
$ProgressPreference = 'SilentlyContinue'
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
Start-BitsTransfer -Source $Source -Destination $Destination -ErrorAction Stop
|
Start-BitsTransfer -Source $Source -Destination $Destination -Priority Normal -ErrorAction Stop
|
||||||
|
|
||||||
$ProgressPreference = $OriginalProgressPreference
|
$ProgressPreference = $OriginalProgressPreference
|
||||||
$VerbosePreference = $OriginalVerbosePreference
|
$VerbosePreference = $OriginalVerbosePreference
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Common Dell driver helpers (catalog index, model listing, latest package selection).
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Convert-DellVendorVersion {
|
||||||
|
param([Parameter(Mandatory=$true)][string]$VendorVersion)
|
||||||
|
$segments = $VendorVersion.Split('.') | ForEach-Object {
|
||||||
|
if ($_ -match '^\d+$') { [int]$_ } else { 0 }
|
||||||
|
}
|
||||||
|
return ,$segments
|
||||||
|
}
|
||||||
|
|
||||||
|
function Compare-DellVendorVersion {
|
||||||
|
param(
|
||||||
|
[int[]]$Left,
|
||||||
|
[int[]]$Right
|
||||||
|
)
|
||||||
|
$len = [Math]::Max($Left.Length,$Right.Length)
|
||||||
|
for ($i=0; $i -lt $len; $i++) {
|
||||||
|
$l = if ($i -lt $Left.Length) { $Left[$i] } else { 0 }
|
||||||
|
$r = if ($i -lt $Right.Length) { $Right[$i] } else { 0 }
|
||||||
|
if ($l -gt $r) { return 1 }
|
||||||
|
if ($l -lt $r) { return -1 }
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DellCatalogIndex {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$DriversFolder
|
||||||
|
)
|
||||||
|
|
||||||
|
$dellFolder = Join-Path $DriversFolder 'Dell'
|
||||||
|
if (-not (Test-Path $dellFolder)) { New-Item -Path $dellFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
$cabPath = Join-Path $dellFolder 'CatalogIndexPC.cab'
|
||||||
|
$xmlPath = Join-Path $dellFolder 'CatalogIndexPC.xml'
|
||||||
|
$url = 'https://downloads.dell.com/catalog/CatalogIndexPC.cab'
|
||||||
|
|
||||||
|
$need = $true
|
||||||
|
if (Test-Path $xmlPath) {
|
||||||
|
$ageDays = ((Get-Date) - (Get-Item $xmlPath).CreationTime).TotalDays
|
||||||
|
if ($ageDays -lt 7) { $need = $false }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($need) {
|
||||||
|
if (Test-Path $cabPath) { Remove-Item $cabPath -Force -ErrorAction SilentlyContinue }
|
||||||
|
if (Test-Path $xmlPath) { Remove-Item $xmlPath -Force -ErrorAction SilentlyContinue }
|
||||||
|
Start-BitsTransferWithRetry -Source $url -Destination $cabPath
|
||||||
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$cabPath"" ""$xmlPath""" | Out-Null
|
||||||
|
Remove-Item $cabPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $xmlPath)) { throw "Dell CatalogIndexPC XML missing: $xmlPath" }
|
||||||
|
return $xmlPath
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DellClientModels {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$CatalogIndexXmlPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$settings = New-Object System.Xml.XmlReaderSettings
|
||||||
|
$settings.IgnoreWhitespace = $true
|
||||||
|
$settings.IgnoreComments = $true
|
||||||
|
$reader = [System.Xml.XmlReader]::Create($CatalogIndexXmlPath,$settings)
|
||||||
|
|
||||||
|
$models = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||||
|
try {
|
||||||
|
while ($reader.Read()) {
|
||||||
|
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq 'GroupManifest') {
|
||||||
|
# Read subtree to pick out brand/model/systemID + path
|
||||||
|
$sub = $reader.ReadSubtree()
|
||||||
|
$doc = New-Object System.Xml.XmlDocument
|
||||||
|
$doc.Load($sub)
|
||||||
|
$sub.Dispose()
|
||||||
|
|
||||||
|
# Use local-name() to ignore namespaces
|
||||||
|
$brandNode = $doc.SelectSingleNode("//*[local-name()='SupportedSystems']/*[local-name()='Brand']")
|
||||||
|
if (-not $brandNode) { continue }
|
||||||
|
$brandDisplay = ($brandNode.SelectSingleNode("*[local-name()='Display']")?.InnerText).Trim()
|
||||||
|
$modelNode = $brandNode.SelectSingleNode("*[local-name()='Model']")
|
||||||
|
if (-not $modelNode) { continue }
|
||||||
|
$modelNumber = ($modelNode.SelectSingleNode("*[local-name()='Display']")?.InnerText).Trim()
|
||||||
|
$systemId = $modelNode.GetAttribute('systemID')
|
||||||
|
$manifestInfo = $doc.SelectSingleNode("//*[local-name()='ManifestInformation']")
|
||||||
|
if (-not $manifestInfo) { continue }
|
||||||
|
$pathAttr = $manifestInfo.GetAttribute('path')
|
||||||
|
if (-not $pathAttr) { continue }
|
||||||
|
$cabUrl = 'https://downloads.dell.com/' + $pathAttr
|
||||||
|
$modelDisplay = "$brandDisplay $modelNumber ($systemId)"
|
||||||
|
$models.Add([pscustomobject]@{
|
||||||
|
Brand = $brandDisplay
|
||||||
|
ModelNumber = $modelNumber
|
||||||
|
SystemId = $systemId
|
||||||
|
CabRelativePath = $pathAttr
|
||||||
|
CabUrl = $cabUrl
|
||||||
|
ModelDisplay = $modelDisplay
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$reader.Dispose()
|
||||||
|
}
|
||||||
|
return $models
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-DellLatestDriverPackages {
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$ModelXmlPath,
|
||||||
|
[Parameter(Mandatory=$true)][string]$WindowsArch,
|
||||||
|
[Parameter(Mandatory=$true)][int]$WindowsRelease
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path $ModelXmlPath)) { throw "Model XML not found: $ModelXmlPath" }
|
||||||
|
|
||||||
|
$xml = [xml](Get-Content -Path $ModelXmlPath -Raw)
|
||||||
|
|
||||||
|
# Collect all SoftwareComponent nodes
|
||||||
|
$components = $xml.SelectNodes("//*[local-name()='SoftwareComponent']")
|
||||||
|
if (-not $components) { return @() }
|
||||||
|
|
||||||
|
$rawPackages = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||||
|
|
||||||
|
foreach ($comp in $components) {
|
||||||
|
$ctype = $comp.SelectSingleNode("*[local-name()='ComponentType']")
|
||||||
|
if (-not $ctype) { continue }
|
||||||
|
if ($ctype.GetAttribute('value') -ne 'DRVR') { continue }
|
||||||
|
|
||||||
|
# OS filtering (arch only – release filtering intentionally minimal for now)
|
||||||
|
$osNodes = @($comp.SelectNodes("*[local-name()='SupportedOperatingSystems']/*[local-name()='OperatingSystem']"))
|
||||||
|
if (-not $osNodes) { continue }
|
||||||
|
$validOS = $osNodes | Where-Object { $_.GetAttribute('osArch') -eq $WindowsArch } | Select-Object -First 1
|
||||||
|
if (-not $validOS) { continue }
|
||||||
|
|
||||||
|
$path = $comp.GetAttribute('path')
|
||||||
|
if (-not $path) { continue }
|
||||||
|
|
||||||
|
$downloadUrl = "https://downloads.dell.com/$path"
|
||||||
|
$fileName = [IO.Path]::GetFileName($path)
|
||||||
|
$vendorVersion = $comp.GetAttribute('vendorVersion')
|
||||||
|
$versionArr = if ($vendorVersion) { Convert-DellVendorVersion $vendorVersion } else { @(0) }
|
||||||
|
$dateTimeAttr = $comp.GetAttribute('dateTime')
|
||||||
|
$dt = Get-Date
|
||||||
|
if ($dateTimeAttr) {
|
||||||
|
try { $dt = [DateTime]::Parse($dateTimeAttr) } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryNode = $comp.SelectSingleNode("*[local-name()='Category']/*[local-name()='Display']")
|
||||||
|
$category = if ($categoryNode) { $categoryNode.InnerText.Trim() } else { 'Uncategorized' }
|
||||||
|
|
||||||
|
# Collect componentIDs (SupportedDevices + SupportedDCHDevices)
|
||||||
|
$compIds = [System.Collections.Generic.List[string]]::new()
|
||||||
|
$devNodes = @($comp.SelectNodes(".//*[local-name()='Device']"))
|
||||||
|
foreach ($dn in $devNodes) {
|
||||||
|
$id = $dn.GetAttribute('componentID')
|
||||||
|
if ($id) { [void]$compIds.Add($id) }
|
||||||
|
}
|
||||||
|
if ($compIds.Count -eq 0) { continue }
|
||||||
|
|
||||||
|
# Build a deterministic sortable key: zero-pad each numeric segment to 6 digits
|
||||||
|
$versionSortable = ($versionArr | ForEach-Object { $_.ToString('D6') }) -join '-'
|
||||||
|
|
||||||
|
$rawPackages.Add([pscustomobject]@{
|
||||||
|
Path = $path
|
||||||
|
DownloadUrl = $downloadUrl
|
||||||
|
FileName = $fileName
|
||||||
|
Category = $category
|
||||||
|
VendorVersion = $vendorVersion
|
||||||
|
VersionArray = $versionArr
|
||||||
|
VersionSortable = $versionSortable
|
||||||
|
DateTime = $dt
|
||||||
|
ComponentIds = $compIds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rawPackages.Count -eq 0) { return @() }
|
||||||
|
|
||||||
|
# Sort newest first by VersionSortable (lexicographic works due to zero padding) then DateTime
|
||||||
|
$sorted = $rawPackages | Sort-Object -Property @{ Expression = { $_.VersionSortable }; Descending = $true }, @{ Expression = { $_.DateTime }; Descending = $true }
|
||||||
|
|
||||||
|
$chosen = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||||
|
$assignedIds = [System.Collections.Generic.HashSet[string]]::new()
|
||||||
|
|
||||||
|
foreach ($pkg in $sorted) {
|
||||||
|
$hasOverlap = $false
|
||||||
|
foreach ($cid in $pkg.ComponentIds) {
|
||||||
|
if ($assignedIds.Contains($cid)) { $hasOverlap = $true; break }
|
||||||
|
}
|
||||||
|
if ($hasOverlap) {
|
||||||
|
WriteLog "Get-DellLatestDriverPackages: Skipping superseded package $($pkg.FileName) (shared componentID with newer package)."
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($cid in $pkg.ComponentIds) { [void]$assignedIds.Add($cid) }
|
||||||
|
|
||||||
|
$chosen.Add([pscustomobject]@{
|
||||||
|
Path = $pkg.Path
|
||||||
|
DownloadUrl = $pkg.DownloadUrl
|
||||||
|
DriverFileName = $pkg.FileName
|
||||||
|
Category = $pkg.Category
|
||||||
|
VendorVersion = $pkg.VendorVersion
|
||||||
|
DateTime = $pkg.DateTime
|
||||||
|
ComponentIds = $pkg.ComponentIds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($chosen.Count -eq 0) {
|
||||||
|
WriteLog "Get-DellLatestDriverPackages: No qualifying driver packages after supersedence."
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLog ("Get-DellLatestDriverPackages: Selected {0} package(s) after supersedence." -f $chosen.Count)
|
||||||
|
return $chosen
|
||||||
|
}
|
||||||
|
|
||||||
|
Export-ModuleMember -Function Convert-DellVendorVersion,Compare-DellVendorVersion,Get-DellCatalogIndex,Get-DellClientModels,Get-DellLatestDriverPackages
|
||||||
@@ -67,6 +67,7 @@ Description = 'Common functions shared between FFU Builder UI and the BuildFFUVM
|
|||||||
|
|
||||||
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
|
||||||
NestedModules = @('FFU.Common.Drivers.psm1',
|
NestedModules = @('FFU.Common.Drivers.psm1',
|
||||||
|
'FFU.Common.Drivers.Dell.psm1',
|
||||||
'FFU.Common.Winget.psm1',
|
'FFU.Common.Winget.psm1',
|
||||||
'FFU.Common.Parallel.psm1',
|
'FFU.Common.Parallel.psm1',
|
||||||
'FFU.Common.Cleanup.psm1')
|
'FFU.Common.Cleanup.psm1')
|
||||||
|
|||||||
@@ -12,147 +12,98 @@ function Get-DellDriversModelList {
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[int]$WindowsRelease,
|
[int]$WindowsRelease,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$DriversFolder, # Base drivers folder (e.g., C:\FFUDevelopment\Drivers)
|
[string]$DriversFolder,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$Make # Should be 'Dell'
|
[string]$Make
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define Dell specific drivers folder and catalog file names
|
# Client pathway (<=11) uses CatalogIndexPC to build full Brand Model (SystemID) strings.
|
||||||
|
if ($WindowsRelease -le 11) {
|
||||||
|
$dellModels = Get-DellClientModels -CatalogIndexXmlPath (Get-DellCatalogIndex -DriversFolder $DriversFolder)
|
||||||
|
$final = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||||
|
foreach ($m in $dellModels) {
|
||||||
|
$final.Add([pscustomobject]@{
|
||||||
|
Make = $Make
|
||||||
|
Model = $m.ModelDisplay
|
||||||
|
Brand = $m.Brand
|
||||||
|
ModelNumber = $m.ModelNumber
|
||||||
|
SystemId = $m.SystemId
|
||||||
|
CabRelativePath = $m.CabRelativePath
|
||||||
|
CabUrl = $m.CabUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return $final
|
||||||
|
}
|
||||||
|
|
||||||
|
# Server pathway (unchanged – still uses Catalog.cab)
|
||||||
$dellDriversFolder = Join-Path -Path $DriversFolder -ChildPath "Dell"
|
$dellDriversFolder = Join-Path -Path $DriversFolder -ChildPath "Dell"
|
||||||
$catalogBaseName = if ($WindowsRelease -le 11) { "CatalogPC" } else { "Catalog" }
|
$catalogBaseName = "Catalog"
|
||||||
$dellCabFile = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).cab"
|
$dellCabFile = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).cab"
|
||||||
$dellCatalogXML = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).xml"
|
$dellCatalogXML = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).xml"
|
||||||
$catalogUrl = if ($WindowsRelease -le 11) { "http://downloads.dell.com/catalog/CatalogPC.cab" } else { "https://downloads.dell.com/catalog/Catalog.cab" }
|
$catalogUrl = "https://downloads.dell.com/catalog/Catalog.cab"
|
||||||
|
|
||||||
$uniqueModelNames = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
if (-not (Test-Path -Path $dellDriversFolder)) {
|
||||||
$reader = $null
|
|
||||||
|
|
||||||
try {
|
|
||||||
# Check if the Dell catalog XML exists and is recent
|
|
||||||
$downloadCatalog = $true
|
|
||||||
if (Test-Path -Path $dellCatalogXML -PathType Leaf) {
|
|
||||||
WriteLog "Dell Catalog XML found: $dellCatalogXML"
|
|
||||||
$dellCatalogCreationTime = (Get-Item $dellCatalogXML).CreationTime
|
|
||||||
WriteLog "Dell Catalog XML Creation time: $dellCatalogCreationTime"
|
|
||||||
# Check if the XML file is less than 7 days old
|
|
||||||
if (((Get-Date) - $dellCatalogCreationTime).TotalDays -lt 7) {
|
|
||||||
WriteLog "Using existing Dell Catalog XML (less than 7 days old): $dellCatalogXML"
|
|
||||||
$downloadCatalog = $false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "Existing Dell Catalog XML is older than 7 days: $dellCatalogXML"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "Dell Catalog XML not found: $dellCatalogXML"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($downloadCatalog) {
|
|
||||||
WriteLog "Attempting to download and extract Dell Catalog for Get-DellDriversModelList..."
|
|
||||||
# Ensure Dell drivers folder exists
|
|
||||||
if (-not (Test-Path -Path $dellDriversFolder -PathType Container)) {
|
|
||||||
WriteLog "Creating Dell drivers folder: $dellDriversFolder"
|
|
||||||
New-Item -Path $dellDriversFolder -ItemType Directory -Force | Out-Null
|
New-Item -Path $dellDriversFolder -ItemType Directory -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check URL accessibility
|
$download = $true
|
||||||
try {
|
if (Test-Path -Path $dellCatalogXML) {
|
||||||
$request = [System.Net.WebRequest]::Create($catalogUrl)
|
if (((Get-Date) - (Get-Item $dellCatalogXML).CreationTime).TotalDays -lt 7) {
|
||||||
$request.Method = 'HEAD'; $response = $request.GetResponse(); $response.Close()
|
$download = $false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch { throw "Dell Catalog URL '$catalogUrl' not accessible: $($_.Exception.Message)" }
|
|
||||||
|
|
||||||
# Remove existing files before download if they exist
|
if ($download) {
|
||||||
if (Test-Path -Path $dellCabFile) { Remove-Item -Path $dellCabFile -Force -ErrorAction SilentlyContinue }
|
if (Test-Path $dellCabFile) { Remove-Item $dellCabFile -Force -ErrorAction SilentlyContinue }
|
||||||
if (Test-Path -Path $dellCatalogXML) { Remove-Item -Path $dellCatalogXML -Force -ErrorAction SilentlyContinue }
|
if (Test-Path $dellCatalogXML) { Remove-Item $dellCatalogXML -Force -ErrorAction SilentlyContinue }
|
||||||
|
|
||||||
WriteLog "Downloading Dell Catalog cab file: $catalogUrl to $dellCabFile"
|
|
||||||
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $dellCabFile
|
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $dellCabFile
|
||||||
WriteLog "Dell Catalog cab file downloaded to $dellCabFile"
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$dellCabFile"" ""$dellCatalogXML""" | Out-Null
|
||||||
|
Remove-Item $dellCabFile -Force -ErrorAction SilentlyContinue
|
||||||
WriteLog "Extracting Dell Catalog cab file '$dellCabFile' to '$dellCatalogXML'"
|
|
||||||
Invoke-Process -FilePath "Expand.exe" -ArgumentList """$dellCabFile"" ""$dellCatalogXML""" | Out-Null
|
|
||||||
WriteLog "Dell Catalog cab file extracted to $dellCatalogXML"
|
|
||||||
|
|
||||||
# Delete the CAB file after extraction
|
|
||||||
WriteLog "Deleting Dell Catalog CAB file: $dellCabFile"
|
|
||||||
Remove-Item -Path $dellCabFile -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure the XML file exists before trying to read it
|
if (-not (Test-Path $dellCatalogXML)) { throw "Dell server catalog XML missing: $dellCatalogXML" }
|
||||||
if (-not (Test-Path -Path $dellCatalogXML -PathType Leaf)) {
|
|
||||||
throw "Dell Catalog XML file '$dellCatalogXML' not found after download/check attempt."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use XmlReader for streaming from the XML file
|
|
||||||
$settings = New-Object System.Xml.XmlReaderSettings
|
$settings = New-Object System.Xml.XmlReaderSettings
|
||||||
$settings.IgnoreWhitespace = $true
|
$settings.IgnoreWhitespace = $true
|
||||||
$settings.IgnoreComments = $true
|
$settings.IgnoreComments = $true
|
||||||
# $settings.DtdProcessing = [System.Xml.DtdProcessing]::Ignore # Optional
|
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML,$settings)
|
||||||
|
$inDriver = $false
|
||||||
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML, $settings)
|
$inModel = $false
|
||||||
WriteLog "Starting XML stream parsing for Dell models from '$dellCatalogXML'..."
|
$depthModel = -1
|
||||||
|
$modelsHash = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
||||||
$isDriverComponent = $false
|
try {
|
||||||
$isModelElement = $false
|
|
||||||
$modelDepth = -1 # Track depth to handle nested elements if needed
|
|
||||||
|
|
||||||
# Read through the XML stream node by node
|
|
||||||
while ($reader.Read()) {
|
while ($reader.Read()) {
|
||||||
switch ($reader.NodeType) {
|
switch ($reader.NodeType) {
|
||||||
([System.Xml.XmlNodeType]::Element) {
|
([System.Xml.XmlNodeType]::Element) {
|
||||||
switch ($reader.Name) {
|
switch ($reader.Name) {
|
||||||
'SoftwareComponent' { $isDriverComponent = $false } # Reset flag
|
'SoftwareComponent' { $inDriver = $false }
|
||||||
'ComponentType' { if ($reader.GetAttribute('value') -eq 'DRVR') { $isDriverComponent = $true } }
|
'ComponentType' { if ($reader.GetAttribute('value') -eq 'DRVR') { $inDriver = $true } }
|
||||||
'Model' { if ($isDriverComponent) { $isModelElement = $true; $modelDepth = $reader.Depth } }
|
'Model' { if ($inDriver) { $inModel = $true; $depthModel = $reader.Depth } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
([System.Xml.XmlNodeType]::CDATA) {
|
([System.Xml.XmlNodeType]::CDATA) {
|
||||||
if ($isModelElement -and $isDriverComponent) {
|
if ($inDriver -and $inModel) {
|
||||||
$modelName = $reader.Value.Trim()
|
$val = $reader.Value.Trim()
|
||||||
if (-not [string]::IsNullOrWhiteSpace($modelName)) { $uniqueModelNames.Add($modelName) | Out-Null }
|
if ($val) { $modelsHash.Add($val) | Out-Null }
|
||||||
$isModelElement = $false # Reset after reading CDATA
|
$inModel = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
([System.Xml.XmlNodeType]::EndElement) {
|
([System.Xml.XmlNodeType]::EndElement) {
|
||||||
switch ($reader.Name) {
|
if ($reader.Name -eq 'SoftwareComponent') { $inDriver = $false; $inModel = $false }
|
||||||
'SoftwareComponent' { $isDriverComponent = $false; $isModelElement = $false; $modelDepth = -1 }
|
elseif ($reader.Name -eq 'Model' -and $reader.Depth -eq $depthModel) { $inModel = $false; $depthModel = -1 }
|
||||||
'Model' { if ($reader.Depth -eq $modelDepth) { $isModelElement = $false; $modelDepth = -1 } }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} # End while ($reader.Read())
|
|
||||||
|
|
||||||
WriteLog "Finished XML stream parsing. Found $($uniqueModelNames.Count) unique Dell models."
|
|
||||||
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Error getting Dell models: $($_.Exception.ToString())" # Log full exception
|
|
||||||
throw "Failed to retrieve Dell models. Check log for details." # Re-throw for UI handling
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
# Ensure the reader is closed and disposed
|
|
||||||
if ($null -ne $reader) {
|
|
||||||
$reader.Dispose()
|
$reader.Dispose()
|
||||||
}
|
}
|
||||||
# Ensure CAB file is deleted even if extraction failed but download succeeded
|
|
||||||
if (Test-Path -Path $dellCabFile) {
|
|
||||||
WriteLog "Cleaning up downloaded Dell CAB file: $dellCabFile"
|
|
||||||
Remove-Item -Path $dellCabFile -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Convert HashSet to sorted list of PSCustomObjects
|
$out = [System.Collections.Generic.List[pscustomobject]]::new()
|
||||||
$models = [System.Collections.Generic.List[PSCustomObject]]::new()
|
foreach ($nm in ($modelsHash | Sort-Object)) {
|
||||||
foreach ($modelName in ($uniqueModelNames | Sort-Object)) {
|
$out.Add([pscustomobject]@{ Make = $Make; Model = $nm })
|
||||||
$models.Add([PSCustomObject]@{
|
|
||||||
Make = $Make
|
|
||||||
Model = $modelName
|
|
||||||
# Link is not applicable here like for Microsoft
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return $out
|
||||||
return $models
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to download and extract drivers for a specific Dell model (Modified for ForEach-Object -Parallel)
|
# Function to download and extract drivers for a specific Dell model (Modified for ForEach-Object -Parallel)
|
||||||
@@ -160,552 +111,281 @@ function Save-DellDriversTask {
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[PSCustomObject]$DriverItemData, # Contains Model property
|
[pscustomobject]$DriverItemData,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$DriversFolder, # Base drivers folder (e.g., C:\FFUDevelopment\Drivers)
|
[string]$DriversFolder,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$WindowsArch,
|
[string]$WindowsArch,
|
||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[int]$WindowsRelease,
|
[int]$WindowsRelease,
|
||||||
[Parameter()]
|
[Parameter()]
|
||||||
[System.Collections.Concurrent.ConcurrentQueue[hashtable]]$ProgressQueue = $null, # Default to null
|
[System.Collections.Concurrent.ConcurrentQueue[hashtable]]$ProgressQueue = $null,
|
||||||
[Parameter()]
|
[Parameter()]
|
||||||
[bool]$CompressToWim = $false, # New parameter for compression
|
[bool]$CompressToWim = $false,
|
||||||
[Parameter()]
|
[Parameter()]
|
||||||
[bool]$PreserveSourceOnCompress = $false
|
[bool]$PreserveSourceOnCompress = $false
|
||||||
)
|
)
|
||||||
|
|
||||||
$modelName = $DriverItemData.Model
|
$modelDisplay = $DriverItemData.Model
|
||||||
$make = "Dell" # Hardcoded for this task
|
$make = 'Dell'
|
||||||
$status = "Starting..." # Initial local status
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Checking...' }
|
||||||
$success = $false
|
|
||||||
|
|
||||||
# Initial status update
|
$sanitizedModelName = ConvertTo-SafeName -Name $modelDisplay
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status "Checking..." }
|
$makeDriversPath = Join-Path -Path $DriversFolder -ChildPath $make
|
||||||
|
|
||||||
$sanitizedModelName = ConvertTo-SafeName -Name $modelName
|
|
||||||
if ($sanitizedModelName -ne $modelName) { WriteLog "Sanitized model name: '$modelName' -> '$sanitizedModelName'" }
|
|
||||||
$makeDriversPath = Join-Path -Path $DriversFolder -ChildPath $Make
|
|
||||||
$modelPath = Join-Path -Path $makeDriversPath -ChildPath $sanitizedModelName
|
$modelPath = Join-Path -Path $makeDriversPath -ChildPath $sanitizedModelName
|
||||||
$driverRelativePath = Join-Path -Path $make -ChildPath $sanitizedModelName # Relative path for the driver folder
|
$driverRelativePath = Join-Path -Path $make -ChildPath $sanitizedModelName
|
||||||
|
|
||||||
try {
|
# Helper: safe folder removal
|
||||||
# Check for existing drivers
|
function Remove-SafeFolder {
|
||||||
$existingDriver = Test-ExistingDriver -Make $make -Model $sanitizedModelName -DriversFolder $DriversFolder -Identifier $modelName -ProgressQueue $ProgressQueue
|
param([string]$Path)
|
||||||
if ($null -ne $existingDriver) {
|
if ([string]::IsNullOrWhiteSpace($Path)) { return }
|
||||||
# Add the 'Model' property to the return object for consistency if it's not there
|
# Never allow deleting the entire Dell root folder accidentally
|
||||||
if (-not $existingDriver.PSObject.Properties['Model']) {
|
$dellRoot = (Resolve-Path $makeDriversPath).ProviderPath
|
||||||
$existingDriver | Add-Member -MemberType NoteProperty -Name 'Model' -Value $modelName
|
$target = (Resolve-Path $Path -ErrorAction SilentlyContinue)?.ProviderPath
|
||||||
|
if ($null -eq $target) { return }
|
||||||
|
if ($target -eq $dellRoot) { return }
|
||||||
|
if (-not ($target.StartsWith($dellRoot,[System.StringComparison]::OrdinalIgnoreCase))) { return }
|
||||||
|
Remove-Item -Path $target -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
# Special handling for existing folders that need compression
|
|
||||||
if ($CompressToWim -and $existingDriver.Status -eq 'Already downloaded') {
|
|
||||||
$wimFilePath = Join-Path -Path $makeDriversPath -ChildPath "$($sanitizedModelName).wim"
|
|
||||||
$sourceFolderPath = Join-Path -Path $makeDriversPath -ChildPath $sanitizedModelName
|
|
||||||
WriteLog "Attempting compression of existing folder '$sourceFolderPath' to '$wimFilePath'."
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status "Compressing existing..." }
|
|
||||||
try {
|
try {
|
||||||
Compress-DriverFolderToWim -SourceFolderPath $sourceFolderPath -DestinationWimPath $wimFilePath -WimName $modelName -WimDescription "Drivers for $modelName" -PreserveSource:$PreserveSourceOnCompress -ErrorAction Stop
|
# Existing drivers short‑circuit
|
||||||
$existingDriver.Status = "Already downloaded & Compressed"
|
$existing = Test-ExistingDriver -Make $make -Model $sanitizedModelName -DriversFolder $DriversFolder -Identifier $modelDisplay -ProgressQueue $ProgressQueue
|
||||||
$existingDriver.DriverPath = Join-Path -Path $make -ChildPath "$($sanitizedModelName).wim"
|
if ($existing) {
|
||||||
$existingDriver.Success = $true
|
if (-not $existing.PSObject.Properties['Model']) {
|
||||||
WriteLog "Successfully compressed existing drivers for $modelName to $wimFilePath."
|
$existing | Add-Member -MemberType NoteProperty -Name 'Model' -Value $modelDisplay
|
||||||
|
}
|
||||||
|
if ($CompressToWim -and $existing.Status -eq 'Already downloaded') {
|
||||||
|
$wimPath = Join-Path $makeDriversPath "$sanitizedModelName.wim"
|
||||||
|
$srcPath = Join-Path $makeDriversPath $sanitizedModelName
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Compressing existing...' }
|
||||||
|
try {
|
||||||
|
Compress-DriverFolderToWim -SourceFolderPath $srcPath -DestinationWimPath $wimPath -WimName $modelDisplay -WimDescription "Drivers for $modelDisplay" -PreserveSource:$PreserveSourceOnCompress -ErrorAction Stop
|
||||||
|
$existing.Status = 'Already downloaded & Compressed'
|
||||||
|
$existing.DriverPath = Join-Path $make "$sanitizedModelName.wim"
|
||||||
|
$existing.Success = $true
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error compressing existing drivers for $($modelName): $($_.Exception.Message)"
|
WriteLog "Compression failed for $($modelDisplay): $($_.Exception.Message)"
|
||||||
$existingDriver.Status = "Already downloaded (Compression failed)"
|
$existing.Status = 'Already downloaded (Compression failed)'
|
||||||
$existingDriver.Success = $false
|
|
||||||
}
|
}
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $existingDriver.Status }
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $existing.Status }
|
||||||
|
}
|
||||||
|
return $existing
|
||||||
}
|
}
|
||||||
|
|
||||||
return $existingDriver
|
if (-not (Test-Path $makeDriversPath)) { New-Item -Path $makeDriversPath -ItemType Directory -Force | Out-Null }
|
||||||
}
|
if (-not (Test-Path $modelPath)) { New-Item -Path $modelPath -ItemType Directory -Force | Out-Null }
|
||||||
|
|
||||||
# Define paths for Dell catalog. The catalog is assumed to be prepared by the calling function.
|
$packages = @()
|
||||||
$dellDriversFolder = Join-Path -Path $DriversFolder -ChildPath "Dell"
|
|
||||||
$catalogBaseName = if ($WindowsRelease -le 11) { "CatalogPC" } else { "Catalog" }
|
|
||||||
$dellCatalogXML = Join-Path -Path $dellDriversFolder -ChildPath "$($catalogBaseName).xml"
|
|
||||||
|
|
||||||
# 3. Parse the *EXISTING* XML and Find Drivers for *this specific model*
|
|
||||||
$status = "Finding drivers..."
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
|
|
||||||
# Check if the provided XML path exists
|
|
||||||
if (-not (Test-Path -Path $dellCatalogXML -PathType Leaf)) {
|
|
||||||
throw "Dell Catalog XML file not found at specified path: $dellCatalogXML"
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Parsing existing Dell Catalog XML for model '$modelName' from: $dellCatalogXML"
|
|
||||||
|
|
||||||
# Initialize variables
|
|
||||||
$baseLocation = $null
|
|
||||||
$latestDrivers = @{} # Hashtable to store latest drivers for this model
|
|
||||||
$modelSpecificDriversFound = $false
|
|
||||||
|
|
||||||
# Create XML reader settings
|
|
||||||
$settings = New-Object System.Xml.XmlReaderSettings
|
|
||||||
$settings.IgnoreWhitespace = $true
|
|
||||||
$settings.IgnoreComments = $true
|
|
||||||
|
|
||||||
# Create XML reader
|
|
||||||
$reader = $null
|
|
||||||
try {
|
|
||||||
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML, $settings)
|
|
||||||
|
|
||||||
# First pass - get baseLocation from manifest
|
|
||||||
while ($reader.Read()) {
|
|
||||||
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq "Manifest") {
|
|
||||||
$baseLocationAttr = $reader.GetAttribute("baseLocation")
|
|
||||||
if ($null -ne $baseLocationAttr) {
|
|
||||||
$baseLocation = "https://" + $baseLocationAttr + "/"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($null -eq $baseLocation) {
|
|
||||||
throw "Invalid Dell Catalog XML format: Missing 'baseLocation' attribute in Manifest element."
|
|
||||||
}
|
|
||||||
|
|
||||||
# Reset reader for second pass
|
|
||||||
$reader.Dispose()
|
|
||||||
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML, $settings)
|
|
||||||
|
|
||||||
# Process SoftwareComponents
|
|
||||||
while ($reader.Read()) {
|
|
||||||
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq "SoftwareComponent") {
|
|
||||||
# Read the entire SoftwareComponent subtree
|
|
||||||
$componentXml = $reader.ReadSubtree()
|
|
||||||
$component = New-Object System.Xml.XmlDocument
|
|
||||||
$component.Load($componentXml)
|
|
||||||
$componentXml.Dispose()
|
|
||||||
|
|
||||||
# Check if it's a driver component
|
|
||||||
$componentTypeNode = $component.SelectSingleNode("//ComponentType[@value='DRVR']")
|
|
||||||
if ($null -eq $componentTypeNode) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if component supports the model
|
|
||||||
$modelNodes = $component.SelectNodes("//SupportedSystems/Brand/Model")
|
|
||||||
$modelMatch = $false
|
|
||||||
|
|
||||||
foreach ($modelNode in $modelNodes) {
|
|
||||||
$displayNode = $modelNode.SelectSingleNode("Display")
|
|
||||||
if ($null -ne $displayNode -and $displayNode.InnerText.Trim() -eq $modelName) {
|
|
||||||
$modelMatch = $true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($modelMatch) {
|
|
||||||
# Check OS compatibility
|
|
||||||
$validOS = $null
|
|
||||||
$osNodes = $component.SelectNodes("//SupportedOperatingSystems/OperatingSystem")
|
|
||||||
|
|
||||||
if ($null -ne $osNodes) {
|
|
||||||
foreach ($osNode in $osNodes) {
|
|
||||||
$osArch = $osNode.GetAttribute("osArch")
|
|
||||||
|
|
||||||
if ($WindowsRelease -le 11) {
|
if ($WindowsRelease -le 11) {
|
||||||
# Client OS check
|
$cabUrl = $DriverItemData.CabUrl
|
||||||
if ($osArch -eq $WindowsArch) {
|
if ([string]::IsNullOrWhiteSpace($cabUrl)) {
|
||||||
$validOS = $osNode
|
WriteLog "CabUrl missing for '$modelDisplay' – falling back to legacy CatalogPC parsing."
|
||||||
break
|
# Fallback legacy client method
|
||||||
}
|
$catalogCab = Join-Path $makeDriversPath 'CatalogPC.cab'
|
||||||
}
|
$catalogXml = Join-Path $makeDriversPath 'CatalogPC.xml'
|
||||||
else {
|
$catalogUrl = 'http://downloads.dell.com/catalog/CatalogPC.cab'
|
||||||
# Server OS check
|
$need = $true
|
||||||
$osCode = $osNode.GetAttribute("osCode")
|
if (Test-Path $catalogXml) {
|
||||||
$osCodePattern = switch ($WindowsRelease) {
|
if (((Get-Date) - (Get-Item $catalogXml).CreationTime).TotalDays -lt 7) { $need = $false }
|
||||||
2016 { "W14" }
|
|
||||||
2019 { "W19" }
|
|
||||||
2022 { "W22" }
|
|
||||||
2025 { "W25" }
|
|
||||||
default { "W22" }
|
|
||||||
}
|
|
||||||
if ($osArch -eq $WindowsArch -and $osCode -match $osCodePattern) {
|
|
||||||
$validOS = $osNode
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if ($need) {
|
||||||
|
if (Test-Path $catalogCab) { Remove-SafeFolder $catalogCab }
|
||||||
|
if (Test-Path $catalogXml) { Remove-SafeFolder $catalogXml }
|
||||||
|
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $catalogCab
|
||||||
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$catalogCab"" ""$catalogXml""" | Out-Null
|
||||||
|
Remove-Item $catalogCab -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
if (-not (Test-Path $catalogXml)) { throw "Legacy fallback failed; missing $catalogXml" }
|
||||||
|
[xml]$xmlContent = Get-Content -Path $catalogXml -Raw
|
||||||
|
$baseLocation = "https://$($xmlContent.manifest.baseLocation)/"
|
||||||
|
$softwareComponents = $xmlContent.Manifest.SoftwareComponent | Where-Object { $_.ComponentType.value -eq 'DRVR' }
|
||||||
|
|
||||||
if ($validOS) {
|
$latestDrivers = @{}
|
||||||
$modelSpecificDriversFound = $true
|
|
||||||
|
|
||||||
# Extract driver information
|
|
||||||
$driverPath = $component.SoftwareComponent.GetAttribute("path")
|
|
||||||
$downloadUrl = $baseLocation + $driverPath
|
|
||||||
$driverFileName = [System.IO.Path]::GetFileName($driverPath)
|
|
||||||
|
|
||||||
# Get name
|
|
||||||
$nameNode = $component.SelectSingleNode("//Name/Display")
|
|
||||||
$name = if ($null -ne $nameNode) { $nameNode.InnerText } else { "UnknownDriver" }
|
|
||||||
$name = $name -replace '[\\\/\:\*\?\"\<\>\| ]', '_' -replace '[\,]', '-'
|
|
||||||
|
|
||||||
# Get category
|
|
||||||
$categoryNode = $component.SelectSingleNode("//Category/Display")
|
|
||||||
$category = if ($null -ne $categoryNode) { $categoryNode.InnerText } else { "Uncategorized" }
|
|
||||||
$category = $category -replace '[\\\/\:\*\?\"\<\>\| ]', '_'
|
|
||||||
|
|
||||||
# Get version
|
|
||||||
$version = [version]"0.0"
|
|
||||||
$vendorVersion = $component.SoftwareComponent.GetAttribute("vendorVersion")
|
|
||||||
if ($null -ne $vendorVersion) {
|
|
||||||
try { $version = [version]$vendorVersion } catch { WriteLog "Warning: Could not parse version '$vendorVersion' for driver '$name'. Using 0.0." }
|
|
||||||
}
|
|
||||||
|
|
||||||
$namePrefix = ($name -split '-')[0]
|
|
||||||
|
|
||||||
# Store the latest version for each category/prefix combination
|
|
||||||
if (-not $latestDrivers.ContainsKey($category)) { $latestDrivers[$category] = @{} }
|
|
||||||
if (-not $latestDrivers[$category].ContainsKey($namePrefix) -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
|
|
||||||
$latestDrivers[$category][$namePrefix] = [PSCustomObject]@{
|
|
||||||
Name = $name
|
|
||||||
DownloadUrl = $downloadUrl
|
|
||||||
DriverFileName = $driverFileName
|
|
||||||
Version = $version
|
|
||||||
Category = $category
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if ($null -ne $reader) {
|
|
||||||
$reader.Dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteLog "Searching $($softwareComponents.Count) DRVR components in '$dellCatalogXML' for model '$modelName'..."
|
|
||||||
|
|
||||||
foreach ($component in $softwareComponents) {
|
foreach ($component in $softwareComponents) {
|
||||||
# Check if SupportedSystems and Brand exist
|
$models = $component.SupportedSystems.Brand.Model
|
||||||
if ($null -eq $component.SupportedSystems -or $null -eq $component.SupportedSystems.Brand) { continue }
|
foreach ($m in $models) {
|
||||||
# Ensure Model is iterable
|
if ($m.Display.'#cdata-section' -eq $modelDisplay) {
|
||||||
$componentModels = @($component.SupportedSystems.Brand.Model)
|
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch }
|
||||||
if ($null -eq $componentModels) { continue }
|
if (-not $validOS) { continue }
|
||||||
|
|
||||||
$modelMatch = $false
|
|
||||||
foreach ($item in $componentModels) {
|
|
||||||
# Check if Display and its CDATA section exist before accessing
|
|
||||||
if ($null -ne $item.Display -and $null -ne $item.Display.'#cdata-section' -and $item.Display.'#cdata-section'.Trim() -eq $modelName) {
|
|
||||||
$modelMatch = $true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($modelMatch) {
|
|
||||||
# Model matches, now check OS compatibility
|
|
||||||
$validOS = $null
|
|
||||||
if ($null -ne $component.SupportedOperatingSystems) {
|
|
||||||
# Ensure OperatingSystem is always an array/collection
|
|
||||||
$osList = @($component.SupportedOperatingSystems.OperatingSystem)
|
|
||||||
|
|
||||||
if ($null -ne $osList) {
|
|
||||||
if ($WindowsRelease -le 11) {
|
|
||||||
# Client OS check
|
|
||||||
$validOS = $osList | Where-Object { $_.osArch -eq $WindowsArch } | Select-Object -First 1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Server OS check
|
|
||||||
$osCodePattern = switch ($WindowsRelease) {
|
|
||||||
2016 { "W14" } # Note: Dell uses W14 for Server 2016
|
|
||||||
2019 { "W19" }
|
|
||||||
2022 { "W22" }
|
|
||||||
2025 { "W25" }
|
|
||||||
default { "W22" } # Fallback, adjust as needed
|
|
||||||
}
|
|
||||||
$validOS = $osList | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match $osCodePattern) } | Select-Object -First 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($validOS) {
|
|
||||||
$modelSpecificDriversFound = $true # Mark that we found at least one relevant driver component
|
|
||||||
$driverPath = $component.path
|
$driverPath = $component.path
|
||||||
$downloadUrl = $baseLocation + $driverPath
|
$downloadUrl = $baseLocation + $driverPath
|
||||||
$driverFileName = [System.IO.Path]::GetFileName($driverPath)
|
$fileName = [IO.Path]::GetFileName($driverPath)
|
||||||
# Check if Name, Display, and CDATA exist
|
$name = $component.Name.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_' -replace '[\,]','-'
|
||||||
$name = "UnknownDriver" # Default name
|
$category = $component.Category.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_'
|
||||||
if ($null -ne $component.Name -and $null -ne $component.Name.Display -and $null -ne $component.Name.Display.'#cdata-section') {
|
$version = [version]$component.vendorVersion
|
||||||
$name = $component.Name.Display.'#cdata-section'
|
$namePrefix = ($name -split '-')[0]
|
||||||
$name = $name -replace '[\\\/\:\*\?\"\<\>\| ]', '_' -replace '[\,]', '-'
|
if (-not $latestDrivers[$category]) { $latestDrivers[$category] = @{} }
|
||||||
}
|
if (-not $latestDrivers[$category][$namePrefix] -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
|
||||||
# Check if Category, Display, and CDATA exist
|
$latestDrivers[$category][$namePrefix] = [pscustomobject]@{
|
||||||
$category = "Uncategorized" # Default category
|
|
||||||
if ($null -ne $component.Category -and $null -ne $component.Category.Display -and $null -ne $component.Category.Display.'#cdata-section') {
|
|
||||||
$category = $component.Category.Display.'#cdata-section'
|
|
||||||
$category = $category -replace '[\\\/\:\*\?\"\<\>\| ]', '_'
|
|
||||||
}
|
|
||||||
$version = [version]"0.0" # Default version
|
|
||||||
if ($null -ne $component.vendorVersion) {
|
|
||||||
try { $version = [version]$component.vendorVersion } catch { WriteLog "Warning: Could not parse version '$($component.vendorVersion)' for driver '$name'. Using 0.0." }
|
|
||||||
}
|
|
||||||
$namePrefix = ($name -split '-')[0] # Group by prefix within category
|
|
||||||
|
|
||||||
# Store the latest version for each category/prefix combination
|
|
||||||
if (-not $latestDrivers.ContainsKey($category)) { $latestDrivers[$category] = @{} }
|
|
||||||
if (-not $latestDrivers[$category].ContainsKey($namePrefix) -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
|
|
||||||
$latestDrivers[$category][$namePrefix] = [PSCustomObject]@{
|
|
||||||
Name = $name
|
Name = $name
|
||||||
DownloadUrl = $downloadUrl
|
DownloadUrl = $downloadUrl
|
||||||
DriverFileName = $driverFileName
|
DriverFileName = $fileName
|
||||||
Version = $version
|
Version = $version
|
||||||
Category = $category
|
Category = $category
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} # End if ($modelMatch)
|
|
||||||
} # End foreach ($component in $softwareComponents)
|
|
||||||
|
|
||||||
if (-not $modelSpecificDriversFound) {
|
|
||||||
$status = "No drivers found for OS"
|
|
||||||
WriteLog "No drivers found for model '$modelName' matching Windows Release '$WindowsRelease' and Arch '$WindowsArch' in '$dellCatalogXML'."
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
# Consider this success as the process completed, just no drivers to download
|
|
||||||
return [PSCustomObject]@{ Model = $modelName; Status = $status; Success = $true }
|
|
||||||
}
|
|
||||||
|
|
||||||
# 4. Download and Extract Found Drivers (Logic remains largely the same)
|
|
||||||
$totalDriversToProcess = ($latestDrivers.Values | ForEach-Object { $_.Values.Count } | Measure-Object -Sum).Sum
|
|
||||||
$driversProcessed = 0
|
|
||||||
WriteLog "Found $totalDriversToProcess latest driver packages to download for $modelName."
|
|
||||||
|
|
||||||
# Ensure base directories exist before loop
|
|
||||||
if (-not (Test-Path -Path $makeDriversPath)) { New-Item -Path $makeDriversPath -ItemType Directory -Force | Out-Null }
|
|
||||||
if (-not (Test-Path -Path $modelPath)) { New-Item -Path $modelPath -ItemType Directory -Force | Out-Null }
|
|
||||||
|
|
||||||
foreach ($category in $latestDrivers.Keys) {
|
|
||||||
foreach ($driver in $latestDrivers[$category].Values) {
|
|
||||||
$driversProcessed++
|
|
||||||
$status = "Downloading $($driversProcessed)/$($totalDriversToProcess): $($driver.Name)"
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
|
|
||||||
$downloadFolder = Join-Path -Path $modelPath -ChildPath $driver.Category
|
|
||||||
$driverFilePath = Join-Path -Path $downloadFolder -ChildPath $driver.DriverFileName
|
|
||||||
$extractFolder = Join-Path -Path $downloadFolder -ChildPath $driver.DriverFileName.TrimEnd($driver.DriverFileName[-4..-1])
|
|
||||||
|
|
||||||
# Check if already extracted (more robust check)
|
|
||||||
if (Test-Path -Path $extractFolder -PathType Container) {
|
|
||||||
$extractSize = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
||||||
if ($extractSize -gt 1KB) {
|
|
||||||
WriteLog "Driver already extracted: $($driver.Name) in $extractFolder. Skipping."
|
|
||||||
continue # Skip to next driver
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# Check if download file exists but extraction folder doesn't or is empty
|
foreach ($cat in $latestDrivers.Keys) { foreach ($drv in $latestDrivers[$cat].Values) { $packages += $drv } }
|
||||||
if (Test-Path -Path $driverFilePath -PathType Leaf) {
|
|
||||||
WriteLog "Download file $($driver.DriverFileName) exists, but extraction folder '$extractFolder' is missing or empty. Will attempt extraction."
|
|
||||||
# Proceed to extraction logic below
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Download the driver
|
# Normal new model-based workflow
|
||||||
WriteLog "Downloading driver: $($driver.Name) ($($driver.DriverFileName))"
|
$modelCabName = [IO.Path]::GetFileName($cabUrl)
|
||||||
if (-not (Test-Path -Path $downloadFolder)) {
|
if ([string]::IsNullOrWhiteSpace($modelCabName)) { throw "Derived model cab name empty for $modelDisplay" }
|
||||||
WriteLog "Creating download folder: $downloadFolder"
|
$modelCabPath = Join-Path $makeDriversPath $modelCabName
|
||||||
New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null
|
$modelXmlPath = Join-Path $makeDriversPath ([IO.Path]::GetFileNameWithoutExtension($modelCabName) + '.xml')
|
||||||
|
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Downloading catalog...' }
|
||||||
|
if (Test-Path $modelCabPath) { Remove-SafeFolder $modelCabPath }
|
||||||
|
if (Test-Path $modelXmlPath) { Remove-SafeFolder $modelXmlPath }
|
||||||
|
|
||||||
|
Start-BitsTransferWithRetry -Source $cabUrl -Destination $modelCabPath
|
||||||
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$modelCabPath"" ""$modelXmlPath""" | Out-Null
|
||||||
|
Remove-Item $modelCabPath -Force -ErrorAction SilentlyContinue
|
||||||
|
if (-not (Test-Path $modelXmlPath)) { throw "Model XML not found after extraction: $modelXmlPath" }
|
||||||
|
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Selecting latest drivers...' }
|
||||||
|
$packages = Get-DellLatestDriverPackages -ModelXmlPath $modelXmlPath -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease
|
||||||
}
|
}
|
||||||
WriteLog "Downloading from: $($driver.DownloadUrl) to $driverFilePath"
|
}
|
||||||
|
else {
|
||||||
|
# Server legacy logic unchanged (kept as before)
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Preparing server catalog...' }
|
||||||
|
$catalogCab = Join-Path $makeDriversPath 'Catalog.cab'
|
||||||
|
$catalogXml = Join-Path $makeDriversPath 'Catalog.xml'
|
||||||
|
$catalogUrl = 'https://downloads.dell.com/catalog/Catalog.cab'
|
||||||
|
$need = $true
|
||||||
|
if (Test-Path $catalogXml) {
|
||||||
|
if (((Get-Date) - (Get-Item $catalogXml).CreationTime).TotalDays -lt 7) { $need = $false }
|
||||||
|
}
|
||||||
|
if ($need) {
|
||||||
|
if (Test-Path $catalogCab) { Remove-SafeFolder $catalogCab }
|
||||||
|
if (Test-Path $catalogXml) { Remove-SafeFolder $catalogXml }
|
||||||
|
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $catalogCab
|
||||||
|
Invoke-Process -FilePath Expand.exe -ArgumentList """$catalogCab"" ""$catalogXml""" | Out-Null
|
||||||
|
Remove-Item $catalogCab -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $catalogXml)) { throw "Server catalog XML missing: $catalogXml" }
|
||||||
|
|
||||||
|
[xml]$xmlContent = Get-Content -Path $catalogXml -Raw
|
||||||
|
$baseLocation = "https://$($xmlContent.manifest.baseLocation)/"
|
||||||
|
$softwareComponents = $xmlContent.Manifest.SoftwareComponent | Where-Object { $_.ComponentType.value -eq 'DRVR' }
|
||||||
|
$latestDrivers = @{}
|
||||||
|
foreach ($component in $softwareComponents) {
|
||||||
|
$models = $component.SupportedSystems.Brand.Model
|
||||||
|
foreach ($m in $models) {
|
||||||
|
if ($m.Display.'#cdata-section' -eq $modelDisplay) {
|
||||||
|
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch }
|
||||||
|
if (-not $validOS) { continue }
|
||||||
|
$driverPath = $component.path
|
||||||
|
$downloadUrl = $baseLocation + $driverPath
|
||||||
|
$fileName = [IO.Path]::GetFileName($driverPath)
|
||||||
|
$name = $component.Name.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_' -replace '[\,]','-'
|
||||||
|
$category = $component.Category.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_'
|
||||||
|
$version = [version]$component.vendorVersion
|
||||||
|
$namePrefix = ($name -split '-')[0]
|
||||||
|
if (-not $latestDrivers[$category]) { $latestDrivers[$category] = @{} }
|
||||||
|
if (-not $latestDrivers[$category][$namePrefix] -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
|
||||||
|
$latestDrivers[$category][$namePrefix] = [pscustomobject]@{
|
||||||
|
Name = $name
|
||||||
|
DownloadUrl = $downloadUrl
|
||||||
|
DriverFileName = $fileName
|
||||||
|
Version = $version
|
||||||
|
Category = $category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($cat in $latestDrivers.Keys) { foreach ($drv in $latestDrivers[$cat].Values) { $packages += $drv } }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $packages -or $packages.Count -eq 0) {
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'No drivers found for OS' }
|
||||||
|
return [pscustomobject]@{ Model = $modelDisplay; Status = 'No drivers found for OS'; Success = $true; DriverPath = $driverRelativePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = $packages.Count
|
||||||
|
$idx = 0
|
||||||
|
foreach ($pkg in $packages) {
|
||||||
|
$idx++
|
||||||
|
$status = "Downloading $idx/$total"
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $status }
|
||||||
|
|
||||||
|
$categorySafe = ($pkg.Category -replace '[\\\/\:\*\?\"\<\>\| ]','_')
|
||||||
|
$downloadFolder = Join-Path $modelPath $categorySafe
|
||||||
|
if (-not (Test-Path $downloadFolder)) { New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
$driverFilePath = Join-Path $downloadFolder $pkg.DriverFileName
|
||||||
|
$plainName = [IO.Path]::GetFileNameWithoutExtension($pkg.DriverFileName)
|
||||||
|
if ([string]::IsNullOrWhiteSpace($plainName)) { $plainName = "_extract" }
|
||||||
|
$extractFolder = Join-Path $downloadFolder $plainName
|
||||||
|
|
||||||
|
if (Test-Path $extractFolder) {
|
||||||
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
|
if ($sz -gt 1KB) { continue }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $driverFilePath)) {
|
||||||
|
try { Start-BitsTransferWithRetry -Source $pkg.DownloadUrl -Destination $driverFilePath }
|
||||||
|
catch { WriteLog "Download failed: $($pkg.DownloadUrl) $($_.Exception.Message)"; continue }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $extractFolder)) { New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null }
|
||||||
|
|
||||||
|
$arg1 = "/s /e=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
||||||
|
$arg2 = "/s /drivers=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
||||||
|
$ok = $false
|
||||||
try {
|
try {
|
||||||
Start-BitsTransferWithRetry -Source $driver.DownloadUrl -Destination $driverFilePath
|
Invoke-Process -FilePath $driverFilePath -ArgumentList $arg1 | Out-Null
|
||||||
WriteLog "Driver downloaded: $($driver.DriverFileName)"
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
}
|
if ($sz -gt 1KB) { $ok = $true }
|
||||||
catch {
|
if (-not $ok) {
|
||||||
WriteLog "Failed to download driver: $($driver.DownloadUrl). Error: $($_.Exception.Message). Skipping."
|
Remove-SafeFolder $extractFolder
|
||||||
# Update status for this specific driver failure? Maybe too granular.
|
|
||||||
continue # Skip to next driver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Extract the driver
|
|
||||||
$status = "Extracting $($driversProcessed)/$($totalDriversToProcess): $($driver.Name)"
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
|
|
||||||
# Ensure extraction folder exists before attempting extraction
|
|
||||||
if (-not (Test-Path -Path $extractFolder)) {
|
|
||||||
WriteLog "Creating extraction folder: $extractFolder"
|
|
||||||
New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
||||||
}
|
Invoke-Process -FilePath $driverFilePath -ArgumentList $arg2 | Out-Null
|
||||||
|
$sz = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
||||||
# Dell uses /e to extact the entire DUP while /drivers to extract only the drivers
|
if ($sz -gt 1KB) { $ok = $true }
|
||||||
# In many cases /drivers will extract drivers for mutliple OS versions
|
|
||||||
# Which can cause many duplicate files and bloat your driver folder
|
|
||||||
# /e seems to be better and only extracts what is necessary and has less issues
|
|
||||||
# We will default to using /e, but will fall back to /drivers if content cannot be found
|
|
||||||
|
|
||||||
$arguments = "/s /e=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
|
||||||
$altArguments = "/s /drivers=`"$extractFolder`" /l=`"$extractFolder\log.log`""
|
|
||||||
$extractionSuccess = $false
|
|
||||||
try {
|
|
||||||
# Handle special cases (Chipset/Network) - Check if OS is Server
|
|
||||||
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem # Get OS info within the task scope
|
|
||||||
$isServer = $osInfo.Caption -match 'server'
|
|
||||||
|
|
||||||
# Chipset drivers may require killing child processes in some cases
|
|
||||||
if ($driver.Category -eq "Chipset") {
|
|
||||||
WriteLog "Extracting Chipset driver: $driverFilePath $arguments"
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false
|
|
||||||
Start-Sleep -Seconds 5 # Allow time for extraction
|
|
||||||
WriteLog "Extraction exited with exit code: $($process.ExitCode)"
|
|
||||||
# Attempt to gracefully close child process if needed (logic from original script)
|
|
||||||
$childProcesses = Get-CimInstance Win32_Process -Filter "ParentProcessId = $($process.Id)"
|
|
||||||
if ($childProcesses) {
|
|
||||||
$latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1
|
|
||||||
WriteLog "Stopping child process for Chipset driver: $($latestProcess.Name) (PID: $($latestProcess.ProcessId))"
|
|
||||||
Stop-Process -Id $latestProcess.ProcessId -Force -ErrorAction SilentlyContinue
|
|
||||||
Start-Sleep -Seconds 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# Network drivers on client OS may require killing child processes
|
|
||||||
elseif ($driver.Category -eq "Network" -and -not $isServer) {
|
|
||||||
WriteLog "Extracting Network driver: $driverFilePath $arguments"
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false
|
|
||||||
Start-Sleep -Seconds 5
|
|
||||||
WriteLog "Extraction exited with exit code: $($process.ExitCode)"
|
|
||||||
if (-not $process.HasExited) {
|
|
||||||
$childProcesses = Get-CimInstance Win32_Process -Filter "ParentProcessId = $($process.Id)"
|
|
||||||
if ($childProcesses) {
|
|
||||||
$latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1
|
|
||||||
WriteLog "Stopping child process for Network driver: $($latestProcess.Name) (PID: $($latestProcess.ProcessId))"
|
|
||||||
Stop-Process -Id $latestProcess.ProcessId -Force -ErrorAction SilentlyContinue
|
|
||||||
Start-Sleep -Seconds 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "Extracting driver: $driverFilePath $arguments"
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments
|
|
||||||
WriteLog "Extraction exited with exit code: $($process.ExitCode)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify extraction (check if folder has content)
|
|
||||||
if (Test-Path -Path $extractFolder -PathType Container) {
|
|
||||||
$extractSize = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
||||||
if ($extractSize -gt 1KB) {
|
|
||||||
$extractionSuccess = $true
|
|
||||||
WriteLog "Extraction successful (Method 1) for $driverFilePath $arguments"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# If primary extraction failed or folder is empty, try alternative
|
|
||||||
if (-not $extractionSuccess) {
|
|
||||||
# $arguments = "/s /e=`"$extractFolder`""
|
|
||||||
# $altArguments = "/s /drivers=`"$extractFolder`""
|
|
||||||
WriteLog "Extraction with $arguments failed or resulted in empty folder for $driverFilePath. Retrying with $altArguments"
|
|
||||||
# Clean up potentially empty folder before retrying
|
|
||||||
Remove-Item -Path $extractFolder -Recurse -Force -ErrorAction SilentlyContinue
|
|
||||||
New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null # Recreate empty folder
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $altArguments
|
|
||||||
WriteLog "Extraction exited with exit code: $($process.ExitCode)"
|
|
||||||
|
|
||||||
# Verify extraction again
|
|
||||||
if (Test-Path -Path $extractFolder -PathType Container) {
|
|
||||||
$extractSize = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
||||||
if ($extractSize -gt 1KB) {
|
|
||||||
$extractionSuccess = $true
|
|
||||||
WriteLog "Extraction successful (Method 2) for $driverFilePath $altArguments"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error during extraction process for $($driver.DriverFileName): $($_.Exception.Message). Trying alternative method."
|
WriteLog "Extraction error: $($_.Exception.Message)"
|
||||||
# Try alternative method on any error during the first attempt block
|
|
||||||
try {
|
|
||||||
if (Test-Path -Path $extractFolder) {
|
|
||||||
# Clean up before retry if needed
|
|
||||||
Remove-Item -Path $extractFolder -Recurse -Force -ErrorAction SilentlyContinue
|
|
||||||
}
|
}
|
||||||
New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null
|
|
||||||
# $arguments = "/s /e=`"$extractFolder`""
|
|
||||||
# $altArguments = "/s /drivers=`"$extractFolder`""
|
|
||||||
WriteLog "Extracting driver (Method 2): $driverFilePath $altArguments"
|
|
||||||
$process = Invoke-Process -FilePath $driverFilePath -ArgumentList $altArguments
|
|
||||||
WriteLog "Extraction exited with exit code: $($process.ExitCode)"
|
|
||||||
|
|
||||||
# Verify extraction again
|
if ($ok) {
|
||||||
if (Test-Path -Path $extractFolder -PathType Container) {
|
Remove-Item $driverFilePath -Force -ErrorAction SilentlyContinue
|
||||||
$extractSize = (Get-ChildItem -Path $extractFolder -Recurse -Exclude *.log | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue).Sum
|
|
||||||
if ($extractSize -gt 1KB) {
|
|
||||||
$extractionSuccess = $true
|
|
||||||
WriteLog "Extraction successful (Method 2) for $driverFilePath."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
WriteLog "Alternative extraction method also failed for $($driver.DriverFileName): $($_.Exception.Message)."
|
|
||||||
# Extraction failed completely
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cleanup downloaded file only if extraction was successful
|
|
||||||
if ($extractionSuccess) {
|
|
||||||
WriteLog "Deleting driver file: $driverFilePath"
|
|
||||||
Remove-Item -Path $driverFilePath -Force -ErrorAction SilentlyContinue
|
|
||||||
WriteLog "Driver file deleted: $driverFilePath"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "Extraction failed for $($driver.DriverFileName). Downloaded file kept at $driverFilePath for inspection."
|
|
||||||
# Update status to indicate partial failure?
|
|
||||||
}
|
|
||||||
|
|
||||||
} # End foreach ($driver in $latestDrivers)
|
|
||||||
} # End foreach ($category in $latestDrivers)
|
|
||||||
|
|
||||||
# --- Compress to WIM if requested (after all drivers processed) ---
|
|
||||||
if ($CompressToWim) {
|
if ($CompressToWim) {
|
||||||
$status = "Compressing..."
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status 'Compressing...' }
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
$wimPath = Join-Path $makeDriversPath "$sanitizedModelName.wim"
|
||||||
$wimFileName = "$($modelName).wim"
|
|
||||||
$destinationWimPath = Join-Path -Path $makeDriversPath -ChildPath $wimFileName
|
|
||||||
$driverRelativePath = Join-Path -Path $make -ChildPath $wimFileName # Update relative path to the WIM file
|
|
||||||
WriteLog "Compressing '$modelPath' to '$destinationWimPath'..."
|
|
||||||
try {
|
try {
|
||||||
$compressResult = Compress-DriverFolderToWim -SourceFolderPath $modelPath -DestinationWimPath $destinationWimPath -WimName $modelName -WimDescription $modelName -PreserveSource:$PreserveSourceOnCompress -ErrorAction Stop
|
Compress-DriverFolderToWim -SourceFolderPath $modelPath -DestinationWimPath $wimPath -WimName $modelDisplay -WimDescription $modelDisplay -PreserveSource:$PreserveSourceOnCompress -ErrorAction Stop
|
||||||
if ($compressResult) {
|
$driverRelativePath = Join-Path $make "$sanitizedModelName.wim"
|
||||||
WriteLog "Compression successful for '$modelName'."
|
$statusFinal = 'Completed & Compressed'
|
||||||
$status = "Completed & Compressed"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
WriteLog "Compression failed for '$modelName'. Check verbose/error output from Compress-DriverFolderToWim."
|
|
||||||
$status = "Completed (Compression Failed)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
WriteLog "Error during compression for '$modelName': $($_.Exception.Message)"
|
WriteLog "Compression failed for $($modelDisplay): $($_.Exception.Message)"
|
||||||
$status = "Completed (Compression Error)"
|
$statusFinal = 'Completed (Compression Failed)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$status = "Completed" # Final status if not compressing
|
$statusFinal = 'Completed'
|
||||||
}
|
}
|
||||||
# --- End Compression ---
|
|
||||||
|
|
||||||
$success = $true # Mark success as download/extract was okay
|
|
||||||
|
|
||||||
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $statusFinal }
|
||||||
|
return [pscustomobject]@{ Model = $modelDisplay; Status = $statusFinal; Success = $true; DriverPath = $driverRelativePath }
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
$status = "Error: $($_.Exception.Message.Split('.')[0])" # Shorten error message
|
$err = "Error: $($_.Exception.Message.Split('.')[0])"
|
||||||
WriteLog "Error saving Dell drivers for $($modelName): $($_.Exception.ToString())" # Log full exception string
|
WriteLog "Save-DellDriversTask error for $($modelDisplay): $($_.Exception.ToString())"
|
||||||
$success = $false
|
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $err }
|
||||||
# Enqueue the error status before returning
|
return [pscustomobject]@{ Model = $modelDisplay; Status = $err; Success = $false; DriverPath = $null }
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
# Ensure return object is created even on error
|
|
||||||
return [PSCustomObject]@{ Model = $modelName; Status = $status; Success = $success; DriverPath = $null }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enqueue the final status (success or error) before returning
|
|
||||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
|
||||||
|
|
||||||
# Return the final status
|
|
||||||
return [PSCustomObject]@{ Model = $modelName; Status = $status; Success = $success; DriverPath = $driverRelativePath }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Export-ModuleMember -Function *
|
Export-ModuleMember -Function *
|
||||||
@@ -84,8 +84,8 @@ function ConvertTo-StandardizedDriverModel {
|
|||||||
[psobject]$State
|
[psobject]$State
|
||||||
)
|
)
|
||||||
|
|
||||||
$modelDisplay = $RawDriverObject.Model # Default
|
$modelDisplay = $RawDriverObject.Model
|
||||||
$id = $RawDriverObject.Model # Default
|
$id = $RawDriverObject.Model
|
||||||
$link = $null
|
$link = $null
|
||||||
$productName = $null
|
$productName = $null
|
||||||
$machineType = $null
|
$machineType = $null
|
||||||
@@ -102,7 +102,21 @@ function ConvertTo-StandardizedDriverModel {
|
|||||||
$id = $RawDriverObject.MachineType
|
$id = $RawDriverObject.MachineType
|
||||||
}
|
}
|
||||||
|
|
||||||
return [PSCustomObject]@{
|
# Dell-specific passthrough (needed for per-model cab workflow)
|
||||||
|
$dellBrand = $null
|
||||||
|
$dellModelNumber = $null
|
||||||
|
$dellSystemId = $null
|
||||||
|
$dellCabUrl = $null
|
||||||
|
$dellCabRelative = $null
|
||||||
|
if ($Make -eq 'Dell') {
|
||||||
|
if ($RawDriverObject.PSObject.Properties['Brand']) { $dellBrand = $RawDriverObject.Brand }
|
||||||
|
if ($RawDriverObject.PSObject.Properties['ModelNumber']) { $dellModelNumber = $RawDriverObject.ModelNumber }
|
||||||
|
if ($RawDriverObject.PSObject.Properties['SystemId']) { $dellSystemId = $RawDriverObject.SystemId }
|
||||||
|
if ($RawDriverObject.PSObject.Properties['CabUrl']) { $dellCabUrl = $RawDriverObject.CabUrl }
|
||||||
|
if ($RawDriverObject.PSObject.Properties['CabRelativePath']) { $dellCabRelative = $RawDriverObject.CabRelativePath }
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = [PSCustomObject]@{
|
||||||
IsSelected = $false
|
IsSelected = $false
|
||||||
Make = $Make
|
Make = $Make
|
||||||
Model = $modelDisplay
|
Model = $modelDisplay
|
||||||
@@ -110,12 +124,23 @@ function ConvertTo-StandardizedDriverModel {
|
|||||||
Id = $id
|
Id = $id
|
||||||
ProductName = $productName
|
ProductName = $productName
|
||||||
MachineType = $machineType
|
MachineType = $machineType
|
||||||
Version = "" # Placeholder
|
Version = ""
|
||||||
Type = "" # Placeholder
|
Type = ""
|
||||||
Size = "" # Placeholder
|
Size = ""
|
||||||
Arch = "" # Placeholder
|
Arch = ""
|
||||||
DownloadStatus = "" # Initial download status
|
DownloadStatus = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($Make -eq 'Dell') {
|
||||||
|
# Add Dell-only fields so Save-DellDriversTask can use CabUrl
|
||||||
|
$output | Add-Member -NotePropertyName Brand -NotePropertyValue $dellBrand
|
||||||
|
$output | Add-Member -NotePropertyName ModelNumber -NotePropertyValue $dellModelNumber
|
||||||
|
$output | Add-Member -NotePropertyName SystemId -NotePropertyValue $dellSystemId
|
||||||
|
$output | Add-Member -NotePropertyName CabUrl -NotePropertyValue $dellCabUrl
|
||||||
|
$output | Add-Member -NotePropertyName CabRelativePath -NotePropertyValue $dellCabRelative
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to filter the driver model list based on text input
|
# Function to filter the driver model list based on text input
|
||||||
|
|||||||
Reference in New Issue
Block a user