#!/bin/pwsh . "$PSScriptRoot/../../lib/Scripting.ps1"; function Start-Setup { param($ConfigurationName) . "$PSScriptRoot/../../lib/Settings.ps1"; . "$PSScriptRoot/../../lib/Scripting.ps1"; $Global:InformationPreference = "Continue"; $Global:ErrorActionPreference = "Inquire"; $env:CONFIG_NAME ??= $ConfigurationName; $null = $env:SETUP_SCRIPT_NAME ??= "$PSScriptRoot/Install.ps1"; $env:WSLENV = "CONFIG_NAME"; Show-ProfileNamePrompt; $valhallaConfig = ConvertFrom-Json (Get-Content "$PSScriptRoot/../../../.config/$env:CONFIG_NAME.json"); [xml]$unattendedConfig = [xml]::new(); $unattendedConfig.PreserveWhitespace = $true; $readerSettings = [System.Xml.XmlReaderSettings]::new(); $readerSettings.IgnoreComments = $true; $reader = [System.Xml.XmlReader]::Create("$PSScriptRoot/../Resources/Autounattend.template.xml", $readerSettings); $unattendedConfig.Load($reader); $namespace = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $unattendedConfig.NameTable; $namespace.AddNamespace("ua", $unattendedConfig.DocumentElement.NamespaceURI); function Get-InstallDisk { [int]$installTarget.Disk.InnerText; } function Get-PassSettings { [OutputType([xml])] param( [string] $passName ) return $unattendedConfig.SelectSingleNode("/ua:unattend/ua:settings[@pass='$passName']", $namespace); } function Get-Component { [OutputType([xml])] param( [System.Xml.XmlNode] $pass, [string] $componentName = $null ); $Local:selector = "./ua:component"; if ($null -ne $componentName) { $Local:selector += "[@name='$componentName']"; } return $pass.SelectSingleNode($Local:selector, $namespace); } function Get-RemoteScriptPath($path) { $relativePath = [System.IO.Path]::GetRelativePath("$PSScriptRoot/../../..", $path); Join-Path $env:REMOTE_PROJECT_PATH $relativePath; } function Get-PathInjection($path) { "(Join-Path `$env:SystemDrive $(ConvertTo-Injection $path))"; } function Get-ScriptPathInjection($path) { Get-PathInjection (Get-RemoteScriptPath $path); } function Get-DiskConfig { param( [int] $Disk ) $node = $disks | Where-Object { $_.SelectSingleNode("./ua:DiskID", $namespace).InnerText -eq $Disk }; $creations = $node.SelectSingleNode("./ua:CreatePartitions", $namespace); $modifications = $node.SelectSingleNode("./ua:ModifyPartitions", $namespace); @{ PartitionCreationContainer = $creations; PartitionCreations = $creations.SelectNodes("./ua:CreatePartition", $namespace); PartitionModificationContainer = $modifications; PartitionModifications = $modifications.SelectNodes("./ua:ModifyPartition", $namespace); }; } function Move-PartitionRange { param ( [int] $Disk = (Get-InstallDisk), [Parameter(Position = 0)] [int] $From = 0, [Parameter(Position = 1)] [System.Nullable[int]] $To = $null, [Parameter(Position = 2)] [int] $By = 1 ) $diskInfo = Get-DiskConfig $Disk; if ((Get-InstallDisk) -eq $Disk) { $partition = [int]$installTarget.Partition.InnerText; if (($partition -ge $From) -and (($null -eq $To) -or ($partition -le $To))) { $installTarget.Partition.InnerText = "$($partition + $By)"; } } foreach ($config in @( @($diskInfo.PartitionCreations, @("Order")), @($diskInfo.PartitionModifications, @("Order", "PartitionID")))) { foreach ($partition in $config[0]) { foreach ($property in $config[1]) { $partitionNode = $partition.SelectSingleNode("./ua:$property", $namespace); $partitionID = [int]$partitionNode.InnerText; $newID = $partitionID; if (($newID -ge $From) -and (($null -eq $To) -or ($newID -le $To))) { $newID += $By; } if ($partitionID -ne $newID) { $partitionNode.InnerText = "$newID"; } } } } } function Add-Partition { param ( [int] $Disk = (Get-InstallDisk), [Parameter(Position = 0)] [int] $Index, [Parameter(Position = 1)] [int] $Size, [Parameter(Position = 2)] [string] $Type = "Primary" ) $diskInfo = Get-DiskConfig $Disk; Move-PartitionRange -Disk $Disk -From $Index -By 1; $configs = @( @( $diskInfo.PartitionCreations, 0, @( @("Order", "$Index"), @("Type", "$Type"), @("Size", "$Size"))), @( $diskInfo.PartitionModifications, 2, @( @("Order", "$Index"), @("PartitionID", "$Index"))) ); foreach ($config in $configs) { $partition = $config[0][$config[1]]; $newPartition = $partition.CloneNode($true); foreach ($entry in $config[2]) { $newPartition.SelectSingleNode("./ua:$($entry[0])", $namespace).InnerText = $entry[1]; } $null = $partition.ParentNode.AppendChild($newPartition); } } function Move-Partition { param ( [int] $Disk = (Get-InstallDisk), [Parameter(Position = 0)] [int] $From, [Parameter(Position = 1)] [int] $To ) Move-PartitionRange -Disk $Disk $From $From (-1 * ($From + 1)); if ($From -gt $To) { Move-PartitionRange -Disk $Disk $To ($From - 1); } elseif ($From -lt $To) { Move-PartitionRange -Disk $Disk ($From + 1) $To - 1; } Move-PartitionRange -Disk $Disk -1 -1 ($To + 1) } function Add-StartupCommand { param( [string] $Script, [string] $Description ) $installationCommand = $oobeSettings.SelectSingleNode("./ua:FirstLogonCommands/ua:SynchronousCommand[last()]", $namespace); $newCommand = $installationCommand.ParentNode.AppendChild($installationCommand.CloneNode($true)); $newCommand.SelectSingleNode("./ua:CommandLine", $namespace).InnerText = $Script; $orderElement = $newCommand.SelectSingleNode("./ua:Order", $namespace); $orderElement.InnerText = ([int]($orderElement.InnerText) + 1); $newCommand.SelectSingleNode("./ua:Description", $namespace).InnerText = $Description; } # Collect necessary variables $winpePass = Get-PassSettings "windowsPE"; $setupConfig = Get-Component $winpePass "Microsoft-Windows-Setup"; $disks = $setupConfig.SelectNodes("./ua:DiskConfiguration/ua:Disk", $namespace); $installTarget = & { $target = $setupConfig.SelectSingleNode("./ua:ImageInstall/ua:OSImage/ua:InstallTo", $namespace); @{ Disk = $target.SelectSingleNode("./ua:DiskID", $namespace); Partition = $target.SelectSingleNode("./ua:PartitionID", $namespace); }; }; # Adjust unattended settings $computerName = (Get-Component (Get-PassSettings "specialize") "Microsoft-Windows-Shell-Setup").SelectSingleNode("./ua:ComputerName", $namespace); $computerName.InnerText = $valhallaConfig.hostname; # Execute corresponding installer script after startup $oobeSettings = (Get-Component (Get-PassSettings "oobeSystem") "Microsoft-Windows-Shell-Setup"); foreach ($xpath in @("./ua:AutoLogon/ua:Username", "./ua:UserAccounts/ua:LocalAccounts/ua:LocalAccount/ua:Name", "./ua:UserAccounts/ua:LocalAccounts/ua:LocalAccount/ua:DisplayName")) { $oobeSettings.SelectSingleNode($xpath, $namespace).InnerText = $valhallaConfig.setupUser.name; } Add-StartupCommand ` -Script ( "powershell -Command " + ($env:DEBUG ? "`$env:DEBUG = $([int]$env:DEBUG);" : "") + "`$env:VALHALLA_ROOT = $(Get-PathInjection $env:REMOTE_PROJECT_PATH);" + "`$env:PWSH_PATH = $(Get-PathInjection $env:PWSH_PATH);" + "`$env:INSTALLER_SCRIPT = $(Get-ScriptPathInjection $env:SETUP_SCRIPT_NAME);" + "`$env:CONFIG_NAME = $(ConvertTo-Injection $env:CONFIG_NAME);" + "& (Join-Path `$env:PWSH_PATH pwsh) `$env:INSTALLER_SCRIPT;") ` -Description "Install PowerShell Core and git and run setup script"; if ($valhallaConfig.dualboot.enable) { $diskSize = [long](ConvertFrom-Csv (wmic diskdrive where "Index=$(Get-InstallDisk)" get Size | ForEach-Object { "$_".Trim(); })).Size; # Calculate Linux size $linuxSize = ([System.Math]::Floor(($diskSize * ($valhallaConfig.dualboot.linuxPercentage / 100)) / 1024 / 1024 / 1024)) * 1024; # Resize EFI partition to 1GB (for GRUB) (Get-DiskConfig (Get-InstallDisk)).PartitionCreations[1].SelectSingleNode("./ua:Size", $namespace).InnerText = "$(1024)"; # Move boot partition to the beginning Move-Partition 2 1; # Add a Swap spacer $swapSpacer = 100; Add-Partition 2 $swapSpacer; Add-Partition 3 ($linuxSize - $swapSpacer); } $unattendedConfigFile = "X:\unattend.xml"; $unattendedConfig.PreserveWhitespace = $true; $unattendedConfig.Save($unattendedConfigFile); Write-Warning "Attention: This program will completely wipe your current disk #1 and install Windows on it. Are you sure you want to do this?"; Read-Host -Prompt "Hit enter to continue or CTRL+C to abort"; & "$SETUP_DRIVE\setup.exe" /Unattend:$unattendedConfigFile; } Start-Setup @args;