using namespace Microsoft.Win32; using namespace System.Security.AccessControl; using namespace System.Security.Principal; enum WindowsInstallerStage { Initialize Run Cleanup Completed } enum SetupStage { Configure Install CreateUser } enum UserStage { Create Configure Cleanup Completed } $null = New-Module { [string] $configRoot = "HKLM:\Software\PortValhalla"; [string] $stageOption = "Stage"; [string] $setupStageOption = "SetupStage"; [string] $userOption = "SetupUser"; [string] $userStageOption = "UserStage"; [string] $accountOption = "MSAccount"; [string] $finishedOption = "Finished"; [RegistryKey] $key = $null; <# .SYNOPSIS Converts the specified path to linux and escapes it for the use in a script. .PARAMETER Path The path to convert. #> function ConvertTo-LinuxPath { param( [string] $Path ) & { $ErrorActionPreference = 'Continue'; $completed = $false; while (-not $completed) { $job = Start-Job { $env:Value = Resolve-Path $Using:Path; $env:WSLENV = "Value/p"; $result = wsl -- bash -c 'echo "$Value"'; wsl -e printf "%q" "$result"; }; $result = Receive-Job -Wait $job; if ((Split-Path -Leaf $Path) -ne (Split-Path -Leaf $result)) { Write-Error "The result of the path conversion of ``$Path`` was unexpected: ``$result``"; continue; } if ($job.State -ne ([System.Management.Automation.JobState]::Completed)) { Write-Error "An error occurred while converting ``$Path`` to a Linux path.`nOutput: ``$result``"; continue; } $completed = $true; } $result; }; } <# .SYNOPSIS Gets the registry key containing options related to the setup. #> function Get-SetupConfigKey { if (-not (Test-Path $configRoot)) { $key = New-Item $configRoot; $acl = Get-Acl $configRoot; $acl.AddAccessRule( [RegistryAccessRule]::new( [SecurityIdentifier]::new([WellKnownSidType]::BuiltinUsersSid, $null), [RegistryRights]::FullControl, [InheritanceFlags]::ObjectInherit -bor [InheritanceFlags]::ContainerInherit, [PropagationFlags]::None, [AccessControlType]::Allow)); Set-Acl $configRoot $acl; } else { $key = Get-Item $configRoot; } return $key; } <# .SYNOPSIS Runs a script based on the `config.fish` script. .PARAMETER Script The script to run. #> function Invoke-ConfigScript { param( [string] $Script ) $scriptPath = "$PSScriptRoot/../../Common/Scripts/config.fish"; if ($env:CONFIG_NAME) { $output = & { if (-not $IsWindows) { $escapedPath = (fish -c 'string escape $argv' "$scriptPath"); fish -c ". $escapedPath; $Script"; } else { $cleanup = { }; $projectRoot = "$PSScriptRoot/../../.."; $archisoDir = "$projectRoot/archiso"; function fish { wsl --shell-type login -- nix --extra-experimental-features "nix-command flakes" run nixpkgs`#fish -- $args } if (Test-Path -PathType Container "$archisoDir") { $git = { git -C "$projectRoot" -c safe.directory="$("$(Resolve-Path $projectRoot)".Replace("\", "/"))" @args; }; & $git rm -r --cached "$archisoDir" *> $null; $cleanup = { & $git restore --staged "$archisoDir" }; } $output = fish -c ". $(ConvertTo-LinuxPath $scriptPath); $Script"; if (-not $?) { Write-Error "The configuration could not be retrieved!"; } else { $output; } & $cleanup *> $null; } } if (-not ($output -and ($output | Test-Json))) { Write-Error "The value ``$output`` is not valid JSON."; } else { $output | ConvertFrom-Json; } } else { $null; } } <# .SYNOPSIS Gets a configuration option. .PARAMETER Name The name of the option to get. #> function Get-Config { param( [string] $Name, [Parameter(ValueFromRemainingArguments)] [string[]] $ArgumentList ) Invoke-ConfigScript "getConfig $Name --json $ArgumentList"; } <# .SYNOPSIS Gets the name of the config root. #> function Get-ConfigRootName { return "valhalla.$($IsWindows ? "windows" : "linux")"; } <# .SYNOPSIS Gets the name of the user root. #> function Get-UserRootName { return "$(Get-ConfigRootName).$($IsWindows ? "winUsers" : "users")"; } <# .SYNOPSIS Gets a user configuration. .PARAMETER UserName The name of the user to get the configuration for. .PARAMETER Name The name of the configuration to get. #> function Get-UserConfig { param( [string] $UserName = ($IsWindows ? $env:UserName : $env:USER), [Parameter(Mandatory, Position = 0)] [string] $Name ) if ((Get-Users) -contains $UserName) { Get-Config "$(Get-UserRootName).$UserName.$Name"; } else { return $null; } } <# .SYNOPSIS Gets the attributes of a configuration object. .PARAMETER Name The name of the configuration to get the attributes of. #> function Get-Attributes { param( [string] $Name ) Invoke-ConfigScript "getAttributes $Name"; } <# .SYNOPSIS Gets the names of the users to create. #> function Get-Users { [OutputType([string[]])] param() Get-Attributes "$(Get-UserRootName)"; } <# .SYNOPSIS Gets the name of the setup user. #> function Get-SetupUser { [OutputType([string])] param() Get-Config "$(Get-ConfigRootName).setupUser.name"; } <# .SYNOPSIS Gets the value of an option related to the setup. .PARAMETER Name The name of the option value to get. #> function Get-SetupOption { param( [string] $Name ) $key = Get-SetupConfigKey; if ($key.GetValueNames().Contains($Name)) { return $key.GetValue($Name); } else { return $null; } } <# .SYNOPSIS Sets the value of an option related to the setup. .PARAMETER Name The name of the option to set. .PARAMETER Value The value to set the option to. #> function Set-SetupOption { param( [string] $Name, $Value ) $key = Get-SetupConfigKey; $null = Set-ItemProperty ($key.PSPath) -Name $Name -Value $Value; } <# .SYNOPSIS Gets the name of the current stage of the Windows install script action. #> function Get-Stage { $stage = Get-SetupOption $stageOption; if ($null -ne $stage) { $stage = [WindowsInstallerStage]$stage; } return $stage; } <# .SYNOPSIS Sets the name of the current stage of the Windows install script action. .PARAMETER Name The name of the stage to set. #> function Set-Stage { param( $Name ) if (-not (($null -eq $Name) -or ($Name -is [string]))) { $Name = ([WindowsInstallerStage]$Name).ToString(); } $null = Set-SetupOption $stageOption $Name; } <# .SYNOPSIS Gets the name of the current setup stage. #> function Get-SetupStage { $stage = Get-SetupOption $setupStageOption; if ($null -ne $stage) { $stage = [SetupStage]$stage; } return $stage; } <# .SYNOPSIS Sets the current stage. .PARAMETER Name The name to set the current stage to. #> function Set-SetupStage { param( $Name ) if (-not (($null -eq $Name) -or ($Name -is [string]))) { $Name = ([SetupStage]$Name).ToString(); } $null = Set-SetupOption $setupStageOption $Name; } <# .SYNOPSIS Gets the current user to set up. #> function Get-CurrentUser { return (Get-SetupOption $userOption) ?? 0; } <# .SYNOPSIS Sets the index of the current user to set up. .PARAMETER Value The index of the user to set up. #> function Set-CurrentUser { param( [int] $Value ) Set-SetupOption $userOption $value; } <# .SYNOPSIS Gets the name of the current stage of the user setup. #> function Get-UserStage { $stage = Get-SetupOption $userStageOption; if ($null -ne $stage) { $stage = [UserStage]$stage; } return $stage; } <# .SYNOPSIS Sets the current stage of the user setup. .PARAMETER Name The name of the stage to set. #> function Set-UserStage { param( $Name ) if (-not (($null -eq $Name) -or ($Name -is [string]))) { $Name = ([UserStage]$Name).ToString(); } $null = Set-SetupOption $userStageOption $Name; } <# .SYNOPSIS Gets the name of the microsoft account to create. #> function Get-MSAccountName { return Get-SetupOption $accountOption; } <# .SYNOPSIS Sets the name of the microsoft account to create. .PARAMETER Name The name of the microsoft account to create. #> function Set-MSAccountName { param( [string] $Name ) Set-SetupOption $accountOption $Name; } <# .SYNOPSIS Gets a value indicating whether the setup has finished. #> function Get-IsFinished { return [bool](Get-SetupOption $finishedOption); } <# .SYNOPSIS Sets a value indicating whether the setup has finished. #> function Set-IsFinished { param( $Value ) Set-SetupOption $finishedOption $true; } <# .SYNOPSIS Checks whether the specified software collection is enabled. .PARAMETER Name The name of the collection to check. #> function Test-Collection { param( [string] $Name ) Get-Config "$(Get-ConfigRootName).software.$Name"; } <# .SYNOPSIS Checks whether the running system is a QEMU virtual machine. #> function Test-Qemu { ((Get-WmiObject win32_computersystem).Manufacturer) -eq "QEMU"; } <# .SYNOPSIS Checks whether the current user is the setup user. #> function Test-SetupUser { ($IsWindows ? $env:UserName : $env:USER) -eq (Get-SetupUser); } <# .SYNOPSIS Checks whether the active session is executed with admin rights. #> function Test-Admin { net session 2> $null | Out-Null; return $?; } }