Public/Dell.Client.Update/Public/Get-DellUpdate.ps1
|
function Get-DellUpdate { <# .SYNOPSIS Gets Dell model-catalog updates applicable to this computer. .DESCRIPTION Downloads Dell's model-specific catalog, matches its supported-device metadata against native Windows PnP and INF inventory, and returns the newest applicable package for each component family. Dell OpenManage Inventory Agent packages are excluded. .PARAMETER All Return the newest applicable package for each detected component, including packages whose installed components are current. .PARAMETER NoTestInstalled Do not report installed state. Requires -All; IsInstalled is $null. .PARAMETER UseCachedCatalog Reuse the model-specific XML if it exists in WorkingDirectory. .PARAMETER WorkingDirectory Directory for catalog downloads and extracted XML. Defaults to C:\ProgramData\DellPSUpdate\Catalogs. .PARAMETER CatalogUrl Dell model catalog index CAB URL. .PARAMETER ExplainRules Include matching device and version details in verbose output. .PARAMETER Details Display every property on returned update objects. By default, output uses a concise property set while retaining all properties for the pipeline. .PARAMETER HonorDCUPolicy Apply Dell Command Update's configured release delay, update types, device categories, and severity filters. .PARAMETER DelayDays Exclude packages released within the specified number of days. Valid values are 1 through 45. Cannot be combined with -HonorDCUPolicy. .NOTES This command has no Dell Command Update or OpenManage dependency. Downloaded CAB files are stored under C:\ProgramData\DellPSUpdate\Downloads. Extracted catalogs are stored under C:\ProgramData\DellPSUpdate\Catalogs by default. Use -UseCachedCatalog to avoid refreshing the model catalog. #> [CmdletBinding()] param( [switch]$All, [switch]$NoTestInstalled, [switch]$UseCachedCatalog, [switch]$ExplainRules, [switch]$Details, [switch]$HonorDCUPolicy, [ValidateRange(1, 45)] [int]$DelayDays, [uri]$CatalogUrl = 'https://downloads.dell.com/catalog/CatalogIndexPC.cab', [string]$WorkingDirectory = (Join-Path $env:ProgramData 'DellPSUpdate\Catalogs') ) if ($NoTestInstalled -and -not $All) { throw '-NoTestInstalled can only be used with -All.' } if ($HonorDCUPolicy -and $PSBoundParameters.ContainsKey('DelayDays')) { throw '-HonorDCUPolicy and -DelayDays cannot be used together.' } $dcuPolicy = if ($HonorDCUPolicy) { Get-DellCommandUpdatePolicy } $effectiveDelayDays = if ($dcuPolicy) { $dcuPolicy.DelayDays } elseif ($PSBoundParameters.ContainsKey('DelayDays')) { $DelayDays } else { 0 } $computer = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop if ($computer.Manufacturer -notmatch '^Dell') { throw "Get-DellUpdate supports Dell systems only. Detected '$($computer.Manufacturer)'." } $systemId = Get-DellSystemId -ComputerSystem $computer $catalogPath = Get-DellModelCatalogXml -SystemId $systemId -WorkingDirectory $WorkingDirectory -UseCachedCatalog:$UseCachedCatalog -CatalogIndexUrl $CatalogUrl $catalog = [System.Xml.XmlDocument]::new() $catalog.XmlResolver = $null $catalog.Load($catalogPath) $baseLocation = $catalog.DocumentElement.GetAttribute('baseLocation') if ([string]::IsNullOrWhiteSpace($baseLocation)) { $baseLocation = 'downloads.dell.com' } $operatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop $inventory = Get-DellModelInventory $candidateUpdates = [System.Collections.Generic.List[object]]::new() $componentCount = 0 Write-Host "Evaluating Dell model catalog for system ID $systemId ..." foreach ($componentNode in $catalog.SelectNodes("//*[local-name()='SoftwareComponent']")) { $componentCount++ $component = [System.Xml.XmlElement]$componentNode $nameNode = $component.SelectSingleNode("./*[local-name()='Name']/*[local-name()='Display']") $name = if ($nameNode) { $nameNode.InnerText.Trim() } else { $component.GetAttribute('releaseID') } if ($name -like 'Dell OpenManage Inventory Agent*') { continue } if (-not (Test-DellModelOperatingSystem -Component $component -OperatingSystem $operatingSystem)) { continue } $state = Get-DellModelComponentState -Component $component -Inventory $inventory if (-not $state) { continue } $vendorVersion = $component.GetAttribute('vendorVersion') $dellVersion = $component.GetAttribute('dellVersion') $releaseId = $component.GetAttribute('releaseID') $packageId = $component.GetAttribute('packageID') if (-not $packageId) { $packageId = $releaseId } $relativePath = $component.GetAttribute('path') $componentIds = @($component.SelectNodes("./*[local-name()='SupportedDCHDevices' or local-name()='SupportedDevices']/*[local-name()='Device']/@componentID") | ForEach-Object Value | Where-Object { $_ } | Sort-Object -Unique) $familyId = if ($componentIds.Count) { $componentIds -join ',' } else { $name } $releaseDate = [datetime]::MinValue $null = [datetime]::TryParse($component.GetAttribute('releaseDate'), [ref]$releaseDate) $severityNode = $component.SelectSingleNode("./*[local-name()='Criticality']/*[local-name()='Display']") $typeNode = $component.SelectSingleNode("./*[local-name()='ComponentType']/*[local-name()='Display']") $categoryNode = $component.SelectSingleNode("./*[local-name()='Category']/*[local-name()='Display']") $sha256Node = $component.SelectSingleNode("./*[local-name()='Cryptography']/*[local-name()='Hash' and translate(@algorithm, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')='SHA256']") $update = [pscustomobject]@{ ID = $releaseId PackageID = $packageId ReleaseID = $releaseId Name = $name Title = "$name,$vendorVersion,$dellVersion" Version = $vendorVersion DellVersion = $dellVersion Severity = if ($severityNode) { $severityNode.InnerText.Trim() } else { '' } ReleaseDate = $releaseDate Type = if ($typeNode) { $typeNode.InnerText.Trim() } else { $componentTypeNode = $component.SelectSingleNode("./*[local-name()='ComponentType']") if ($componentTypeNode) { $componentTypeNode.GetAttribute('value') } else { '' } } Category = if ($categoryNode) { $categoryNode.InnerText.Trim() } else { '' } Size = [long]$component.GetAttribute('size') FileSize = [long]$component.GetAttribute('size') DownloadUri = [uri]::new("https://$baseLocation/$relativePath") URL = [uri]::new("https://$baseLocation/$relativePath") Sha256 = if ($sha256Node) { $sha256Node.InnerText.Trim() } else { '' } Installer = [pscustomobject]@{ Program = [IO.Path]::GetFileName($relativePath) Arguments = '/s' Unattended = $true } IsApplicable = $true IsInstalled = if ($NoTestInstalled) { $null } else { [bool]$state.IsInstalled } WhyApplicable = [string]$state.ApplicabilityReason ApplicabilityRuleStatus = 1 InstallRuleStatus = if ($NoTestInstalled) { $null } elseif ($state.IsInstalled) { 1 } else { 0 } MatchedDevices = @($state.Matches) CatalogPackage = $component CatalogItem = $null FamilyID = $familyId } $update.PSObject.TypeNames.Insert(0, 'Dell.Client.Update.DellUpdate') if (-not $Details) { $defaultProperties = [string[]]@('ID', 'Name', 'Title', 'Version', 'DellVersion', 'ReleaseDate', 'Type', 'Category', 'URL', 'WhyApplicable') $defaultPropertySet = [System.Management.Automation.PSPropertySet]::new('DefaultDisplayPropertySet', $defaultProperties) $standardMembers = [System.Management.Automation.PSMemberSet]::new('PSStandardMembers', [System.Management.Automation.PSMemberInfo[]]@($defaultPropertySet)) $update.PSObject.Members.Add($standardMembers) } $candidateUpdates.Add($update) } $latestUpdates = foreach ($family in ($candidateUpdates | Group-Object FamilyID)) { $family.Group | Sort-Object -Property @( @{ Expression = { $parsedVersion = [version]'0.0' $null = [version]::TryParse($_.Version, [ref]$parsedVersion) $parsedVersion }; Descending = $true }, @{ Expression = 'ReleaseDate'; Descending = $true } ) | Select-Object -First 1 } $matchedFamilyCount = @($latestUpdates).Count if ($dcuPolicy) { $policyExcludedUpdates = @($latestUpdates | Where-Object { $_.Type -notin $dcuPolicy.Types -or $_.Category -notin $dcuPolicy.Categories -or $_.Severity -notin $dcuPolicy.Severities }) foreach ($update in $policyExcludedUpdates) { Write-Verbose "Excluding $($update.ReleaseID) because its type '$($update.Type)', category '$($update.Category)', or severity '$($update.Severity)' is disabled by Dell Command Update policy." } $latestUpdates = @($latestUpdates | Where-Object { $_.Type -in $dcuPolicy.Types -and $_.Category -in $dcuPolicy.Categories -and $_.Severity -in $dcuPolicy.Severities }) } $deferredUpdates = @() if ($effectiveDelayDays -gt 0) { $releaseCutoff = (Get-Date).Date.AddDays(-$effectiveDelayDays) $deferredUpdates = @($latestUpdates | Where-Object { $_.ReleaseDate.Date -gt $releaseCutoff }) foreach ($update in $deferredUpdates) { Write-Verbose "Deferring $($update.ReleaseID) until $($update.ReleaseDate.Date.AddDays($effectiveDelayDays).ToShortDateString()) because the effective delay is $effectiveDelayDays days." } $latestUpdates = @($latestUpdates | Where-Object { $_.ReleaseDate.Date -le $releaseCutoff }) } $delaySummary = if ($effectiveDelayDays -gt 0) { "; $($deferredUpdates.Count) deferred by the $effectiveDelayDays-day policy" } else { '' } Write-Host "Model catalog evaluation complete: $componentCount packages checked; $matchedFamilyCount installed component families matched$delaySummary." foreach ($update in $latestUpdates) { if (-not $All -and $update.IsInstalled -ne $false) { continue } if ($ExplainRules) { $matchSummary = @($update.MatchedDevices | ForEach-Object { "$($_.DeviceName): $($_.InstalledVersion) -> $($_.ExpectedVersion)" }) -join '; ' Write-Verbose "$($update.ReleaseID) $($update.Name): installed=$($update.IsInstalled); $matchSummary" } $update } } |