Public/Add-OSDAppPackage.ps1
|
function Add-OSDAppPackage { [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)][string]$Id, [Parameter(Mandatory)][string]$DisplayName, [string]$Vendor, [Parameter(Mandatory)][string]$Version, [Parameter(Mandatory)][string]$PackagePath, [Parameter(Mandatory)][string]$RepositoryPath, [ValidateSet('x64','arm64','any')] [string]$Architecture = 'x64', [ValidateSet('Application','Script')] [string]$PackageType, [int[]]$SuccessCodes = @(0,3010), [string[]]$Alias ) if ($Id -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$' -or $Id -match '\.\.') { throw "Invalid package Id '$Id'." } $packageTest = Test-OSDAppPackage -PackagePath $PackagePath if (-not $packageTest.Valid) { throw 'Package validation failed. Package.zip must contain Package/Install.ps1.' } $targetFolder = Join-Path $RepositoryPath (Join-Path 'Apps' (Join-Path $Id (Join-Path $Version $Architecture))) $targetPackage = Join-Path $targetFolder 'Package.zip' $targetManifest = Join-Path $targetFolder 'manifest.json' $catalog = Get-OSDAppRepositoryManifest -RepositoryPath $RepositoryPath $existingApplication = @($catalog.Applications | Where-Object { $_.Id -eq $Id } | Select-Object -First 1) if ($PSBoundParameters.ContainsKey('PackageType')) { $PackageType = if ($PackageType -ieq 'Script') { 'Script' } else { 'Application' } } elseif (Test-Path -LiteralPath $targetManifest -PathType Leaf) { $existingPackageManifest = Get-OSDAppManifest -Path $targetManifest $existingPackageType = if ($existingPackageManifest.PSObject.Properties.Name -contains 'PackageType' -and -not [string]::IsNullOrWhiteSpace([string]$existingPackageManifest.PackageType)) { [string]$existingPackageManifest.PackageType } else { 'Application' } if ($existingPackageType -notin @('Application','Script')) { throw "Existing package '$Id' has unsupported PackageType '$existingPackageType'." } $PackageType = if ($existingPackageType -ieq 'Script') { 'Script' } else { 'Application' } } else { $PackageType = 'Application' } $effectiveAliases = if ($PSBoundParameters.ContainsKey('Alias')) { @($Alias | ForEach-Object { ([string]$_).Trim() } | Select-Object -Unique) } elseif ($existingApplication.Count -gt 0 -and $existingApplication[0].PSObject.Properties.Name -contains 'Aliases') { @($existingApplication[0].Aliases) } else { @() } foreach ($aliasValue in $effectiveAliases) { $aliasId = [string]$aliasValue if ([string]::IsNullOrWhiteSpace($aliasId) -or $aliasId -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$' -or $aliasId -match '\.\.') { throw "Invalid alias '$aliasId' for '$Id'." } if ($aliasId -ieq $Id) { throw "Alias '$aliasId' cannot be the same as canonical Id '$Id'." } } foreach ($application in @($catalog.Applications | Where-Object { $_.Id -ne $Id })) { if ([string]$application.Id -ieq $Id) { continue } if ([string]$application.Id -in $effectiveAliases) { throw "Alias '$($application.Id)' for '$Id' conflicts with an existing canonical application Id." } $otherAliases = @( if ($application.PSObject.Properties.Name -contains 'Aliases') { @($application.Aliases) } ) if ($Id -in $otherAliases) { throw "Canonical Id '$Id' is already an alias of '$($application.Id)'." } $conflicts = @($effectiveAliases | Where-Object { $_ -in $otherAliases }) if ($conflicts.Count -gt 0) { throw "Alias '$($conflicts[0])' is already assigned to '$($application.Id)'." } } if (-not $PSCmdlet.ShouldProcess($targetFolder, "Publish $Id $Version ($Architecture)")) { return } if (-not (Test-Path -LiteralPath (Join-Path $RepositoryPath 'catalog.json') -PathType Leaf)) { New-OSDAppRepository -Path $RepositoryPath -Confirm:$false | Out-Null } New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null Copy-Item -LiteralPath $PackagePath -Destination $targetPackage -Force $hash = (Get-FileHash -LiteralPath $targetPackage -Algorithm SHA256).Hash $packageManifest = [ordered]@{ SchemaVersion = 1 Id = $Id DisplayName = $DisplayName Version = $Version Architecture = $Architecture PackageType = $PackageType SuccessCodes = @($SuccessCodes) Archive = [ordered]@{ FileName = 'Package.zip' Sha256 = $hash } } # Vendor is optional descriptive metadata, not an installer parameter. # Older repositories remain compatible when it is absent. if (-not [string]::IsNullOrWhiteSpace($Vendor)) { $packageManifest['Vendor'] = $Vendor.Trim() } $packageManifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $targetManifest -Encoding UTF8 if ($existingApplication.Count -gt 0) { $applicationPackages = @( $existingApplication[0].Packages | Where-Object { ([string]$_.Architecture).ToLowerInvariant() -ne $Architecture.ToLowerInvariant() } ) } else { $applicationPackages = @() } $manifestRelativePath = "Apps/$Id/$Version/$Architecture/manifest.json" $packageReference = [pscustomobject]@{ Architecture = $Architecture Manifest = $manifestRelativePath } $applicationEntryData = [ordered]@{ Id = $Id Packages = @($applicationPackages) + $packageReference } if ($effectiveAliases.Count -gt 0) { $applicationEntryData['Aliases'] = @($effectiveAliases) } $applicationEntry = [pscustomobject]$applicationEntryData $catalog.Applications = @( @($catalog.Applications | Where-Object { $_.Id -ne $Id }) + $applicationEntry ) Save-OSDAppRepositoryManifest -Manifest $catalog -RepositoryPath $RepositoryPath | Out-Null [pscustomobject]@{ PSTypeName = 'OSDApps.RepositoryPackage' Id = $Id DisplayName = $DisplayName Vendor = if ($Vendor) { $Vendor.Trim() } else { $null } Version = $Version Architecture = $Architecture PackageType = $PackageType SuccessCodes = @($SuccessCodes) Aliases = @($effectiveAliases) ManifestPath = $manifestRelativePath Archive = [pscustomobject]$packageManifest.Archive } } |