Private/ConvertTo-HDTApplicationCatalog.ps1
|
function ConvertTo-HDTApplicationCatalog { <# .SYNOPSIS Projects a validated app.yaml document into the catalog object the engine works with. .DESCRIPTION EVERY DEFAULT IN THE APPLICATION MODEL LIVES HERE, and nowhere else. DESIGN 8 makes successCodes, rebootCodes, runIn, uninstall, detect and dependencies all optional, so most app.yaml files in a real workspace declare four keys and inherit the rest. What they inherit: successCodes 0 and 3010 rebootCodes 3010 runIn FullOS uninstall empty - nothing to run detect $null - install every time dependencies empty 3010 IS A SUCCESS, NOT A FAILURE. It means "installed, reboot required", and an engine that classified it as a failure would scrap a build over an install that worked. It is in both default lists on purpose: successCodes says the install succeeded, rebootCodes says the sequence owes it a restart. DETECT PROJECTS TO $null WHEN THE FILE DECLARES NONE. That is the signal the install step reads as "install unconditionally", and it is DESIGN 8's stated behaviour rather than a missing feature - the engine never infers a rule for an application that declined to declare one. When a rule IS declared, every key its type allows is projected, empty where the file left it out, so the step reads a stable shape under Set-StrictMode instead of testing for property existence. SourcePath IS THE SAME SEAM ConvertTo-HDTOperatingSystemCatalog CLOSED. An application's payload is Applications\<id>\source by convention (DESIGN 2.1) rather than by a key in the file, and with a provider it is what the provider answered for that path relative to the provider's own root. Without one it is resolved against the workspace, which is what an administrator on a workstation gets. .PARAMETER Document The validated document, as ConvertFrom-HDTYaml returns it. .PARAMETER AppFolder The application folder, built with Get-HDTWorkspacePath. .PARAMETER CatalogPath The app.yaml the document came from. .PARAMETER Content An IContentProvider, or nothing. .OUTPUTS System.Management.Automation.PSCustomObject .EXAMPLE ConvertTo-HDTApplicationCatalog -Document $document -AppFolder $folder -CatalogPath $path #> [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNull()] [System.Collections.IDictionary] $Document, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [string] $AppFolder, [Parameter(Mandatory = $true, Position = 2)] [ValidateNotNullOrEmpty()] [string] $CatalogPath, [Parameter()] [AllowNull()] [object] $Content = $null ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $read = { param($Key) if ($Document.Contains($Key)) { return $Document[$Key] } return $null } # -- the exit codes ------------------------------------------------------- $successCode = @(0, 3010) if ($null -ne (& $read 'successCodes')) { $successCode = @(@($Document['successCodes']) | ForEach-Object { [int] $_ }) } $rebootCode = @(3010) if ($null -ne (& $read 'rebootCodes')) { $rebootCode = @(@($Document['rebootCodes']) | ForEach-Object { [int] $_ }) } # -- the detection rule --------------------------------------------------- $detect = $null if ($null -ne (& $read 'detect')) { $rule = $Document['detect'] $type = [string] $rule['type'] $schema = Get-HDTApplicationDetectKey $projection = [ordered] @{ Type = $type } foreach ($key in @($schema[$type].Required + $schema[$type].Optional)) { $value = '' if ($rule.Contains($key)) { $value = [string] $rule[$key] } # 'productCode' -> 'ProductCode'. The engine reads PascalCase off an # object; the file is authored in the camelCase every other HDT # document uses. $projection[($key.Substring(0, 1).ToUpperInvariant() + $key.Substring(1))] = $value } $detect = [pscustomobject] $projection } # -- the dependencies ----------------------------------------------------- $dependency = @() if ($null -ne (& $read 'dependencies')) { $dependency = @(@($Document['dependencies']) | ForEach-Object { [string] $_ }) } # -- the payload folder --------------------------------------------------- # [IO.Path]::Combine, not Join-Path, for the reason Get-HDTWorkspacePath # gives: building a path must not require the drive to be mounted. $sourcePath = [System.IO.Path]::Combine($AppFolder, 'source') if ($null -ne $Content) { # The provider answers for paths relative to ITS root, so the application # folder is made relative to that root first. A folder that is not under # the provider's root stays absolute and the provider returns it # unchanged. $folder = $AppFolder $root = [string] $Content.Root if ((-not [string]::IsNullOrEmpty($root)) -and $folder.StartsWith($root, [System.StringComparison]::OrdinalIgnoreCase)) { $folder = $folder.Substring($root.Length).TrimStart('\', '/') } $sourcePath = [string] $Content.ResolveContent([System.IO.Path]::Combine($folder, 'source')) } $runIn = 'FullOS' if (-not [string]::IsNullOrWhiteSpace([string] (& $read 'runIn'))) { $runIn = [string] $Document['runIn'] } return [pscustomobject] @{ SchemaVersion = [int] (& $read 'schemaVersion') Id = [string] (& $read 'id') Name = [string] (& $read 'name') Description = [string] (& $read 'description') # WHO MAKES IT AND WHICH VERSION, which Workbench asks for and composes # both names from - see Get-HDTApplicationName. Kept on the document so # a catalog somebody has been running for a year can still say what a # given entry actually is. Publisher = [string] (& $read 'publisher') Version = [string] (& $read 'version') # WHICH FOLDER THE CONSOLE DRAWS IT UNDER, and nothing else reads it. # See Import-HDTSequenceDocument for why HDT's folders are labels on a # document rather than real directories the way Workbench's are. Folder = [string] (& $read 'folder') Install = [string] (& $read 'install') Uninstall = [string] (& $read 'uninstall') SuccessCodes = [int[]] $successCode RebootCodes = [int[]] $rebootCode Detect = $detect Dependencies = [string[]] $dependency RunIn = $runIn Path = $CatalogPath AppFolder = $AppFolder SourcePath = $sourcePath } } |