#Requires -Version 5.1 [CmdletBinding()] param( [Parameter(Mandatory)] [ValidatePattern('^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$')] [string]$Version, [string]$OutputRoot = (Join-Path $PSScriptRoot '..\artifacts\releases'), [string]$ServerContentPath, [switch]$SkipBuild ) $ErrorActionPreference = 'Stop' $repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $resolvedOutputRoot = [IO.Path]::GetFullPath($OutputRoot) if (-not $resolvedOutputRoot.StartsWith($repositoryRoot + '\', [StringComparison]::OrdinalIgnoreCase)) { throw 'OutputRoot must be beneath the repository root.' } function Copy-RequiredFile { param( [Parameter(Mandatory)][string]$Source, [Parameter(Mandatory)][string]$Destination ) if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw "Required package input is missing: $Source" } New-Item -ItemType Directory -Path (Split-Path $Destination -Parent) -Force | Out-Null Copy-Item -LiteralPath $Source -Destination $Destination -Force } function Write-PackageManifest { param( [Parameter(Mandatory)][string]$PackageRoot, [Parameter(Mandatory)][string]$PackageVersion, [Parameter(Mandatory)][string]$PackageKind ) $resolvedPackageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path.TrimEnd('\') $files = @(Get-ChildItem -LiteralPath $resolvedPackageRoot -Recurse -File | Where-Object Name -ne 'package-manifest.json' | Sort-Object FullName | ForEach-Object { [ordered]@{ Path = $_.FullName.Substring($resolvedPackageRoot.Length).TrimStart('\') Sha256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash Length = $_.Length } }) $manifest = [ordered]@{ SchemaVersion = 1 Product = 'SGU Credential Provider' PackageKind = $PackageKind Version = $PackageVersion CreatedAt = (Get-Date).ToUniversalTime().ToString('o') Files = $files } [IO.File]::WriteAllText( (Join-Path $resolvedPackageRoot 'package-manifest.json'), ($manifest | ConvertTo-Json -Depth 6), [Text.UTF8Encoding]::new($false)) } if (-not $SkipBuild) { & (Join-Path $PSScriptRoot 'Publish-Lab.ps1') -Configuration Release ` -OutputRoot (Join-Path $repositoryRoot 'artifacts') | Out-Null } $brokerOutput = Join-Path $repositoryRoot 'artifacts\broker' $providerOutput = Join-Path $repositoryRoot 'artifacts\credential-provider' $prerequisiteRoot = Join-Path $repositoryRoot 'artifacts\prerequisites' $runtimeInstaller = Get-ChildItem -LiteralPath $prerequisiteRoot -Filter '*x64*.exe' ` -File -ErrorAction SilentlyContinue | Sort-Object Name -Descending | Select-Object -First 1 if (-not $runtimeInstaller) { throw 'Place the offline Microsoft .NET 10 x64 runtime installer in artifacts\prerequisites.' } New-Item -ItemType Directory -Path $resolvedOutputRoot -Force | Out-Null $clientRoot = Join-Path $resolvedOutputRoot "sgu-client-bootstrap-$Version" $serverRoot = Join-Path $resolvedOutputRoot "sgu-server-bootstrap-$Version" $linuxClientRoot = Join-Path $resolvedOutputRoot "sgu-linux-client-bootstrap-$Version" $clientZip = "$clientRoot.zip" $serverZip = "$serverRoot.zip" $linuxClientZip = "$linuxClientRoot.zip" foreach ($target in @($clientRoot,$serverRoot,$linuxClientRoot,$clientZip,$serverZip,$linuxClientZip)) { if (Test-Path -LiteralPath $target) { throw "Release target already exists: $target" } } New-Item -ItemType Directory -Path $clientRoot,$serverRoot,$linuxClientRoot -Force | Out-Null $welcomeFontNames = @( 'IndivisaTextSans-Regular.otf', 'IndivisaTextSans-Bold.otf', 'IndivisaTextSans-BoldItalic.otf', 'IndivisaTextSerif-Regular.otf', 'IndivisaTextSerif-BoldItalic.otf' ) Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Invoke-SguClientBootstrap.ps1') ` -Destination (Join-Path $clientRoot 'Invoke-SguClientBootstrap.ps1') Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguClientEnrollment.cmd') ` -Destination (Join-Path $clientRoot 'Start-SguClientEnrollment.cmd') $clientScripts = @( 'Enable-LabRemoteAccess.ps1', 'Enable-SguClientMonitoring.ps1', 'Enroll-SguDomainClient.ps1', 'Install-CredentialProvider.ps1', 'Install-SguEnrollmentGuard.ps1', 'Register-SguClientCertificate.ps1', 'Repair-SguClientEnrollment.ps1', 'Test-SguClientEnrollment.ps1' ) foreach ($scriptName in $clientScripts) { Copy-RequiredFile -Source (Join-Path $PSScriptRoot $scriptName) ` -Destination (Join-Path $clientRoot "payload\scripts\$scriptName") } Copy-Item -Path (Join-Path $providerOutput '*') ` -Destination (New-Item -ItemType Directory ` -Path (Join-Path $clientRoot 'payload\credential-provider') -Force).FullName ` -Recurse -Force Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\lasalle-mascot-account.png') ` -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\user.png') Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') ` -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\darkblue.jpg') Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.ps1') ` -Destination (Join-Path $clientRoot 'payload\credential-provider\branding\Set-SguWelcomeWallpaper.ps1') foreach ($fontName in $welcomeFontNames) { Copy-RequiredFile -Source (Join-Path $repositoryRoot "assets\branding\fonts\$fontName") ` -Destination (Join-Path $clientRoot "payload\credential-provider\branding\fonts\$fontName") } Copy-RequiredFile -Source $runtimeInstaller.FullName ` -Destination (Join-Path $clientRoot "payload\prerequisites\$($runtimeInstaller.Name)") Write-PackageManifest -PackageRoot $clientRoot -PackageVersion $Version -PackageKind Client Compress-Archive -Path (Join-Path $clientRoot '*') -DestinationPath $clientZip ` -CompressionLevel Optimal # Linux clients use their native PAM/SSSD sign-in stack rather than the Windows # Credential Provider. Keep their self-contained bootstrap independent so a # Linux administrator never receives Windows binaries or certificate material. Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Enroll-SguLinuxDomainClient.sh') ` -Destination (Join-Path $linuxClientRoot 'Enroll-SguLinuxDomainClient.sh') Copy-RequiredFile -Source (Join-Path $repositoryRoot 'docs\linux-client-enrollment.md') ` -Destination (Join-Path $linuxClientRoot 'README.md') Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.sh') ` -Destination (Join-Path $linuxClientRoot 'welcome-wallpaper\Set-SguWelcomeWallpaper.sh') Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') ` -Destination (Join-Path $linuxClientRoot 'welcome-wallpaper\darkblue.jpg') foreach ($fontName in $welcomeFontNames) { Copy-RequiredFile -Source (Join-Path $repositoryRoot "assets\branding\fonts\$fontName") ` -Destination (Join-Path $linuxClientRoot "welcome-wallpaper\fonts\$fontName") } Write-PackageManifest -PackageRoot $linuxClientRoot -PackageVersion $Version -PackageKind LinuxClient Compress-Archive -Path (Join-Path $linuxClientRoot '*') -DestinationPath $linuxClientZip ` -CompressionLevel Optimal Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Initialize-SguDomainController.ps1') ` -Destination (Join-Path $serverRoot 'Initialize-SguDomainController.ps1') Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguServerBootstrap.cmd') ` -Destination (Join-Path $serverRoot 'Start-SguServerBootstrap.cmd') $serverScripts = @( 'Deploy-AuthBroker.ps1', 'Enable-SguServerRemoteManagement.ps1', 'Get-SguUsageReport.ps1', 'Get-SguBrokerLog.ps1', 'Install-SguDomainMonitoring.ps1', 'Invoke-SguMonitoringMaintenance.ps1', 'New-LabCertificate.ps1', 'Register-SguClientCertificate.ps1', 'Set-LabBrokerDns.ps1', 'Set-SguDomainComputerPolicies.ps1', 'Set-SguDomainUserPolicies.ps1' ) foreach ($scriptName in $serverScripts) { Copy-RequiredFile -Source (Join-Path $PSScriptRoot $scriptName) ` -Destination (Join-Path $serverRoot "payload\scripts\$scriptName") } Copy-Item -Path (Join-Path $brokerOutput '*') ` -Destination (New-Item -ItemType Directory ` -Path (Join-Path $serverRoot 'payload\broker') -Force).FullName ` -Recurse -Force $serverContentTarget = Join-Path $serverRoot 'payload\server-content\Packages' New-Item -ItemType Directory -Path $serverContentTarget -Force | Out-Null if ($ServerContentPath) { if (-not (Test-Path -LiteralPath $ServerContentPath -PathType Container)) { throw 'ServerContentPath does not exist.' } Copy-Item -Path (Join-Path $ServerContentPath '*') ` -Destination $serverContentTarget -Recurse -Force } Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Set-SguWelcomeWallpaper.ps1') ` -Destination (Join-Path $serverContentTarget 'welcome-wallpaper\Set-SguWelcomeWallpaper.ps1') Copy-RequiredFile -Source (Join-Path $repositoryRoot 'assets\branding\darkblue.jpg') ` -Destination (Join-Path $serverContentTarget 'welcome-wallpaper\darkblue.jpg') foreach ($fontName in $welcomeFontNames) { Copy-RequiredFile -Source (Join-Path $repositoryRoot "assets\branding\fonts\$fontName") ` -Destination (Join-Path $serverContentTarget "welcome-wallpaper\fonts\$fontName") } Write-PackageManifest -PackageRoot $serverRoot -PackageVersion $Version -PackageKind Server Compress-Archive -Path (Join-Path $serverRoot '*') -DestinationPath $serverZip ` -CompressionLevel Optimal $checksums = @( ("{0} {1}" -f (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash, (Split-Path $clientZip -Leaf)) ("{0} {1}" -f (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash, (Split-Path $serverZip -Leaf)) ("{0} {1}" -f (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash, (Split-Path $linuxClientZip -Leaf)) ) $checksumsPath = Join-Path $resolvedOutputRoot "SHA256SUMS-$Version.txt" [IO.File]::WriteAllLines($checksumsPath, $checksums, [Text.UTF8Encoding]::new($false)) [pscustomobject]@{ Version = $Version ClientPackage = $clientZip ClientSha256 = (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash LinuxClientPackage = $linuxClientZip LinuxClientSha256 = (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash ServerPackage = $serverZip ServerSha256 = (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash Checksums = $checksumsPath RuntimeInstaller = $runtimeInstaller.Name }