public/Import-OSDeployLicense.ps1
|
function Import-OSDeployLicense { <# .SYNOPSIS Imports a Recast Software license .DESCRIPTION Accepts a .license2 file or a ZIP archive containing a .license2 file, stages and validates the selected license, and interactively confirms copying it to the standard Recast Software license directory under ProgramData. After a successful copy, displays the license by calling Show-OSDeployLicense. ZIP archives are searched recursively. When an archive contains multiple license files, the candidate selected by Get-OSDCoreLicense is imported. Existing licenses are never renamed. Exact duplicates are removed. When licenses differ only by Expiration and Signature, the license with the newest expiration is retained. The imported file keeps its original filename by default. Only when a file with the same name already exists and is not being removed as a duplicate does the imported file get a numeric suffix such as -001. .PARAMETER LicenseFile Specifies the path to a .license2 file or a ZIP archive containing one or more license files with the .license2 extension. The path must identify an existing file. .EXAMPLE PS> Import-OSDeployLicense -LicenseFile 'C:\Downloads\CommunityLicense.license2' Validates the license, asks for confirmation, and imports it into the standard license directory. .EXAMPLE PS> Import-OSDeployLicense -LicenseFile 'C:\Downloads\CommunityLicense.zip' Extracts the archive to a temporary directory, selects and validates a contained license, asks for confirmation, and imports it. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject. After a successful import, returns the valid license object produced by Show-OSDeployLicense. .NOTES Author: David Segura Company: Recast Software Version: 1.0.0 Date: 2026-09-02 Write access to the ProgramData license directory is required. .LINK Show-OSDeployLicense .LINK https://www.osdeploy.com/guide/registration #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Mandatory)] [ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })] [string]$LicenseFile ) Write-Verbose "[$($MyInvocation.MyCommand.Name)] Start" $SourceFile = Get-Item -LiteralPath $LicenseFile -ErrorAction Stop $SourceExtension = $SourceFile.Extension.ToLowerInvariant() if ($SourceExtension -notin @('.license2', '.zip')) { Write-Warning "[$(Get-Date -Format s)] LicenseFile must be a .license2 file or a ZIP archive." return } $StagingPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "OSDeployLicense-$([guid]::NewGuid().ToString('N'))" $DestinationDirectory = Join-Path -Path $env:ProgramData -ChildPath 'Recast Software\Licenses' try { $null = New-Item -Path $StagingPath -ItemType Directory -Force -ErrorAction Stop $ValidationPath = Join-Path -Path $StagingPath -ChildPath 'Licenses' $null = New-Item -Path $ValidationPath -ItemType Directory -Force -ErrorAction Stop if ($SourceExtension -eq '.zip') { $ExtractionPath = Join-Path -Path $StagingPath -ChildPath 'Archive' Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Extracting license archive: $($SourceFile.FullName)" Expand-Archive -LiteralPath $SourceFile.FullName -DestinationPath $ExtractionPath -Force -ErrorAction Stop $ArchiveLicenses = @(Get-ChildItem -LiteralPath $ExtractionPath -Filter '*.license2' -File -Recurse -ErrorAction Stop) if (-not $ArchiveLicenses) { Write-Warning "[$(Get-Date -Format s)] No .license2 file was found in $($SourceFile.FullName)." return } foreach ($ArchiveLicense in $ArchiveLicenses) { $StagedName = $ArchiveLicense.Name if (Test-Path -LiteralPath (Join-Path -Path $ValidationPath -ChildPath $StagedName)) { $StagedName = "{0}-{1}{2}" -f $ArchiveLicense.BaseName, ([guid]::NewGuid().ToString('N')), $ArchiveLicense.Extension } Copy-Item -LiteralPath $ArchiveLicense.FullName -Destination (Join-Path -Path $ValidationPath -ChildPath $StagedName) -Force -ErrorAction Stop } } else { Copy-Item -LiteralPath $SourceFile.FullName -Destination $ValidationPath -Force -ErrorAction Stop } $StagedLicense = Get-OSDCoreLicense -Path $ValidationPath if (-not $StagedLicense) { Write-Warning "[$(Get-Date -Format s)] No readable .license2 file was found in $($SourceFile.FullName)." return } if (-not $StagedLicense.IsValid) { $ValidationMessage = @($StagedLicense.ValidationErrors) -join '; ' Write-Warning "[$(Get-Date -Format s)] The selected license is not valid: $ValidationMessage" return } $IncomingRecord = @(Get-OSDeployLicenseFileRecord -LiteralPath $StagedLicense.FullName | Where-Object { $_.LicenseGuid -eq $StagedLicense.LicenseGuid }) | Select-Object -First 1 if (-not $IncomingRecord -or $IncomingRecord.EntryCount -ne 1) { Write-Warning "[$(Get-Date -Format s)] The selected license file must contain exactly one license payload." return } $ExistingFiles = @(Get-ChildItem -LiteralPath $DestinationDirectory -Filter '*.license2' -File -ErrorAction SilentlyContinue) $UnmanagedFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new() $Candidates = [System.Collections.Generic.List[object]]::new() foreach ($ExistingFile in $ExistingFiles) { $FileRecords = @(Get-OSDeployLicenseFileRecord -LiteralPath $ExistingFile.FullName) if ($FileRecords.Count -ne 1 -or $FileRecords[0].EntryCount -ne 1) { $UnmanagedFiles.Add($ExistingFile) Write-Warning "[$(Get-Date -Format s)] Unable to compare unreadable or multi-license file: $($ExistingFile.FullName)" continue } $Candidates.Add([pscustomobject]@{ Record = $FileRecords[0] File = $ExistingFile IsIncoming = $false LicenseGuid = $FileRecords[0].LicenseGuid Expiration = $FileRecords[0].Expiration ExactFingerprint = $FileRecords[0].ExactFingerprint RenewalFingerprint = $FileRecords[0].RenewalFingerprint TargetFile = $null }) } $IncomingCandidate = [pscustomobject]@{ Record = $IncomingRecord File = $IncomingRecord.File IsIncoming = $true LicenseGuid = $IncomingRecord.LicenseGuid Expiration = $IncomingRecord.Expiration ExactFingerprint = $IncomingRecord.ExactFingerprint RenewalFingerprint = $IncomingRecord.RenewalFingerprint TargetFile = $null } $Candidates.Add($IncomingCandidate) $Survivors = [System.Collections.Generic.List[object]]::new() foreach ($GuidGroup in @($Candidates | Group-Object -Property LicenseGuid)) { foreach ($RenewalGroup in @($GuidGroup.Group | Group-Object -Property RenewalFingerprint)) { $Winner = $RenewalGroup.Group | Sort-Object -Property @( @{ Expression = { $_.Expiration }; Descending = $true }, @{ Expression = { $_.IsIncoming }; Ascending = $true }, @{ Expression = { $_.File.LastWriteTime }; Descending = $true } ) | Select-Object -First 1 $Survivors.Add($Winner) } } $SurvivingExistingPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($Survivor in @($Survivors | Where-Object { -not $_.IsIncoming })) { $null = $SurvivingExistingPaths.Add($Survivor.File.FullName) } $FilesToRemove = @($Candidates | Where-Object { -not $_.IsIncoming -and -not $SurvivingExistingPaths.Contains($_.File.FullName) } | Select-Object -ExpandProperty File -Unique) $ImportIncoming = $Survivors.Contains($IncomingCandidate) $DestinationFile = $null $RenamedIncoming = $false if ($ImportIncoming) { $RemovalPaths = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($FileToRemove in $FilesToRemove) { $null = $RemovalPaths.Add($FileToRemove.FullName) } $DesiredName = $StagedLicense.FileName $BaseName = [System.IO.Path]::GetFileNameWithoutExtension($DesiredName) $Extension = [System.IO.Path]::GetExtension($DesiredName) $CollisionIndex = 0 do { $TargetName = if ($CollisionIndex -eq 0) { $DesiredName } else { '{0}-{1:000}{2}' -f $BaseName, $CollisionIndex, $Extension } $CollisionIndex++ $CandidatePath = Join-Path -Path $DestinationDirectory -ChildPath $TargetName } while ((Test-Path -LiteralPath $CandidatePath -PathType Leaf) -and -not $RemovalPaths.Contains($CandidatePath)) $DestinationFile = $CandidatePath $RenamedIncoming = -not [string]::Equals($TargetName, $DesiredName, [System.StringComparison]::OrdinalIgnoreCase) } Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] The validated license is ready to import." Write-Host -ForegroundColor DarkGray "Source : $($SourceFile.FullName)" Write-Host -ForegroundColor DarkGray "License : $($StagedLicense.FullName)" Write-Host -ForegroundColor DarkGray "Guid : $($IncomingRecord.LicenseGuid)" Write-Host -ForegroundColor DarkGray "Destination : $(if ($DestinationFile) { $DestinationFile } else { 'No copy; an identical or newer license already exists' })" Write-Host -ForegroundColor DarkGray "Duplicates : $($FilesToRemove.Count) file(s) will be removed" if ($RenamedIncoming) { Write-Host -ForegroundColor DarkGray "Rename : Incoming file name collision detected; using $([System.IO.Path]::GetFileName($DestinationFile))" } $HasDirectoryChanges = $FilesToRemove.Count -gt 0 if (-not $ImportIncoming -and -not $HasDirectoryChanges) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Import skipped because an identical or newer license already exists." Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Testing the installed license with Show-OSDeployLicense" return Show-OSDeployLicense } $ConfirmCaption = if ($ImportIncoming) { "Import License - $($StagedLicense.FileName)" } else { 'Clean Duplicate Licenses' } $ConfirmQuestion = if ($ImportIncoming) { 'Apply these license directory changes and import the license?' } else { 'Apply these duplicate cleanup changes?' } $ConfirmMessage = "Source : $($SourceFile.FullName)`nGuid : $($IncomingRecord.LicenseGuid)`nDestination : $(if ($DestinationFile) { $DestinationFile } else { 'No copy; identical or newer license exists' })`nDuplicates : $($FilesToRemove.Count) file(s) will be removed`nRename : $(if ($RenamedIncoming) { 'Incoming file will use incrementing suffix' } else { 'None' })`n`n$ConfirmQuestion" if (-not $PSCmdlet.ShouldContinue($ConfirmMessage, $ConfirmCaption)) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] User declined: import license" return } if (-not $PSCmdlet.ShouldProcess($DestinationDirectory, 'Clean duplicate licenses and import Recast Software license')) { return } $null = New-Item -Path $DestinationDirectory -ItemType Directory -Force -ErrorAction Stop foreach ($FileToRemove in $FilesToRemove) { Remove-Item -LiteralPath $FileToRemove.FullName -Force -ErrorAction Stop Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Removed duplicate license: $($FileToRemove.FullName)" } if ($ImportIncoming) { Copy-Item -LiteralPath $StagedLicense.FullName -Destination $DestinationFile -Force -ErrorAction Stop Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [SUCCESS] License copied successfully to $DestinationFile" } else { Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Import skipped because an identical or newer license already exists." } Write-Host -ForegroundColor DarkGray "[$(Get-Date -Format s)] [INFO] Testing the installed license with Show-OSDeployLicense" Show-OSDeployLicense } catch { Write-Warning "[$(Get-Date -Format s)] Unable to import the license. $($_.Exception.Message)" } finally { if (Test-Path -LiteralPath $StagingPath -PathType Container) { Remove-Item -LiteralPath $StagingPath -Recurse -Force -ErrorAction SilentlyContinue } Write-Verbose "[$($MyInvocation.MyCommand.Name)] End" } } |