Private/Get-InstallPackageLatestVersion.ps1
|
#Requires -Version 5.1 <#! .SYNOPSIS Resolves latest package artifact for Install workflow with local JSON cache. .DESCRIPTION Wraps Get-IntunePackageLatestVersion and stores per-definition resolution results in a cache JSON file. Cached entries are reused when still fresh. .PARAMETER DefinitionPath Full path to App.json definition file. .PARAMETER DefinitionObject Parsed App.json object. .PARAMETER CacheRootPath Directory where the cache file is written. .PARAMETER CacheMaxAgeHours Maximum cache age before refresh. Default: 24. .OUTPUTS PSCustomObject with: Succeeded : bool Version : string URI : string ResolvedArtifact : object FilterExpression: string Error : string IsFromCache : bool CacheFile : string !#> function Get-InstallPackageLatestVersion { [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [string]$DefinitionPath, [Parameter(Mandatory)] [PSCustomObject]$DefinitionObject, [Parameter(Mandatory)] [string]$CacheRootPath, [int]$CacheMaxAgeHours = 24 ) $fail = { param([string]$Message) return [PSCustomObject]@{ Succeeded = $false Version = '' URI = '' ResolvedArtifact = $null FilterExpression = '' Error = $Message IsFromCache = $false CacheFile = '' } } if ([string]::IsNullOrWhiteSpace($DefinitionPath)) { return (& $fail 'DefinitionPath is empty.') } if ($null -eq $DefinitionObject) { return (& $fail 'DefinitionObject is null.') } if ([string]::IsNullOrWhiteSpace($CacheRootPath)) { return (& $fail 'CacheRootPath is empty.') } try { if (-not (Test-Path -LiteralPath $CacheRootPath -PathType Container)) { $null = New-Item -Path $CacheRootPath -ItemType Directory -Force -ErrorAction Stop } } catch { return (& $fail "Failed to create cache directory '$CacheRootPath': $($_.Exception.Message)") } $cacheFile = Join-Path -Path $CacheRootPath -ChildPath 'install-latest-cache.json' $cacheEntries = @() if (Test-Path -LiteralPath $cacheFile -PathType Leaf) { try { $raw = Get-Content -LiteralPath $cacheFile -Raw -ErrorAction Stop if (-not [string]::IsNullOrWhiteSpace($raw)) { $parsed = ConvertFrom-Json -InputObject $raw -ErrorAction Stop if ($parsed -is [System.Array]) { $cacheEntries = @($parsed) } elseif ($null -ne $parsed) { $cacheEntries = @($parsed) } } } catch { $cacheEntries = @() } } $nowUtc = [DateTime]::UtcNow $definitionKey = $DefinitionPath.Trim().ToLowerInvariant() $matchedEntry = $null foreach ($entry in $cacheEntries) { if ($null -eq $entry) { continue } $entryPath = [string]$entry.DefinitionPath if ([string]::IsNullOrWhiteSpace($entryPath)) { continue } if ($entryPath.Trim().ToLowerInvariant() -eq $definitionKey) { $matchedEntry = $entry break } } if ($null -ne $matchedEntry) { [DateTime]$retrievedUtc = [DateTime]::MinValue $entryTimestamp = [string]$matchedEntry.RetrievedUtc if ([DateTime]::TryParseExact( $entryTimestamp, 'o', [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind, [ref]$retrievedUtc )) { $maxAge = [TimeSpan]::FromHours([Math]::Max(1, $CacheMaxAgeHours)) if (($nowUtc - $retrievedUtc.ToUniversalTime()) -le $maxAge) { return [PSCustomObject]@{ Succeeded = [bool]$matchedEntry.Succeeded Version = [string]$matchedEntry.Version URI = [string]$matchedEntry.URI ResolvedArtifact = $matchedEntry.ResolvedArtifact FilterExpression = [string]$matchedEntry.FilterExpression Error = [string]$matchedEntry.Error IsFromCache = $true CacheFile = $cacheFile } } } } $liveResult = Get-IntunePackageLatestVersion -DefinitionObject $DefinitionObject $cacheRecord = [PSCustomObject]@{ DefinitionPath = $definitionKey RetrievedUtc = $nowUtc.ToString('o') Succeeded = [bool]$liveResult.Succeeded Version = [string]$liveResult.Version URI = [string]$liveResult.URI ResolvedArtifact = $liveResult.ResolvedArtifact FilterExpression = [string]$liveResult.FilterExpression Error = [string]$liveResult.Error } $newEntries = [System.Collections.Generic.List[object]]::new() foreach ($entry in $cacheEntries) { if ($null -eq $entry) { continue } $entryPath = [string]$entry.DefinitionPath if ([string]::IsNullOrWhiteSpace($entryPath)) { continue } if ($entryPath.Trim().ToLowerInvariant() -eq $definitionKey) { continue } $newEntries.Add($entry) } $newEntries.Add($cacheRecord) try { $json = @($newEntries) | ConvertTo-Json -Depth 8 [System.IO.File]::WriteAllText($cacheFile, $json, [System.Text.Encoding]::UTF8) } catch { # Cache write failure should not fail version resolution. } return [PSCustomObject]@{ Succeeded = [bool]$liveResult.Succeeded Version = [string]$liveResult.Version URI = [string]$liveResult.URI ResolvedArtifact = $liveResult.ResolvedArtifact FilterExpression = [string]$liveResult.FilterExpression Error = [string]$liveResult.Error IsFromCache = $false CacheFile = $cacheFile } } |