feat: add two-folder first-run playout setup

This commit is contained in:
2026-07-27 01:00:57 +09:00
parent 483b2785a0
commit ee82f0be42
14 changed files with 1753 additions and 278 deletions

View File

@@ -24,6 +24,10 @@ param(
[string] $InteropSha256,
[switch] $PinRegisteredK3D,
[switch] $ReplaceK3DPin,
[ValidateRange(0, 2147483647)]
[Nullable[int]] $OutputChannel = $null,
@@ -1411,7 +1415,7 @@ function Get-DevelopmentLivePreflight {
return [pscustomobject]@{
# Every requested setup invalidates any old one-launch authorization and
# issues a new file only after runtime/DB/build/cleanup success.
# issues a new file only after runtime/DB/build/K3D verification succeeds.
NeedsWrite = $true
NeedsForce = [bool] ($playoutExists -or $authorizationExists)
PlayoutPath = $playoutPath
@@ -1421,6 +1425,144 @@ function Get-DevelopmentLivePreflight {
}
}
function Get-K3DPinPreflight {
param(
[Parameter(Mandatory = $true)][string] $ConfigurationRoot,
[Parameter(Mandatory = $true)][string] $ApprovedNativeHash,
[Parameter(Mandatory = $true)][string] $ApprovedInteropHash,
[switch] $AllowReplacement
)
$pin = [ordered]@{
schemaVersion = 1
nativeSha256 = $ApprovedNativeHash.ToUpperInvariant()
interopSha256 = $ApprovedInteropHash.ToUpperInvariant()
}
$pinJson = Get-CanonicalJsonText -Value $pin -Depth 3
$pinPath = Join-Path $ConfigurationRoot 'k3d-pins.local.json'
Assert-NoReparsePointInExistingAncestry `
-Path $ConfigurationRoot `
-Description 'K3D pin configuration directory'
$pinExists = Test-Path -LiteralPath $pinPath
if ($pinExists) {
[void](Assert-RegularFile `
-Path $pinPath `
-Description 'K3D persistent pin')
}
$pinMatches = $pinExists -and
(Test-ExistingJsonContract -Path $pinPath -ExpectedJson $pinJson)
if ($pinExists -and -not $pinMatches -and -not $AllowReplacement) {
throw (
'The installed K3D files differ from the persistent local pin. ' +
'Automatic pinning cannot approve a changed DLL. Independently ' +
'verify the vendor files before using -ReplaceK3DPin with supplied hashes.')
}
return [pscustomobject]@{
NeedsWrite = [bool] (-not $pinExists -or -not $pinMatches)
NeedsReplace = [bool] $pinExists
Path = $pinPath
Json = $pinJson
}
}
function Write-ProtectedUtf8TextAtomically {
param(
[Parameter(Mandatory = $true)][string] $Path,
[Parameter(Mandatory = $true)][string] $ExpectedText,
[Parameter(Mandatory = $true)][string] $Description,
[switch] $Replace
)
$destination = Get-NormalizedFullPath -Path $Path
$directory = [IO.Path]::GetDirectoryName($destination)
[IO.Directory]::CreateDirectory($directory) | Out-Null
Assert-NoReparsePointInExistingAncestry `
-Path $directory `
-Description "$Description directory"
Set-UserOnlyDirectoryAcl -Path $directory
$temporaryPath = Join-Path $directory (
'.' + [IO.Path]::GetFileName($destination) + '.' +
[Guid]::NewGuid().ToString('N') + '.tmp')
$rollbackSnapshot = New-FileRollbackSnapshot `
-Path $destination `
-BackupDirectory $directory `
-Description $Description
$mutationStarted = $false
$preserveRollbackBackup = $false
try {
[IO.File]::WriteAllText(
$temporaryPath,
$ExpectedText,
(New-Object Text.UTF8Encoding($false)))
[IO.File]::SetAccessControl($temporaryPath, (Get-UserOnlyAcl))
if (-not (Test-ExactUtf8Text `
-Path $temporaryPath `
-ExpectedText $ExpectedText) -or
-not (Test-UserOnlyFileAcl -Path $temporaryPath)) {
throw "$Description temporary file failed exact JSON/ACL verification."
}
if (Test-Path -LiteralPath $destination) {
if (-not $Replace) {
throw "Refusing to replace the existing $Description."
}
[void](Assert-RegularFile -Path $destination -Description $Description)
$mutationStarted = $true
[IO.File]::Replace($temporaryPath, $destination, $null)
$temporaryPath = $null
}
else {
$mutationStarted = $true
[IO.File]::Move($temporaryPath, $destination)
$temporaryPath = $null
}
[IO.File]::SetAccessControl($destination, (Get-UserOnlyAcl))
if (-not (Test-ExactUtf8Text `
-Path $destination `
-ExpectedText $ExpectedText) -or
-not (Test-UserOnlyFileAcl -Path $destination)) {
throw "The atomically installed $Description failed exact JSON/ACL verification."
}
}
catch {
$writeError = $_
if ($mutationStarted) {
try {
Restore-FileRollbackSnapshot `
-Snapshot $rollbackSnapshot `
-Description $Description
}
catch {
$preserveRollbackBackup = $true
throw [InvalidOperationException]::new(
(
"$Description write failed and its previous trust " +
'anchor could not be restored: ' + $_.Exception.Message
),
$writeError.Exception)
}
}
throw $writeError
}
finally {
if (-not [string]::IsNullOrWhiteSpace($temporaryPath) -and
(Test-Path -LiteralPath $temporaryPath)) {
[IO.File]::Delete($temporaryPath)
}
if (-not $preserveRollbackBackup -and
$null -ne $rollbackSnapshot -and
-not [string]::IsNullOrWhiteSpace(
[string] $rollbackSnapshot.BackupPath) -and
(Test-Path -LiteralPath ([string] $rollbackSnapshot.BackupPath))) {
[IO.File]::Delete([string] $rollbackSnapshot.BackupPath)
}
}
}
function Copy-DatabaseProfileAtomically {
param(
[Parameter(Mandatory = $true)][string] $Source,
@@ -1624,21 +1766,29 @@ function Assert-DebugRuntimePayloadMatchesManifest {
function Invoke-DebugBuildAndVerify {
param(
[Parameter(Mandatory = $true)][string] $DotNetPath,
[Parameter(Mandatory = $true)][string] $ProjectPath,
[Parameter(Mandatory = $true)][string] $ExpectedRuntimeRoot,
[Parameter(Mandatory = $true)][string] $ExpectedManifestPath,
[Parameter(Mandatory = $true)][string] $ExpectedManifestSha256
)
& dotnet build $ProjectPath -c Debug '-p:Platform=x64' |
$runtimeRootProperty = "-p:LegacyRuntimeSourceRoot=$ExpectedRuntimeRoot"
& $DotNetPath build $ProjectPath `
-c Debug `
'-p:Platform=x64' `
'-p:LegacyRuntimeAssetsMode=Required' `
$runtimeRootProperty |
ForEach-Object { Write-Host $_ }
if ($LASTEXITCODE -ne 0) {
throw "Debug x64 build failed with exit code $LASTEXITCODE."
}
$propertyText = (& dotnet msbuild $ProjectPath `
$propertyText = (& $DotNetPath msbuild $ProjectPath `
'-p:Configuration=Debug' `
'-p:Platform=x64' `
'-p:LegacyRuntimeAssetsMode=Required' `
$runtimeRootProperty `
'-getProperty:TargetDir' `
'-getProperty:LegacyRuntimeAssetsEnabled' `
'-getProperty:LegacyRuntimeAssetsMode' `
@@ -1710,6 +1860,23 @@ function Invoke-DebugBuildAndVerify {
}
}
$setupMutex = [Threading.Mutex]::new(
$false,
'Local\MBN_STOCK_WEBVIEW.ExistingDevelopmentPcInitialization.v1')
$setupMutexOwned = $false
try {
try {
$setupMutexOwned = $setupMutex.WaitOne(0)
}
catch [Threading.AbandonedMutexException] {
# The prior process ended unexpectedly. Atomic local files are re-preflighted
# below so this run can safely complete the setup forward from that state.
$setupMutexOwned = $true
}
if (-not $setupMutexOwned) {
throw 'Another existing-development-PC initialization is already running.'
}
$repositoryRoot = Get-NormalizedFullPath -Path (Join-Path $PSScriptRoot '..')
$projectPath = Join-Path $repositoryRoot (
'src\MBN_STOCK_WEBVIEW.LegacyParityApp\MBN_STOCK_WEBVIEW.LegacyParityApp.csproj')
@@ -1729,6 +1896,14 @@ foreach ($requiredScript in @(
if ($ReplaceLiveConfig -and -not $ConfigureDevelopmentLive) {
throw '-ReplaceLiveConfig requires -ConfigureDevelopmentLive.'
}
if ($ReplaceK3DPin -and -not $ConfigureDevelopmentLive) {
throw '-ReplaceK3DPin requires -ConfigureDevelopmentLive.'
}
if ($ReplaceK3DPin -and $PinRegisteredK3D) {
throw (
'-ReplaceK3DPin cannot be combined with -PinRegisteredK3D. ' +
'A replacement requires independently supplied native and interop hashes.')
}
if ($ConfigureDevelopmentLive -and $SkipBuild) {
throw '-ConfigureDevelopmentLive requires the default Debug x64 build verification.'
}
@@ -1737,10 +1912,25 @@ if (-not $ConfigureDevelopmentLive -and
$PlayoutPort -ne 0 -or
-not [string]::IsNullOrWhiteSpace($NativeSha256) -or
-not [string]::IsNullOrWhiteSpace($InteropSha256) -or
$PinRegisteredK3D -or
$ReplaceK3DPin -or
$null -ne $OutputChannel)) {
throw 'Development Live endpoint/hash options require -ConfigureDevelopmentLive.'
}
$dotnetPath = $null
if (-not $SkipBuild) {
$dotnetCommand = Get-Command -Name 'dotnet.exe' -CommandType Application `
-ErrorAction Stop | Select-Object -First 1
if ($null -eq $dotnetCommand -or
[string]::IsNullOrWhiteSpace([string] $dotnetCommand.Source)) {
throw 'A .NET SDK dotnet.exe could not be resolved for build verification.'
}
$dotnetPath = Get-NormalizedFullPath -Path ([string] $dotnetCommand.Source)
[void](Assert-RegularFile -Path $dotnetPath -Description '.NET SDK host')
Assert-FixedLocalPath -Path $dotnetPath -Description '.NET SDK host'
}
$runtimeSourceRoot = Select-LegacyRuntimeRoot `
-RepositoryRoot $repositoryRoot `
-RequestedRoot $LegacyRuntimeSourceRoot `
@@ -1792,9 +1982,16 @@ Assert-NoReparsePointInExistingAncestry `
-Description 'LocalAppData'
# Preflight a requested Live replacement before any setup write. The caller
# supplies both approval hashes; this script only verifies them against files.
# either supplies both independent approval hashes or explicitly requests
# first-use pinning of the already verified HKLM x64 K3D registration. Both
# paths keep the exact file hashes in the one-launch authorization and the app
# verifies them again before loading either vendor binary.
$livePreflight = $null
$k3dPinPreflight = $null
$k3dPinWritten = $false
$liveAuthorizationInvalidated = $false
$approvedNativeSha256 = $null
$approvedInteropSha256 = $null
if ($ConfigureDevelopmentLive) {
$configurationRoot = Join-Path $localAppDataRoot 'MBN_STOCK_WEBVIEW\Config'
$prospectiveAuthorizationPath = Join-Path $configurationRoot (
@@ -1806,19 +2003,67 @@ if ($ConfigureDevelopmentLive) {
if ($PlayoutPort -lt 1 -or $PlayoutPort -gt 65535) {
throw 'Development Live playout port must be between 1 and 65535.'
}
if ([string]::IsNullOrWhiteSpace($NativeSha256) -or
$NativeSha256 -notmatch '^[0-9A-Fa-f]{64}$' -or
[string]::IsNullOrWhiteSpace($InteropSha256) -or
$InteropSha256 -notmatch '^[0-9A-Fa-f]{64}$') {
throw 'Independent native and interop SHA-256 approvals are both required.'
$actualNativeHash = (
Get-FileHash -LiteralPath $nativePath -Algorithm SHA256
).Hash.ToUpperInvariant()
$actualInteropHash = (
Get-FileHash -LiteralPath $interopPath -Algorithm SHA256
).Hash.ToUpperInvariant()
if ($PinRegisteredK3D) {
if (-not [string]::IsNullOrWhiteSpace($NativeSha256) -or
-not [string]::IsNullOrWhiteSpace($InteropSha256)) {
throw (
'-PinRegisteredK3D cannot be combined with independently ' +
'supplied K3D hashes.')
}
$approvedNativeSha256 = $actualNativeHash
$approvedInteropSha256 = $actualInteropHash
}
else {
if ([string]::IsNullOrWhiteSpace($NativeSha256) -or
$NativeSha256 -notmatch '^[0-9A-Fa-f]{64}$' -or
[string]::IsNullOrWhiteSpace($InteropSha256) -or
$InteropSha256 -notmatch '^[0-9A-Fa-f]{64}$') {
throw 'Independent native and interop SHA-256 approvals are both required.'
}
$approvedNativeSha256 = $NativeSha256.ToUpperInvariant()
$approvedInteropSha256 = $InteropSha256.ToUpperInvariant()
if (-not $actualNativeHash.Equals(
$approvedNativeSha256,
[StringComparison]::Ordinal) -or
-not $actualInteropHash.Equals(
$approvedInteropSha256,
[StringComparison]::Ordinal)) {
throw 'An independently supplied K3D SHA-256 does not match the registered files.'
}
}
$k3dPinPreflight = Get-K3DPinPreflight `
-ConfigurationRoot $configurationRoot `
-ApprovedNativeHash $approvedNativeSha256 `
-ApprovedInteropHash $approvedInteropSha256 `
-AllowReplacement:$ReplaceK3DPin
if ($k3dPinPreflight.NeedsWrite) {
# This is a durable trust anchor, not transactional runtime state.
# Preserve the first-seen/independently replaced pin even when a
# later DB, build, Live-config, or cleanup step fails. Otherwise a
# changed DLL could be automatically accepted on the next attempt.
Write-ProtectedUtf8TextAtomically `
-Path ([string] $k3dPinPreflight.Path) `
-ExpectedText ([string] $k3dPinPreflight.Json) `
-Description 'K3D persistent pin' `
-Replace:$k3dPinPreflight.NeedsReplace
$k3dPinWritten = $true
}
$livePreflight = Get-DevelopmentLivePreflight `
-ConfigurationRoot $configurationRoot `
-HostName $PlayoutHost `
-Port $PlayoutPort `
-ApprovedNativeHash $NativeSha256 `
-ApprovedInteropHash $InteropSha256 `
-ApprovedNativeHash $approvedNativeSha256 `
-ApprovedInteropHash $approvedInteropSha256 `
-Channel $OutputChannel `
-AllowReplacement:$ReplaceLiveConfig
}
@@ -1849,20 +2094,6 @@ if ($ConfigureDevelopmentLive) {
}
$liveAuthorizationInvalidated = $true
$actualNativeHash = (
Get-FileHash -LiteralPath $nativePath -Algorithm SHA256
).Hash.ToUpperInvariant()
$actualInteropHash = (
Get-FileHash -LiteralPath $interopPath -Algorithm SHA256
).Hash.ToUpperInvariant()
if (-not $actualNativeHash.Equals(
$NativeSha256,
[StringComparison]::OrdinalIgnoreCase) -or
-not $actualInteropHash.Equals(
$InteropSha256,
[StringComparison]::OrdinalIgnoreCase)) {
throw 'An independently supplied K3D SHA-256 does not match the registered files.'
}
}
$databaseSource = $null
@@ -1949,6 +2180,7 @@ $setupRoot = Join-Path $setupParent ([Guid]::NewGuid().ToString('N'))
$rollbackRoot = Join-Path $rollbackParent ([Guid]::NewGuid().ToString('N'))
$runtimeResult = $null
$pendingRuntimeBinding = $null
$buildResult = $null
$databaseInstalled = $false
$liveConfigured = $false
@@ -1956,6 +2188,8 @@ $runtimeBindingMutationStarted = $false
$databaseMutationStarted = $false
$propsSnapshot = $null
$databaseSnapshot = $null
$playoutSnapshot = $null
$playoutMutationStarted = $false
$transactionCommitted = $false
$transactionError = $null
try {
@@ -1988,6 +2222,12 @@ try {
-BackupDirectory $rollbackRoot `
-Description 'Installed database profile'
}
if ($ConfigureDevelopmentLive) {
$playoutSnapshot = New-FileRollbackSnapshot `
-Path ([string] $livePreflight.PlayoutPath) `
-BackupDirectory $rollbackRoot `
-Description 'Development Live base configuration'
}
$bundleOutput = Join-Path $setupRoot 'bundle'
$bundleResults = @(& $bundleCreatorPath `
@@ -2061,35 +2301,35 @@ try {
-ShadowRepositoryRoot $shadowRepositoryRoot `
-RuntimeStorageRoot $runtimeStorageRoot
$runtimeBindingMutationStarted = $true
Set-RuntimeBindingAtomically `
-GeneratedPropsPath $initialized.LocalBuildPropsPath `
-RepositoryRoot $repositoryRoot `
-Replace:($null -ne $existingRuntime)
$verifiedBinding = Get-ExistingRuntimeBinding `
-RepositoryRoot $repositoryRoot `
-RuntimeStorageRoot $runtimeStorageRoot
if ($null -eq $verifiedBinding -or
-not (Test-SamePath `
-Left $verifiedBinding.InstalledRoot `
-Right ([string] $initialized.InstalledRoot)) -or
-not $verifiedBinding.ManifestSha256.Equals(
[string] $initialized.ManifestSha256,
[StringComparison]::Ordinal)) {
throw 'The atomically installed runtime binding failed final verification.'
}
Assert-InstalledRuntimeMatchesManifest `
-InstalledRoot $verifiedBinding.InstalledRoot `
-ManifestPath $newManifestPath
$runtimeResult = [pscustomobject]@{
InstalledRoot = $verifiedBinding.InstalledRoot
ManifestSha256 = $verifiedBinding.ManifestSha256
ManifestPath = $verifiedBinding.ManifestPath
InstalledRoot = [string] $initialized.InstalledRoot
ManifestSha256 = [string] $initialized.ManifestSha256
ManifestPath = Join-Path `
([string] $initialized.InstalledRoot) `
$manifestFileName
CutsFileCount = [int] $initialized.CutsFileCount
ResFileCount = [int] $initialized.ResFileCount
Reused = $false
}
$pendingRuntimeBinding = [pscustomobject]@{
GeneratedPropsPath = [string] $initialized.LocalBuildPropsPath
Replace = [bool] ($null -ne $existingRuntime)
}
}
if (-not $SkipBuild) {
$buildResult = Invoke-DebugBuildAndVerify `
-DotNetPath $dotnetPath `
-ProjectPath $projectPath `
-ExpectedRuntimeRoot $runtimeResult.InstalledRoot `
-ExpectedManifestPath $runtimeResult.ManifestPath `
-ExpectedManifestSha256 $runtimeResult.ManifestSha256
if ([int] $buildResult.CutsFileCount -ne
[int] $runtimeResult.CutsFileCount -or
[int] $buildResult.ResFileCount -ne
[int] $runtimeResult.ResFileCount) {
throw 'Debug x64 payload count mismatch after exact manifest verification.'
}
}
if ($databaseNeedsWrite) {
@@ -2102,22 +2342,92 @@ try {
$databaseInstalled = $true
}
if (-not $SkipBuild) {
$buildResult = Invoke-DebugBuildAndVerify `
-ProjectPath $projectPath `
-ExpectedRuntimeRoot $runtimeResult.InstalledRoot `
-ExpectedManifestPath $runtimeResult.ManifestPath `
-ExpectedManifestSha256 $runtimeResult.ManifestSha256
if ([int] $buildResult.CutsFileCount -ne
[int] $runtimeResult.CutsFileCount -or
[int] $buildResult.ResFileCount -ne
[int] $runtimeResult.ResFileCount) {
throw 'Debug x64 payload count mismatch after exact manifest verification.'
if ($ConfigureDevelopmentLive) {
[void](Assert-RegularFile `
-Path $nativePath `
-Description 'Registered K3D native DLL before authorization')
[void](Assert-RegularFile `
-Path $interopPath `
-Description 'K3D interop assembly before authorization')
$finalNativeHash = (
Get-FileHash -LiteralPath $nativePath -Algorithm SHA256
).Hash.ToUpperInvariant()
$finalInteropHash = (
Get-FileHash -LiteralPath $interopPath -Algorithm SHA256
).Hash.ToUpperInvariant()
if (-not $finalNativeHash.Equals(
$approvedNativeSha256,
[StringComparison]::Ordinal) -or
-not $finalInteropHash.Equals(
$approvedInteropSha256,
[StringComparison]::Ordinal)) {
throw (
'A K3D DLL changed during setup. The durable pin remains at ' +
'the originally approved hash, and no Live authorization was issued.')
}
}
# Both working data and rollback copies must be gone before a new Live
# authorization can be issued. A cleanup failure is a transaction failure.
# Live authorization is the final functional mutation. No COM activation,
# PGM command, or database connection is performed by this setup script.
if ($ConfigureDevelopmentLive -and $livePreflight.NeedsWrite) {
$liveArguments = @{
PlayoutHost = $PlayoutHost
PlayoutPort = $PlayoutPort
NativeSha256 = $approvedNativeSha256
InteropSha256 = $approvedInteropSha256
ConfigurationDirectory = $configurationRoot
}
if ($null -ne $OutputChannel) {
$liveArguments.OutputChannel = $OutputChannel
}
if ($livePreflight.NeedsForce) {
$liveArguments.Force = $true
}
$playoutMutationStarted = $true
$liveResults = @(& $liveInitializerPath @liveArguments)
if ($liveResults.Count -ne 0) {
throw 'Development Live initializer returned unexpected success-stream output.'
}
Assert-ExistingJsonContract `
-Path ([string] $livePreflight.PlayoutPath) `
-ExpectedJson ([string] $livePreflight.PlayoutJson) `
-Description 'Development Live base configuration'
Assert-ExistingJsonContract `
-Path ([string] $livePreflight.AuthorizationPath) `
-ExpectedJson ([string] $livePreflight.AuthorizationJson) `
-Description 'Development Live authorization'
$liveConfigured = $true
}
# Directory.Build.local.props is the durable success marker for a fresh
# clone. Keep it source-only until runtime/build/DB/K3D/Live are complete so
# a stopped setup returns to this first-run UI on the next F5.
if ($null -ne $pendingRuntimeBinding) {
$runtimeBindingMutationStarted = $true
Set-RuntimeBindingAtomically `
-GeneratedPropsPath ([string] $pendingRuntimeBinding.GeneratedPropsPath) `
-RepositoryRoot $repositoryRoot `
-Replace:([bool] $pendingRuntimeBinding.Replace)
$verifiedBinding = Get-ExistingRuntimeBinding `
-RepositoryRoot $repositoryRoot `
-RuntimeStorageRoot $runtimeStorageRoot
if ($null -eq $verifiedBinding -or
-not (Test-SamePath `
-Left $verifiedBinding.InstalledRoot `
-Right ([string] $runtimeResult.InstalledRoot)) -or
-not $verifiedBinding.ManifestSha256.Equals(
[string] $runtimeResult.ManifestSha256,
[StringComparison]::Ordinal)) {
throw 'The atomically installed runtime binding failed final verification.'
}
Assert-InstalledRuntimeMatchesManifest `
-InstalledRoot $verifiedBinding.InstalledRoot `
-ManifestPath $runtimeResult.ManifestPath
}
# A cleanup failure is still a transaction failure. Rollback snapshots are
# also held in memory so rollback remains possible if cleanup only partly ran.
Remove-OwnedTemporaryDirectory `
-Parent $setupParent `
-Path $setupRoot `
@@ -2135,6 +2445,30 @@ catch {
if (-not $transactionCommitted) {
$secondaryFailures = New-Object 'System.Collections.Generic.List[string]'
# Always fail closed before restoring any build/runtime state.
if ($ConfigureDevelopmentLive -and $liveAuthorizationInvalidated) {
try {
Remove-DevelopmentLiveAuthorizationFailClosed `
-Path ([string] $livePreflight.AuthorizationPath)
}
catch {
$secondaryFailures.Add(
'Development Live fail-closed cleanup failed: ' +
$_.Exception.Message)
}
}
if ($playoutMutationStarted -and $null -ne $playoutSnapshot) {
try {
Restore-FileRollbackSnapshot `
-Snapshot $playoutSnapshot `
-Description 'Development Live base configuration'
}
catch {
$secondaryFailures.Add(
'Development Live base configuration rollback failed: ' +
$_.Exception.Message)
}
}
if ($databaseMutationStarted -and $null -ne $databaseSnapshot) {
try {
Restore-FileRollbackSnapshot `
@@ -2182,18 +2516,6 @@ if (-not $transactionCommitted) {
}
}
if ($ConfigureDevelopmentLive -and $liveAuthorizationInvalidated) {
try {
Remove-DevelopmentLiveAuthorizationFailClosed `
-Path ([string] $livePreflight.AuthorizationPath)
}
catch {
$secondaryFailures.Add(
'Development Live fail-closed cleanup failed: ' +
$_.Exception.Message)
}
}
if ($null -eq $transactionError) {
$transactionError = [System.Management.Automation.ErrorRecord]::new(
[InvalidOperationException]::new(
@@ -2214,56 +2536,6 @@ if (-not $transactionCommitted) {
throw $transactionError
}
# Live authorization is the final mutation, after build verification and after
# temporary cleanup. No COM activation, PGM command, or database connection is
# performed by this setup script.
if ($ConfigureDevelopmentLive -and $livePreflight.NeedsWrite) {
$liveArguments = @{
PlayoutHost = $PlayoutHost
PlayoutPort = $PlayoutPort
NativeSha256 = $NativeSha256
InteropSha256 = $InteropSha256
ConfigurationDirectory = $configurationRoot
}
if ($null -ne $OutputChannel) {
$liveArguments.OutputChannel = $OutputChannel
}
if ($livePreflight.NeedsForce) {
$liveArguments.Force = $true
}
try {
$liveResults = @(& $liveInitializerPath @liveArguments)
if ($liveResults.Count -ne 0) {
throw 'Development Live initializer returned unexpected success-stream output.'
}
Assert-ExistingJsonContract `
-Path ([string] $livePreflight.PlayoutPath) `
-ExpectedJson ([string] $livePreflight.PlayoutJson) `
-Description 'Development Live base configuration'
Assert-ExistingJsonContract `
-Path ([string] $livePreflight.AuthorizationPath) `
-ExpectedJson ([string] $livePreflight.AuthorizationJson) `
-Description 'Development Live authorization'
$liveConfigured = $true
}
catch {
$liveError = $_
try {
Remove-DevelopmentLiveAuthorizationFailClosed `
-Path ([string] $livePreflight.AuthorizationPath)
}
catch {
throw [InvalidOperationException]::new(
(
'Development Live configuration failed and fail-closed ' +
'authorization cleanup also failed: ' + $_.Exception.Message
),
$liveError.Exception)
}
throw $liveError
}
}
[pscustomobject]@{
RuntimeSourceRoot = $runtimeSourceRoot
InstalledRuntimeRoot = $runtimeResult.InstalledRoot
@@ -2275,6 +2547,14 @@ if ($ConfigureDevelopmentLive -and $livePreflight.NeedsWrite) {
DatabaseProfileWritten = $databaseInstalled
K3DStatus = [string] $registration.Status
K3DComActivated = [bool] $registration.ComActivated
K3DPinPath = if ($ConfigureDevelopmentLive) {
[string] $k3dPinPreflight.Path
} else {
$null
}
K3DPinWritten = $k3dPinWritten
K3DPinReused = [bool] (
$ConfigureDevelopmentLive -and -not $k3dPinPreflight.NeedsWrite)
BuildVerified = $null -ne $buildResult
BuildOutputPath = if ($null -eq $buildResult) { $null } else {
$buildResult.TargetDirectory
@@ -2283,3 +2563,14 @@ if ($ConfigureDevelopmentLive -and $livePreflight.NeedsWrite) {
DevelopmentLiveWritten = $liveConfigured
DevelopmentLiveReused = $false
}
}
finally {
try {
if ($setupMutexOwned) {
$setupMutex.ReleaseMutex()
}
}
finally {
$setupMutex.Dispose()
}
}