private/core-license/Get-OSDeployLicenseFileRecord.ps1
|
function ConvertTo-OSDeployCanonicalLicenseValue { <# .SYNOPSIS Converts a license value to a deterministic comparison string .DESCRIPTION Recursively sorts object properties and array values so semantically identical license payloads can be compared independently of JSON formatting or property order. .PARAMETER InputObject Specifies the value to canonicalize. .PARAMETER ExcludeRootProperty Specifies top-level payload properties to exclude from the comparison. .PARAMETER IsRoot Indicates that InputObject is the root license payload. .EXAMPLE PS> ConvertTo-OSDeployCanonicalLicenseValue -InputObject $License Returns a deterministic string for the license payload. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.String. Returns the canonical comparison value. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-09-02 #> [CmdletBinding()] [OutputType([string])] param ( [AllowNull()] [object]$InputObject, [string[]]$ExcludeRootProperty = @(), [switch]$IsRoot ) if ($null -eq $InputObject) { return 'null' } if ($InputObject -is [System.Collections.IDictionary]) { $Properties = foreach ($Key in @($InputObject.Keys | Sort-Object)) { if ($IsRoot -and $Key -in $ExcludeRootProperty) { continue } $Name = ConvertTo-Json -InputObject ([string]$Key) -Compress $Value = ConvertTo-OSDeployCanonicalLicenseValue -InputObject $InputObject[$Key] -ExcludeRootProperty $ExcludeRootProperty '{0}:{1}' -f $Name, $Value } return '{' + ($Properties -join ',') + '}' } if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) { $Values = foreach ($Value in $InputObject) { ConvertTo-OSDeployCanonicalLicenseValue -InputObject $Value -ExcludeRootProperty $ExcludeRootProperty } return '[' + (@($Values | Sort-Object) -join ',') + ']' } $PropertyNames = @($InputObject.PSObject.Properties.Name) if ($PropertyNames.Count -gt 0 -and $InputObject -isnot [ValueType] -and $InputObject -isnot [string]) { $Properties = foreach ($NameValue in @($PropertyNames | Sort-Object)) { if ($IsRoot -and $NameValue -in $ExcludeRootProperty) { continue } $Name = ConvertTo-Json -InputObject ([string]$NameValue) -Compress $Value = ConvertTo-OSDeployCanonicalLicenseValue -InputObject $InputObject.$NameValue -ExcludeRootProperty $ExcludeRootProperty '{0}:{1}' -f $Name, $Value } return '{' + ($Properties -join ',') + '}' } ConvertTo-Json -InputObject $InputObject -Compress } function Get-OSDeployLicenseFileRecord { <# .SYNOPSIS Reads comparable records from a Recast Software license file .DESCRIPTION Parses a .license2 file and returns one comparison record per payload entry. Each record includes normalized GUID, signature hash, and expiration values plus exact and renewal fingerprints. .PARAMETER LiteralPath Specifies the literal path to the .license2 file. .EXAMPLE PS> Get-OSDeployLicenseFileRecord -LiteralPath 'C:\Licenses\license.license2' Returns records used to normalize and deduplicate the license directory. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. Returns one record per parseable payload entry that contains a valid Data.LicenseGuid value. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-09-02 #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Mandatory)] [string]$LiteralPath ) try { $File = Get-Item -LiteralPath $LiteralPath -ErrorAction Stop $Payload = @(Get-Content -LiteralPath $File.FullName -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop) } catch { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Unable to parse $LiteralPath. $($_.Exception.Message)" return } foreach ($Entry in $Payload) { $LicenseGuid = [guid]::Empty if (-not $Entry.Data -or -not [guid]::TryParse([string]$Entry.Data.LicenseGuid, [ref]$LicenseGuid)) { continue } $SignatureHash = $null if (-not [string]::IsNullOrWhiteSpace([string]$Entry.Signature)) { $HashProvider = [System.Security.Cryptography.SHA256]::Create() try { $SignatureBytes = [System.Text.Encoding]::UTF8.GetBytes([string]$Entry.Signature) $SignatureHash = [System.BitConverter]::ToString($HashProvider.ComputeHash($SignatureBytes)).Replace('-', '').ToLowerInvariant() } finally { $HashProvider.Dispose() } } $Expiration = [datetime]::MinValue if (-not [string]::IsNullOrWhiteSpace([string]$Entry.Expiration)) { $ParsedExpiration = [datetime]::MinValue if ([datetime]::TryParse([string]$Entry.Expiration, [ref]$ParsedExpiration)) { $Expiration = $ParsedExpiration } } [pscustomobject]@{ File = $File EntryCount = $Payload.Count LicenseGuid = $LicenseGuid.ToString().ToLowerInvariant() SignatureHash = $SignatureHash Expiration = $Expiration ExactFingerprint = ConvertTo-OSDeployCanonicalLicenseValue -InputObject $Entry -IsRoot RenewalFingerprint = ConvertTo-OSDeployCanonicalLicenseValue -InputObject $Entry -ExcludeRootProperty @('Expiration', 'Signature') -IsRoot } } } |