Public/Add-OSDAppMicrosoftCompanyPortal.ps1

function Add-OSDAppMicrosoftCompanyPortal {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [ValidateSet('x64','arm64')][string]$Architecture = 'x64',
        [string]$WindowsPath
    )

    $target = Resolve-OSDAppWindowsPath -WindowsPath $WindowsPath
    $cachePath = Get-OSDAppCachePath
    $source = Join-Path $cachePath ('BuiltIn\MicrosoftCompanyPortal\' + $Architecture)
    $cacheInfoPath = Join-Path $source 'CacheInfo.json'
    if (-not (Test-Path -LiteralPath $cacheInfoPath -PathType Leaf)) {
        throw ('Company Portal {0} is not cached. Run Sync-OSDAppMicrosoftCompanyPortal -KitPath on full Windows first.' -f $Architecture)
    }
    $info = Get-Content -LiteralPath $cacheInfoPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
    if ($info.Id -ne 'MicrosoftCompanyPortal' -or $info.Architecture -ne $Architecture -or
        @($info.Files).Count -ne 7 -or $info.DependencyCount -ne 5) {
        throw 'Invalid Company Portal cache metadata.'
    }
    $stage = Join-Path $target 'Windows\Temp\OSDApps'
    $relativeRoot = 'BuiltIn\MicrosoftCompanyPortal\' + $Architecture
    $destination = Join-Path $stage $relativeRoot
    foreach ($file in @($info.Files)) {
        $relative = [string]$file.Path
        if ([string]::IsNullOrWhiteSpace($relative) -or
            [IO.Path]::IsPathRooted($relative) -or $relative -match '(^|[\\/])(\.|\.{2})([\\/]|$)' -or
            $relative -match '[:*?"<>|]' -or [string]$file.Sha256 -notmatch '^[a-fA-F0-9]{64}$') {
            throw ('Unsafe Company Portal cache entry: {0}' -f $relative)
        }
        $path = Join-Path $source $relative
        if (-not (Test-Path -LiteralPath $path -PathType Leaf) -or
            (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -ne $file.Sha256) {
            throw ('Company Portal cache hash validation failed: {0}' -f $relative)
        }
    }
    if (-not $PSCmdlet.ShouldProcess($destination, 'Stage Company Portal for SetupComplete')) { return }
    New-Item -ItemType Directory -Path $stage -Force | Out-Null
    if (Test-Path -LiteralPath $destination) { Remove-Item -LiteralPath $destination -Recurse -Force }
    New-Item -ItemType Directory -Path (Split-Path $destination -Parent) -Force | Out-Null
    Copy-Item -LiteralPath $source -Destination $destination -Recurse -Force -ErrorAction Stop

    $manifestPath = Join-Path $stage 'DeviceManifest.json'
    $manifest = if (Test-Path -LiteralPath $manifestPath) {
        Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json -ErrorAction Stop
    } else {
        [pscustomobject]@{ SchemaVersion=1; StagedAt=(Get-Date).ToUniversalTime().ToString('o'); Apps=@() }
    }
    $fileHashes = @(
        foreach ($file in @($info.Files)) {
            [pscustomobject]@{
                Path = (Join-Path $relativeRoot ([string]$file.Path))
                Sha256 = [string]$file.Sha256
            }
        }
    )
    $depPaths = @($fileHashes | Where-Object { $_.Path -match '\\Dependencies\\' } | ForEach-Object { [string]$_.Path })
    $app = [pscustomobject]@{
        Id='MicrosoftCompanyPortal'; DisplayName='Microsoft Company Portal'
        Source='BuiltIn'; PackageType='Application'; Type='CompanyPortalAppxBundle'
        Version=[string]$info.Version; Architecture=$Architecture
        Package=(Join-Path $relativeRoot 'Bundle.appxbundle')
        License=(Join-Path $relativeRoot 'License.xml')
        Dependencies=$depPaths; FileHashes=$fileHashes
    }
    $manifest = Set-OSDAppManifestApp -Manifest $manifest -App $app
    $manifest = Set-OSDAppManifestRuntimeConfiguration -Manifest $manifest
    $manifest.StagedAt = (Get-Date).ToUniversalTime().ToString('o')
    $manifest | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
    Add-OSDAppSetupComplete -WindowsPath $target -StagedRelativePath 'Windows\Temp\OSDApps' -Confirm:$false | Out-Null

    [pscustomobject]@{
        Name='MicrosoftCompanyPortal'; Source='BuiltIn'; PackageType='Application'
        Version=[string]$info.Version; Architecture=$Architecture
        WindowsPath=$target; StagedPath=$destination
    }
}