Support/Package/Schema/Eigenverft.Manifested.Sandbox.Package.DefinitionSchema.ps1
|
<#
Eigenverft.Manifested.Sandbox.Package.DefinitionSchema Package definition JSON validation (version-dispatched) and release default merge helpers. #> $script:PackageDefinitionSupportedSchemaVersions = @( '1.0' ) function Assert-PackageDefinitionSchemaVersionSupported { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$SchemaVersionText, [Parameter(Mandatory = $true)] [string]$DefinitionDocumentPath ) foreach ($supported in $script:PackageDefinitionSupportedSchemaVersions) { if ([string]::Equals($SchemaVersionText, $supported, [System.StringComparison]::Ordinal)) { return } } $supportedList = ($script:PackageDefinitionSupportedSchemaVersions | ForEach-Object { "'$_'" }) -join ', ' throw "Package definition '$DefinitionDocumentPath' uses unsupported schemaVersion '$SchemaVersionText'. Supported schemaVersion values are $supportedList." } function Assert-PackageDefinitionSchema { <# .SYNOPSIS Validates the Package definition schema for this package pass. .DESCRIPTION Performs cross-version checks (retired names, required top-level fields, schemaVersion) then dispatches to a schema-specific validator. Today only schemaVersion '1.0' is implemented; additional versions add new branches without changing callers. .PARAMETER DefinitionDocumentInfo The loaded Package definition document info. .PARAMETER DefinitionId The expected definition id. .EXAMPLE Assert-PackageDefinitionSchema -DefinitionDocumentInfo $definitionInfo -DefinitionId VSCodeRuntime #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$DefinitionDocumentInfo, [Parameter(Mandatory = $true)] [string]$DefinitionId, [string]$DefinitionRepositoryId = (Get-PackageDefaultRepositoryId) ) $definition = $DefinitionDocumentInfo.Document foreach ($retiredProperty in @('classification', 'target', 'origins', 'interfaces', 'packageType', 'paths', 'sources', 'packages', 'entryPoints', 'packageFamily', 'managedPaths')) { if ($definition.PSObject.Properties[$retiredProperty]) { throw "Package definition '$($DefinitionDocumentInfo.Path)' still uses retired property '$retiredProperty'." } } foreach ($requiredProperty in @('schemaVersion', 'id', 'display', 'upstreamSources', 'providedTools', 'releaseDefaults', 'releases')) { if (-not $definition.PSObject.Properties[$requiredProperty]) { throw "Package definition '$($DefinitionDocumentInfo.Path)' is missing required property '$requiredProperty'." } } $schemaVersionText = [string]$definition.schemaVersion if ([string]::IsNullOrWhiteSpace($schemaVersionText)) { throw "Package definition '$($DefinitionDocumentInfo.Path)' defines schemaVersion, but it is empty." } Assert-PackageDefinitionSchemaVersionSupported -SchemaVersionText $schemaVersionText -DefinitionDocumentPath $DefinitionDocumentInfo.Path switch -Exact ($schemaVersionText) { '1.0' { Assert-PackageDefinitionSchema_1_0 -DefinitionDocumentInfo $DefinitionDocumentInfo -DefinitionId $DefinitionId -DefinitionRepositoryId $DefinitionRepositoryId return } default { throw "Package definition '$($DefinitionDocumentInfo.Path)' encountered unsupported schemaVersion '$schemaVersionText' after validation gate." } } } function Assert-PackageDefinitionSchema_1_0 { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$DefinitionDocumentInfo, [Parameter(Mandatory = $true)] [string]$DefinitionId, [string]$DefinitionRepositoryId = (Get-PackageDefaultRepositoryId) ) $definition = $DefinitionDocumentInfo.Document if (-not [string]::Equals([string]$definition.id, [string]$DefinitionId, [System.StringComparison]::Ordinal)) { throw "Package definition id '$($definition.id)' does not match requested definition id '$DefinitionId'." } if ($definition.PSObject.Properties['dependencies']) { foreach ($dependency in @($definition.dependencies)) { if (-not $dependency.PSObject.Properties['definitionId'] -or [string]::IsNullOrWhiteSpace([string]$dependency.definitionId)) { throw "Package definition '$($definition.id)' has dependency without definitionId." } $dependencyRepositoryId = $DefinitionRepositoryId if ($dependency.PSObject.Properties['repositoryId']) { if ([string]::IsNullOrWhiteSpace([string]$dependency.repositoryId)) { throw "Package definition '$($definition.id)' has dependency '$($dependency.definitionId)' with empty repositoryId." } $dependencyRepositoryId = [string]$dependency.repositoryId } if ([string]::Equals([string]$dependency.definitionId, [string]$definition.id, [System.StringComparison]::OrdinalIgnoreCase) -and [string]::Equals($dependencyRepositoryId, $DefinitionRepositoryId, [System.StringComparison]::OrdinalIgnoreCase)) { throw "Package definition '$($definition.id)' cannot depend on itself." } } } foreach ($upstreamSourceProperty in @($definition.upstreamSources.PSObject.Properties)) { $upstreamSource = $upstreamSourceProperty.Value if (-not $upstreamSource.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$upstreamSource.kind)) { throw "Package definition '$($definition.id)' has upstream source '$($upstreamSourceProperty.Name)' without kind." } switch -Exact ([string]$upstreamSource.kind) { 'download' { if (-not $upstreamSource.PSObject.Properties['baseUri'] -or [string]::IsNullOrWhiteSpace([string]$upstreamSource.baseUri)) { throw "Package definition '$($definition.id)' has download upstream source '$($upstreamSourceProperty.Name)' without baseUri." } } 'githubRelease' { if (-not $upstreamSource.PSObject.Properties['repositoryOwner'] -or [string]::IsNullOrWhiteSpace([string]$upstreamSource.repositoryOwner)) { throw "Package definition '$($definition.id)' has GitHub release upstream source '$($upstreamSourceProperty.Name)' without repositoryOwner." } if (-not $upstreamSource.PSObject.Properties['repositoryName'] -or [string]::IsNullOrWhiteSpace([string]$upstreamSource.repositoryName)) { throw "Package definition '$($definition.id)' has GitHub release upstream source '$($upstreamSourceProperty.Name)' without repositoryName." } } default { throw "Package definition '$($definition.id)' uses unsupported upstream source kind '$($upstreamSource.kind)' for '$($upstreamSourceProperty.Name)'." } } } if ($definition.releaseDefaults.PSObject.Properties['requirements']) { throw "Package definition '$($definition.id)' still uses retired property 'releaseDefaults.requirements'. Use 'releaseDefaults.compatibility.checks'." } foreach ($requiredDefaultProperty in @('compatibility', 'install', 'validation', 'existingInstallDiscovery', 'existingInstallPolicy')) { if (-not $definition.releaseDefaults.PSObject.Properties[$requiredDefaultProperty]) { throw "Package definition '$($definition.id)' is missing releaseDefaults.$requiredDefaultProperty." } } foreach ($retiredDefaultProperty in @('existingInstall')) { if ($definition.releaseDefaults.PSObject.Properties[$retiredDefaultProperty]) { throw "Package definition '$($definition.id)' still uses retired property 'releaseDefaults.$retiredDefaultProperty'." } } foreach ($release in @($definition.releases)) { foreach ($retiredProperty in @('artifact', 'acquisitions', 'sourceOptions', 'reuse', 'channel')) { if ($release.PSObject.Properties[$retiredProperty]) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property '$retiredProperty'." } } if ($release.PSObject.Properties['requirements']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'requirements'. Use 'compatibility.checks'." } foreach ($retiredReleaseProperty in @('existingInstall')) { if ($release.PSObject.Properties[$retiredReleaseProperty]) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property '$retiredReleaseProperty'." } } foreach ($requiredProperty in @('id', 'version', 'releaseTrack', 'flavor', 'constraints')) { if (-not $release.PSObject.Properties[$requiredProperty]) { throw "Package release '$($release.id)' in '$($definition.id)' is missing required property '$requiredProperty'." } } $effectiveRelease = Resolve-PackageEffectiveRelease -Definition $definition -Release $release foreach ($requiredEffectiveProperty in @('install', 'validation', 'compatibility', 'existingInstallDiscovery', 'existingInstallPolicy')) { if (-not $effectiveRelease.PSObject.Properties[$requiredEffectiveProperty]) { throw "Package release '$($release.id)' in '$($definition.id)' is missing required effective property '$requiredEffectiveProperty'." } } if ($effectiveRelease.compatibility.PSObject.Properties['packages']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'compatibility.packages'. Use 'compatibility.checks'." } if (-not $effectiveRelease.compatibility.PSObject.Properties['checks']) { throw "Package release '$($release.id)' in '$($definition.id)' is missing compatibility.checks." } foreach ($compatibilityCheck in @($effectiveRelease.compatibility.checks)) { if ($null -eq $compatibilityCheck) { continue } if (-not $compatibilityCheck.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' has a compatibility check without kind." } $onFail = 'fail' if ($compatibilityCheck.PSObject.Properties['onFail'] -and -not [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.onFail)) { $onFail = ([string]$compatibilityCheck.onFail).ToLowerInvariant() } if ($onFail -notin @('fail', 'warn')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported compatibility onFail '$($compatibilityCheck.onFail)'." } switch -Exact ([string]$compatibilityCheck.kind) { 'osFamily' { $hasAllowed = $compatibilityCheck.PSObject.Properties['allowed'] -and @($compatibilityCheck.allowed).Count -gt 0 $hasBlocked = $compatibilityCheck.PSObject.Properties['blocked'] -and @($compatibilityCheck.blocked).Count -gt 0 if (-not $hasAllowed -and -not $hasBlocked) { throw "Package release '$($release.id)' in '$($definition.id)' has an osFamily compatibility check without allowed or blocked values." } } 'cpuArchitecture' { $hasAllowed = $compatibilityCheck.PSObject.Properties['allowed'] -and @($compatibilityCheck.allowed).Count -gt 0 $hasBlocked = $compatibilityCheck.PSObject.Properties['blocked'] -and @($compatibilityCheck.blocked).Count -gt 0 if (-not $hasAllowed -and -not $hasBlocked) { throw "Package release '$($release.id)' in '$($definition.id)' has a cpuArchitecture compatibility check without allowed or blocked values." } } 'osVersion' { if (-not $compatibilityCheck.PSObject.Properties['operator'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.operator)) { throw "Package release '$($release.id)' in '$($definition.id)' has an osVersion compatibility check without operator." } if (-not $compatibilityCheck.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.value)) { throw "Package release '$($release.id)' in '$($definition.id)' has an osVersion compatibility check without value." } } 'physicalMemoryGiB' { if (-not $compatibilityCheck.PSObject.Properties['operator'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.operator)) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalMemoryGiB compatibility check without operator." } if (-not $compatibilityCheck.PSObject.Properties['value']) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalMemoryGiB compatibility check without value." } $parsedValue = 0.0 if (-not [double]::TryParse(([string]$compatibilityCheck.value), [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsedValue)) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalMemoryGiB compatibility check with non-numeric value '$($compatibilityCheck.value)'." } } 'videoMemoryGiB' { if (-not $compatibilityCheck.PSObject.Properties['operator'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.operator)) { throw "Package release '$($release.id)' in '$($definition.id)' has a videoMemoryGiB compatibility check without operator." } if (-not $compatibilityCheck.PSObject.Properties['value']) { throw "Package release '$($release.id)' in '$($definition.id)' has a videoMemoryGiB compatibility check without value." } $parsedValue = 0.0 if (-not [double]::TryParse(([string]$compatibilityCheck.value), [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsedValue)) { throw "Package release '$($release.id)' in '$($definition.id)' has a videoMemoryGiB compatibility check with non-numeric value '$($compatibilityCheck.value)'." } } 'physicalOrVideoMemoryGiB' { if (-not $compatibilityCheck.PSObject.Properties['operator'] -or [string]::IsNullOrWhiteSpace([string]$compatibilityCheck.operator)) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalOrVideoMemoryGiB compatibility check without operator." } if (-not $compatibilityCheck.PSObject.Properties['value']) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalOrVideoMemoryGiB compatibility check without value." } $parsedValue = 0.0 if (-not [double]::TryParse(([string]$compatibilityCheck.value), [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsedValue)) { throw "Package release '$($release.id)' in '$($definition.id)' has a physicalOrVideoMemoryGiB compatibility check with non-numeric value '$($compatibilityCheck.value)'." } } default { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported compatibility kind '$($compatibilityCheck.kind)'." } } } if ($effectiveRelease.existingInstallPolicy -and $effectiveRelease.existingInstallPolicy.PSObject.Properties['requireManagedOwnership']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'requireManagedOwnership'. Use 'requirePackageOwnership'." } $installKind = if ($effectiveRelease.install -and $effectiveRelease.install.PSObject.Properties['kind']) { [string]$effectiveRelease.install.kind } else { $null } if ([string]::IsNullOrWhiteSpace($installKind)) { throw "Package release '$($release.id)' in '$($definition.id)' is missing install.kind." } if ($installKind -notin @('expandArchive', 'placePackageFile', 'runInstaller', 'nsisInstaller', 'npmGlobalPackage', 'reuseExisting')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported install.kind '$installKind'." } foreach ($retiredInstallProperty in @('managerKind', 'managerDependency')) { if ($effectiveRelease.install.PSObject.Properties[$retiredInstallProperty]) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'install.$retiredInstallProperty'. Use install.kind 'npmGlobalPackage' with install.installerCommand." } } if ($effectiveRelease.install.PSObject.Properties['targetKind'] -and -not [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.targetKind) -and ([string]$effectiveRelease.install.targetKind) -notin @('directory', 'machinePrerequisite')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported install.targetKind '$($effectiveRelease.install.targetKind)'." } if ($effectiveRelease.install.PSObject.Properties['elevation'] -and -not [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.elevation) -and ([string]$effectiveRelease.install.elevation) -notin @('none', 'required', 'auto')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported install.elevation '$($effectiveRelease.install.elevation)'." } if ([string]::Equals($installKind, 'npmGlobalPackage', [System.StringComparison]::OrdinalIgnoreCase)) { if (-not $effectiveRelease.install.PSObject.Properties['packageSpec'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.packageSpec)) { throw "Package release '$($release.id)' in '$($definition.id)' uses install.kind 'npmGlobalPackage' without install.packageSpec." } if (-not $effectiveRelease.install.PSObject.Properties['installerCommand'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.installerCommand)) { throw "Package release '$($release.id)' in '$($definition.id)' uses install.kind 'npmGlobalPackage' without install.installerCommand." } } if ([string]::Equals($installKind, 'nsisInstaller', [System.StringComparison]::OrdinalIgnoreCase)) { if ($effectiveRelease.install.PSObject.Properties['targetDirectoryArgument'] -and $null -ne $effectiveRelease.install.targetDirectoryArgument) { $targetDirectoryArgument = $effectiveRelease.install.targetDirectoryArgument if ($targetDirectoryArgument.PSObject.Properties['prefix'] -and [string]::IsNullOrWhiteSpace([string]$targetDirectoryArgument.prefix)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.targetDirectoryArgument.prefix without a value." } } } if ($effectiveRelease.install -and $effectiveRelease.install.PSObject.Properties['pathRegistration'] -and $null -ne $effectiveRelease.install.pathRegistration) { $pathRegistration = $effectiveRelease.install.pathRegistration if (-not $pathRegistration.PSObject.Properties['mode'] -or [string]::IsNullOrWhiteSpace([string]$pathRegistration.mode)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration without mode." } $pathRegistrationMode = ([string]$pathRegistration.mode).ToLowerInvariant() if ($pathRegistrationMode -notin @('none', 'user', 'machine')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported install.pathRegistration.mode '$($pathRegistration.mode)'." } if ($pathRegistrationMode -ne 'none') { if (-not $pathRegistration.PSObject.Properties['source'] -or $null -eq $pathRegistration.source) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration.mode '$($pathRegistration.mode)' without source." } if (-not $pathRegistration.source.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$pathRegistration.source.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration without source.kind." } switch -Exact ([string]$pathRegistration.source.kind) { 'commandEntryPoint' { if (-not $pathRegistration.source.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$pathRegistration.source.value)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source kind 'commandEntryPoint' without source.value." } if ($pathRegistration.source.PSObject.Properties['values']) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source.values for source kind 'commandEntryPoint'." } } 'appEntryPoint' { if (-not $pathRegistration.source.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$pathRegistration.source.value)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source kind 'appEntryPoint' without source.value." } if ($pathRegistration.source.PSObject.Properties['values']) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source.values for source kind 'appEntryPoint'." } } 'installRelativeDirectory' { if (-not $pathRegistration.source.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$pathRegistration.source.value)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source kind 'installRelativeDirectory' without source.value." } if ($pathRegistration.source.PSObject.Properties['values']) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source.values for source kind 'installRelativeDirectory'." } } 'shim' { $hasShimValue = $pathRegistration.source.PSObject.Properties['value'] -and -not [string]::IsNullOrWhiteSpace([string]$pathRegistration.source.value) $hasShimValues = $false if ($pathRegistration.source.PSObject.Properties['values'] -and $null -ne $pathRegistration.source.values) { $hasShimValues = @($pathRegistration.source.values | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }).Count -gt 0 } if (-not $hasShimValue -and -not $hasShimValues) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.pathRegistration source kind 'shim' without source.value or source.values." } } default { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported install.pathRegistration.source.kind '$($pathRegistration.source.kind)'." } } } } $requiresPackageFile = $false $requiresAcquisitionCandidates = $false switch -Exact ($installKind) { 'expandArchive' { $requiresPackageFile = $true $requiresAcquisitionCandidates = $true } 'placePackageFile' { $requiresPackageFile = $true $requiresAcquisitionCandidates = $true if ($effectiveRelease.install.PSObject.Properties['targetRelativePath'] -and [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.targetRelativePath)) { throw "Package release '$($release.id)' in '$($definition.id)' defines install.targetRelativePath without a value." } } 'runInstaller' { if (-not $effectiveRelease.install.PSObject.Properties['commandPath'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.install.commandPath)) { $requiresPackageFile = $true $requiresAcquisitionCandidates = $true } } 'nsisInstaller' { $requiresPackageFile = $true $requiresAcquisitionCandidates = $true } } if ($requiresPackageFile) { if (-not $effectiveRelease.PSObject.Properties['packageFile'] -or $null -eq $effectiveRelease.packageFile) { throw "Package release '$($release.id)' in '$($definition.id)' is missing required property 'packageFile'." } if (-not $effectiveRelease.packageFile.PSObject.Properties['fileName'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.packageFile.fileName)) { throw "Package release '$($release.id)' in '$($definition.id)' is missing packageFile.fileName." } } if ($effectiveRelease.PSObject.Properties['packageFile'] -and $effectiveRelease.packageFile -and (-not $effectiveRelease.packageFile.PSObject.Properties['fileName'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.packageFile.fileName))) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile without packageFile.fileName." } if ($effectiveRelease.PSObject.Properties['packageFile'] -and $effectiveRelease.packageFile) { if ($effectiveRelease.packageFile.PSObject.Properties['autoUpdateSupported']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'packageFile.autoUpdateSupported'." } if ($effectiveRelease.packageFile.PSObject.Properties['integrity']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'packageFile.integrity'. Use 'packageFile.contentHash'." } if ($effectiveRelease.packageFile.PSObject.Properties['authenticode']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'packageFile.authenticode'. Use 'packageFile.publisherSignature'." } } if ($effectiveRelease.PSObject.Properties['packageFile'] -and $effectiveRelease.packageFile -and $effectiveRelease.packageFile.PSObject.Properties['contentHash'] -and $null -ne $effectiveRelease.packageFile.contentHash) { $contentHash = $effectiveRelease.packageFile.contentHash if (-not $contentHash.PSObject.Properties['algorithm'] -or [string]::IsNullOrWhiteSpace([string]$contentHash.algorithm)) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile.contentHash without algorithm." } if (-not $contentHash.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$contentHash.value)) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile.contentHash without value." } if (-not [string]::Equals([string]$contentHash.algorithm, 'sha256', [System.StringComparison]::OrdinalIgnoreCase)) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported packageFile.contentHash.algorithm '$($contentHash.algorithm)'." } } if ($effectiveRelease.PSObject.Properties['packageFile'] -and $effectiveRelease.packageFile -and $effectiveRelease.packageFile.PSObject.Properties['publisherSignature'] -and $null -ne $effectiveRelease.packageFile.publisherSignature) { $publisherSignature = $effectiveRelease.packageFile.publisherSignature if (-not $publisherSignature.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$publisherSignature.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile.publisherSignature without kind." } if (-not [string]::Equals([string]$publisherSignature.kind, 'authenticode', [System.StringComparison]::OrdinalIgnoreCase)) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported packageFile.publisherSignature.kind '$($publisherSignature.kind)'." } if ($publisherSignature.PSObject.Properties['requireValid'] -and $null -eq $publisherSignature.requireValid) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile.publisherSignature.requireValid without a value." } if ($publisherSignature.PSObject.Properties['subjectContains'] -and [string]::IsNullOrWhiteSpace([string]$publisherSignature.subjectContains)) { throw "Package release '$($release.id)' in '$($definition.id)' defines packageFile.publisherSignature.subjectContains without a value." } } if ($requiresAcquisitionCandidates) { if (-not $effectiveRelease.PSObject.Properties['acquisitionCandidates'] -or @($effectiveRelease.acquisitionCandidates).Count -eq 0) { throw "Package release '$($release.id)' in '$($definition.id)' is missing required property 'acquisitionCandidates'." } } if ($effectiveRelease.PSObject.Properties['acquisitionCandidates']) { foreach ($candidate in @($effectiveRelease.acquisitionCandidates)) { if ($null -eq $candidate) { continue } if ($candidate.PSObject.Properties['sourceBindingId']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'sourceBindingId'." } if ($candidate.PSObject.Properties['sourceRef']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired property 'sourceRef'." } if ($candidate.PSObject.Properties['priority']) { throw "Package release '$($release.id)' in '$($definition.id)' acquisition candidate still uses retired property 'priority'. Use 'searchOrder'." } if (-not $candidate.PSObject.Properties['searchOrder']) { throw "Package release '$($release.id)' in '$($definition.id)' has an acquisition candidate without searchOrder." } if (-not $candidate.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$candidate.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' has an acquisition candidate without kind." } switch -Exact ([string]$candidate.kind) { 'packageDepot' { } 'download' { if (-not $candidate.PSObject.Properties['sourceId'] -or [string]::IsNullOrWhiteSpace([string]$candidate.sourceId)) { throw "Package release '$($release.id)' in '$($definition.id)' has a download acquisition candidate without sourceId." } $downloadSource = $null foreach ($upstreamSourceProperty in @($definition.upstreamSources.PSObject.Properties)) { if ([string]::Equals([string]$upstreamSourceProperty.Name, [string]$candidate.sourceId, [System.StringComparison]::OrdinalIgnoreCase)) { $downloadSource = $upstreamSourceProperty.Value break } } if (-not $downloadSource) { throw "Package release '$($release.id)' in '$($definition.id)' references unknown download sourceId '$($candidate.sourceId)'." } $downloadSourceKind = if ($downloadSource.PSObject.Properties['kind']) { [string]$downloadSource.kind } else { $null } switch -Exact ($downloadSourceKind) { 'download' { if (-not $candidate.PSObject.Properties['sourcePath'] -or [string]::IsNullOrWhiteSpace([string]$candidate.sourcePath)) { throw "Package release '$($release.id)' in '$($definition.id)' has a download acquisition candidate without sourcePath." } } 'githubRelease' { if ($candidate.PSObject.Properties['sourcePath'] -and -not [string]::IsNullOrWhiteSpace([string]$candidate.sourcePath)) { throw "Package release '$($release.id)' in '$($definition.id)' must not define sourcePath for GitHub release source '$($candidate.sourceId)'." } if (-not $effectiveRelease.PSObject.Properties['releaseTag'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.releaseTag)) { throw "Package release '$($release.id)' in '$($definition.id)' requires releaseTag when download source '$($candidate.sourceId)' is a GitHub release source." } if (-not $effectiveRelease.PSObject.Properties['packageFile'] -or $null -eq $effectiveRelease.packageFile -or -not $effectiveRelease.packageFile.PSObject.Properties['fileName'] -or [string]::IsNullOrWhiteSpace([string]$effectiveRelease.packageFile.fileName)) { throw "Package release '$($release.id)' in '$($definition.id)' requires packageFile.fileName when download source '$($candidate.sourceId)' is a GitHub release source." } } default { throw "Package release '$($release.id)' in '$($definition.id)' references unsupported download source kind '$downloadSourceKind' for sourceId '$($candidate.sourceId)'." } } } 'filesystem' { if (-not $candidate.PSObject.Properties['sourcePath'] -or [string]::IsNullOrWhiteSpace([string]$candidate.sourcePath)) { throw "Package release '$($release.id)' in '$($definition.id)' has a filesystem acquisition candidate without sourcePath." } } default { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported acquisition kind '$($candidate.kind)'." } } } } $existingInstallDiscovery = $effectiveRelease.existingInstallDiscovery if ($existingInstallDiscovery.PSObject.Properties['enableDetection'] -and [bool]$existingInstallDiscovery.enableDetection) { if (-not $existingInstallDiscovery.PSObject.Properties['searchLocations']) { throw "Package release '$($release.id)' in '$($definition.id)' is missing existingInstallDiscovery.searchLocations." } if (-not $existingInstallDiscovery.PSObject.Properties['installRootRules']) { throw "Package release '$($release.id)' in '$($definition.id)' is missing existingInstallDiscovery.installRootRules." } foreach ($searchLocation in @($existingInstallDiscovery.searchLocations)) { if ($null -eq $searchLocation) { continue } if (-not $searchLocation.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' has an existingInstallDiscovery.searchLocation without kind." } switch -Exact ([string]$searchLocation.kind) { 'command' { if (-not $searchLocation.PSObject.Properties['name'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.name)) { throw "Package release '$($release.id)' in '$($definition.id)' has a command searchLocation without name." } } 'path' { if (-not $searchLocation.PSObject.Properties['path'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.path)) { throw "Package release '$($release.id)' in '$($definition.id)' has a path searchLocation without path." } } 'directory' { if (-not $searchLocation.PSObject.Properties['path'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.path)) { throw "Package release '$($release.id)' in '$($definition.id)' has a directory searchLocation without path." } } 'windowsUninstallRegistryKey' { if (-not $searchLocation.PSObject.Properties['paths'] -or @($searchLocation.paths).Count -eq 0) { throw "Package release '$($release.id)' in '$($definition.id)' has a windowsUninstallRegistryKey searchLocation without paths." } if (-not $searchLocation.PSObject.Properties['installDirectorySource'] -or [string]::IsNullOrWhiteSpace([string]$searchLocation.installDirectorySource)) { throw "Package release '$($release.id)' in '$($definition.id)' has a windowsUninstallRegistryKey searchLocation without installDirectorySource." } if ([string]$searchLocation.installDirectorySource -notin @('installLocation', 'displayIconDirectory', 'uninstallStringDirectory')) { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported windowsUninstallRegistryKey installDirectorySource '$($searchLocation.installDirectorySource)'." } } default { throw "Package release '$($release.id)' in '$($definition.id)' uses unsupported existingInstallDiscovery.searchLocation kind '$($searchLocation.kind)'." } } } foreach ($rule in @($existingInstallDiscovery.installRootRules)) { if ($null -eq $rule) { continue } if ($rule.PSObject.Properties['fileName'] -or $rule.PSObject.Properties['homePath']) { throw "Package release '$($release.id)' in '$($definition.id)' still uses retired installRootRules fields from installHomeRules." } if (-not $rule.PSObject.Properties['match'] -or $null -eq $rule.match) { throw "Package release '$($release.id)' in '$($definition.id)' has an installRootRule without match." } if (-not $rule.match.PSObject.Properties['kind'] -or [string]::IsNullOrWhiteSpace([string]$rule.match.kind)) { throw "Package release '$($release.id)' in '$($definition.id)' has an installRootRule without match.kind." } if (-not $rule.match.PSObject.Properties['value'] -or [string]::IsNullOrWhiteSpace([string]$rule.match.value)) { throw "Package release '$($release.id)' in '$($definition.id)' has an installRootRule without match.value." } if (-not $rule.PSObject.Properties['installRootRelativePath']) { throw "Package release '$($release.id)' in '$($definition.id)' has an installRootRule without installRootRelativePath." } } } } } function Resolve-PackageEffectiveRelease { <# .SYNOPSIS Builds the effective Package release by applying definition defaults. .DESCRIPTION Applies whole-block fallback from the definition releaseDefaults block to a single release entry. When a release defines one of the known release blocks, that block fully replaces the default block. .PARAMETER Definition The Package definition object. .PARAMETER Release The raw release object from the definition. .EXAMPLE Resolve-PackageEffectiveRelease -Definition $definition -Release $release #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [psobject]$Definition, [Parameter(Mandatory = $true)] [psobject]$Release ) $effectiveRelease = ConvertTo-PackageObject -InputObject $Release foreach ($propertyName in @('compatibility', 'install', 'validation', 'existingInstallDiscovery', 'existingInstallPolicy')) { if (-not $effectiveRelease.PSObject.Properties[$propertyName] -and $Definition.releaseDefaults.PSObject.Properties[$propertyName]) { $effectiveRelease | Add-Member -MemberType NoteProperty -Name $propertyName -Value (ConvertTo-PackageObject -InputObject $Definition.releaseDefaults.$propertyName) } } return $effectiveRelease } |