| | | 1 | | <# |
| | | 2 | | .SYNOPSIS |
| | | 3 | | Exports a PowerShell object to a serialized XML representation. |
| | | 4 | | |
| | | 5 | | .DESCRIPTION |
| | | 6 | | Export-KrSharedState serializes a PowerShell object using |
| | | 7 | | [System.Management.Automation.PSSerializer] and returns the serialized |
| | | 8 | | data as a string, as a byte array, or writes it to a file. |
| | | 9 | | |
| | | 10 | | Access is synchronized through a shared lock so that callers using the |
| | | 11 | | same lock serialize shared state in a thread-safe manner within the |
| | | 12 | | current process. |
| | | 13 | | |
| | | 14 | | .PARAMETER InputObject |
| | | 15 | | The object to serialize. |
| | | 16 | | |
| | | 17 | | .PARAMETER Path |
| | | 18 | | The destination file path. When provided, Export-KrSharedState writes the |
| | | 19 | | serialized XML to this file and returns the written file. |
| | | 20 | | |
| | | 21 | | .PARAMETER OutputType |
| | | 22 | | Specifies how the serialized XML is returned: |
| | | 23 | | - String |
| | | 24 | | - ByteArray |
| | | 25 | | - File |
| | | 26 | | |
| | | 27 | | When Path is provided, the effective output type is File. Supplying Path |
| | | 28 | | with a different OutputType value results in an error. |
| | | 29 | | |
| | | 30 | | .PARAMETER Lock |
| | | 31 | | The semaphore used to synchronize access to shared state. If not provided, |
| | | 32 | | the default shared-state lock is used. |
| | | 33 | | |
| | | 34 | | .PARAMETER TimeoutMilliseconds |
| | | 35 | | The maximum time to wait for the lock. Use -1 to wait indefinitely. |
| | | 36 | | |
| | | 37 | | .PARAMETER Encoding |
| | | 38 | | The text encoding used when converting to bytes or writing to a file. |
| | | 39 | | |
| | | 40 | | .EXAMPLE |
| | | 41 | | $xml = Export-KrSharedState -InputObject $state |
| | | 42 | | |
| | | 43 | | .EXAMPLE |
| | | 44 | | $bytes = Export-KrSharedState -InputObject $state -OutputType ByteArray |
| | | 45 | | |
| | | 46 | | .EXAMPLE |
| | | 47 | | Export-KrSharedState -InputObject $state -OutputType File -Path '.\state.xml' |
| | | 48 | | |
| | | 49 | | .EXAMPLE |
| | | 50 | | Export-KrSharedState -InputObject $state -Path '.\state.xml' |
| | | 51 | | |
| | | 52 | | .EXAMPLE |
| | | 53 | | $lock = Get-KrLock 'sharedstate:cache' |
| | | 54 | | Export-KrSharedState -InputObject $state -Lock $lock |
| | | 55 | | #> |
| | | 56 | | function Export-KrSharedState { |
| | | 57 | | [KestrunRuntimeApi('Everywhere')] |
| | | 58 | | [OutputType([string], [byte[]], [System.IO.FileInfo])] |
| | | 59 | | [CmdletBinding(DefaultParameterSetName = 'ToString')] |
| | | 60 | | param( |
| | | 61 | | [Parameter(Mandatory, Position = 0)] |
| | | 62 | | [AllowNull()] |
| | | 63 | | [object]$InputObject, |
| | | 64 | | |
| | | 65 | | [Parameter(ParameterSetName = 'ToFile', Mandatory)] |
| | | 66 | | [string]$Path, |
| | | 67 | | |
| | | 68 | | [Parameter()] |
| | | 69 | | [ValidateSet('String', 'ByteArray', 'File')] |
| | | 70 | | [string]$OutputType = 'String', |
| | | 71 | | |
| | | 72 | | [Parameter()] |
| | | 73 | | [System.Threading.SemaphoreSlim]$Lock, |
| | | 74 | | |
| | | 75 | | [Parameter()] |
| | | 76 | | [int]$TimeoutMilliseconds = 30000, |
| | | 77 | | |
| | | 78 | | [Parameter()] |
| | | 79 | | [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8 |
| | | 80 | | ) |
| | | 81 | | |
| | 1 | 82 | | $stateLock = $null |
| | 1 | 83 | | $lockTaken = $false |
| | 1 | 84 | | $resolvedOutputType = $OutputType |
| | | 85 | | |
| | | 86 | | try { |
| | 1 | 87 | | if ($PSCmdlet.ParameterSetName -eq 'ToFile') { |
| | 1 | 88 | | if ($PSBoundParameters.ContainsKey('OutputType') -and $OutputType -ne 'File') { |
| | 1 | 89 | | throw "OutputType '$OutputType' cannot be used when Path is provided." |
| | | 90 | | } |
| | | 91 | | |
| | 1 | 92 | | $resolvedOutputType = 'File' |
| | | 93 | | } |
| | | 94 | | |
| | | 95 | | # Resolve lock |
| | 2 | 96 | | $stateLock = ($Lock)? $Lock : [Kestrun.Utilities.KestrunLockRegistry]::Default |
| | | 97 | | |
| | | 98 | | # Acquire lock |
| | 1 | 99 | | if ($TimeoutMilliseconds -lt 0) { |
| | 0 | 100 | | $stateLock.Wait() |
| | 0 | 101 | | $lockTaken = $true |
| | | 102 | | } else { |
| | 1 | 103 | | $lockTaken = $stateLock.Wait($TimeoutMilliseconds) |
| | 1 | 104 | | if (-not $lockTaken) { |
| | 1 | 105 | | throw 'Timeout waiting for shared state lock.' |
| | | 106 | | } |
| | | 107 | | } |
| | | 108 | | |
| | 1 | 109 | | $xml = [System.Management.Automation.PSSerializer]::Serialize($InputObject) |
| | | 110 | | |
| | 1 | 111 | | switch ($resolvedOutputType) { |
| | | 112 | | 'String' { |
| | 1 | 113 | | return $xml |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | 'ByteArray' { |
| | 1 | 117 | | return $Encoding.GetBytes($xml) |
| | | 118 | | } |
| | | 119 | | |
| | | 120 | | 'File' { |
| | 1 | 121 | | if ([string]::IsNullOrWhiteSpace($Path)) { |
| | 0 | 122 | | throw "Path is required when OutputType is 'File'." |
| | | 123 | | } |
| | | 124 | | |
| | 1 | 125 | | $fullPath = [System.IO.Path]::GetFullPath($Path) |
| | 1 | 126 | | $directory = [System.IO.Path]::GetDirectoryName($fullPath) |
| | | 127 | | |
| | 2 | 128 | | if (-not [string]::IsNullOrWhiteSpace($directory) -and -not (Test-Path -LiteralPath $directory)) { |
| | 2 | 129 | | [System.IO.Directory]::CreateDirectory($directory) | Out-Null |
| | | 130 | | } |
| | | 131 | | |
| | 1 | 132 | | [System.IO.File]::WriteAllText($fullPath, $xml, $Encoding) |
| | 1 | 133 | | return Get-Item -LiteralPath $fullPath |
| | | 134 | | } |
| | | 135 | | |
| | | 136 | | default { |
| | 0 | 137 | | throw "Unsupported OutputType '$resolvedOutputType'." |
| | | 138 | | } |
| | | 139 | | } |
| | | 140 | | } finally { |
| | 1 | 141 | | if ($stateLock -and $lockTaken) { |
| | | 142 | | try { |
| | 1 | 143 | | $null = $stateLock.Release() |
| | | 144 | | } catch { |
| | 0 | 145 | | Write-KrLog -Level Verbose -Message 'Failed to release shared state lock' -ErrorRecord $_ |
| | | 146 | | } |
| | | 147 | | } |
| | | 148 | | } |
| | | 149 | | } |