Files

80 lines
7.0 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][Alias('SourcePath')][string]$WimPath,
[string]$OutputPath,
[string]$SwitchName = 'external',
[string]$ImageName = 'Windows 11 Pro for Workstations',
[string]$VirtioIsoPath,
[string]$ServicingIsoPath,
[ValidateRange(1,256)][int]$CpuCount = 4,
[ValidateRange(2,1024)][int]$RamGiB = 8,
[ValidateRange(64,65536)][int]$DiskGiB = 128,
[string]$TimeZone = 'Eastern Standard Time',
[switch]$InstallUpdates,
[switch]$SkipChrome,
[switch]$SkipVSCode,
[string]$ConfigPath,
[AllowEmptyString()][string]$ProductKey='',
[ValidateSet('Legacy','Kms','Custom','None')][string]$ProductKeyMode='Legacy',
[AllowEmptyString()][string]$KmsServer
)
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'scripts\Project.Common.ps1')
. (Join-Path $PSScriptRoot 'scripts\Configuration.Common.ps1')
$settings = Get-ProjectSettings
if ($PSBoundParameters.ContainsKey('ProductKey') -and $ProductKeyMode -eq 'Legacy') { $ProductKeyMode=$(if($ProductKey){'Custom'}else{'None'}) }
$selectedKey=Resolve-PveProductKey -ImageName $ImageName -Mode $ProductKeyMode -ProductKey $ProductKey
if (-not $PSBoundParameters.ContainsKey('KmsServer')) { $KmsServer=Get-PveDefaultKmsServer }
$KmsServer=(ConvertTo-PveKmsEndpoint -Address $KmsServer).Address
if (-not $OutputPath) { $OutputPath = Join-Path $settings.AssetsRoot ('output\' + (Get-DefaultImageFileName -ImageName $ImageName)) }
if (-not $ConfigPath) { $ConfigPath = Join-Path $ProjectRoot 'local\image.ini' }
if (-not $VirtioIsoPath) { $VirtioIsoPath=Find-VirtioInstallerIso -ProjectRoot $ProjectRoot -AssetsRoot $settings.AssetsRoot;if(-not$VirtioIsoPath){$VirtioIsoPath=Join-Path $settings.AssetsRoot 'iso\virtio-win.iso'} }
$source = [IO.Path]::GetFullPath($WimPath)
if(-not$ServicingIsoPath -and [IO.Path]::GetExtension($source) -ieq '.iso'){$ServicingIsoPath=$source}
if(-not$ServicingIsoPath){$ServicingIsoPath=Find-WindowsInstallerIso -ProjectRoot $ProjectRoot -AssetsRoot $settings.AssetsRoot}
if($ServicingIsoPath){$ServicingIsoPath=[IO.Path]::GetFullPath($ServicingIsoPath);if(-not(Test-Path -LiteralPath $ServicingIsoPath -PathType Leaf) -or [IO.Path]::GetExtension($ServicingIsoPath) -ine '.iso'){throw 'Windows PE servicing requires an existing Windows installer ISO.'}}
$output = [IO.Path]::GetFullPath($OutputPath)
$virtio = [IO.Path]::GetFullPath($VirtioIsoPath)
$config = [IO.Path]::GetFullPath($ConfigPath)
foreach ($entry in @{Source=$source;Output=$output;Virtio=$virtio;Config=$config;ImageName=$ImageName;SwitchName=$SwitchName;TimeZone=$TimeZone}.GetEnumerator()) { Assert-ConfigValue -Value $entry.Value -Name $entry.Key }
if (-not (Test-Path -LiteralPath $source -PathType Leaf) -or [IO.Path]::GetExtension($source).ToLowerInvariant() -notin @('.iso','.wim')) { throw 'Select an existing Windows ISO or install.wim file.' }
if ([IO.Path]::GetExtension($output).ToLowerInvariant() -ne '.qcow2') { throw 'Output must have the .qcow2 extension.' }
if (Test-Path -LiteralPath $output) { throw 'Output already exists; choose a new output path.' }
if (Test-Path -LiteralPath ([IO.Path]::ChangeExtension($output,'.vhdx'))) { throw 'The corresponding temporary VHDX already exists; choose a new output path.' }
if (-not (Test-Path -LiteralPath $virtio -PathType Leaf) -or [IO.Path]::GetExtension($virtio).ToLowerInvariant() -ne '.iso') { throw 'VirtIO ISO is missing or is not an ISO file.' }
$localRoot = [IO.Path]::GetFullPath((Join-Path $ProjectRoot 'local')).TrimEnd('\') + '\'
if (-not $config.StartsWith($localRoot, [StringComparison]::OrdinalIgnoreCase)) { throw 'Generated configuration must be inside the project local directory.' }
if ($ProjectRoot.Contains(',')) { throw 'The upstream resource parser does not support a comma in the project path.' }
[TimeZoneInfo]::FindSystemTimeZoneById($TimeZone) | Out-Null
$packages = @(Get-SelectedImagePackages -SkipChrome:$SkipChrome -SkipVSCode:$SkipVSCode)
$virtioInstaller = Initialize-VirtioGuestTools -Settings $settings -VirtioIsoPath $virtio
$installerPaths = @{}
foreach ($package in $packages) {
if ($package.Name -eq 'VirtIO') { $installer = $virtioInstaller }
else { $installer = Find-ImagePackageInstaller -Package $package -Directories @((Join-Path $ProjectRoot 'data\custom-resources'),(Join-Path $settings.AssetsRoot 'custom-resources')) }
if (-not $installer) {
Assert-ImagePackage -Path (Join-Path $settings.AssetsRoot ('custom-resources\' + $package.File)) -Package $package
}
$installerPaths[$package.Name] = $installer
}
$runId = (Get-Date -Format 'yyyyMMdd-HHmmss') + '-' + [guid]::NewGuid().ToString('N').Substring(0,8)
$staged = Join-Path $ProjectRoot ('local\staged-resources\' + $runId)
New-Item -ItemType Directory -Path $staged -Force | Out-Null
foreach ($package in $packages) { Copy-Item -LiteralPath $installerPaths[$package.Name] -Destination (Join-Path $staged $package.File) }
foreach ($file in Get-ChildItem -LiteralPath (Join-Path $ProjectRoot 'resources') -File) { Copy-Item -LiteralPath $file.FullName -Destination (Join-Path $staged $file.Name) }
Copy-Item -LiteralPath (Join-Path $ProjectRoot 'scripts\Software.Selection.ps1') -Destination (Join-Path $staged 'Software.Selection.ps1')
Copy-Item -LiteralPath (Join-Path $ProjectRoot 'scripts\Package.Validation.ps1') -Destination (Join-Path $staged 'Package.Validation.ps1')
$software = [ordered]@{SchemaVersion=1;InstallChrome=(-not $SkipChrome);InstallVSCode=(-not $SkipVSCode);ServicingIsoPath=$ServicingIsoPath;ProductKeyMode=$ProductKeyMode;KmsServer=$KmsServer;Packages=@($packages | Select-Object Name,File,Validation)}
[IO.File]::WriteAllText((Join-Path $staged 'software-selection.json'), ($software | ConvertTo-Json -Depth 5), [Text.UTF8Encoding]::new($false))
$text = [IO.File]::ReadAllText((Join-Path $ProjectRoot 'config\image.example.ini'))
$password = 'Build!' + [guid]::NewGuid().ToString('N')
$text = $text.Replace('__EPHEMERAL_BUILD_PASSWORD__',$password).Replace('__WIM_PATH__',$source).Replace('__OUTPUT_PATH__',$output).Replace('__ASSET_ROOT__',$settings.AssetsRoot).Replace('__STAGED_RESOURCES__',$staged).Replace('__PROJECT_ROOT__',$ProjectRoot)
$keyValue='""'
if ($selectedKey) { $keyValue=$selectedKey }
$values = [ordered]@{image_name=$ImageName;product_key=$keyValue;external_switch=$SwitchName;virtio_iso_path=$virtio;cpu_count=[string]$CpuCount;ram_size=[string]([long]$RamGiB*1GB);disk_size=[string]([long]$DiskGiB*1GB);time_zone=$TimeZone;install_updates=$InstallUpdates.IsPresent.ToString()}
foreach ($key in $values.Keys) { $text = Set-ImageConfigValue -Text $text -Key $key -Value $values[$key] }
New-Item -ItemType Directory -Path ([IO.Path]::GetDirectoryName($config)) -Force | Out-Null
# The upstream parser requires an INI without a BOM.
[IO.File]::WriteAllText($config,$text,[Text.UTF8Encoding]::new($false))
[PSCustomObject]@{ConfigPath=$config;SourcePath=$source;ImageName=$ImageName;KmsServer=$KmsServer;OutputPath=$output;DiskGiB=$DiskGiB;StagedResources=$staged;InstallChrome=(-not $SkipChrome);InstallVSCode=(-not $SkipVSCode);BuildPassword='Generated automatically.'}