using namespace System.Management.Automation.Host; . "$PSScriptRoot/../Types/OneShotTask.ps1"; $null = New-Module { . "$PSScriptRoot/Config.ps1"; . "$PSScriptRoot/../Scripts/SoftwareManagement.ps1"; . "$PSScriptRoot/../Types/OneShotTask.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Hooks.ps1"; . "$PSScriptRoot/../../Windows/Scripts/PowerManagement.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Registry.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Security.ps1"; . "$PSScriptRoot/../../Windows/Scripts/WSL.ps1"; $oneShotTaskName = "PortValhalla OneShot"; $logName = "Application"; $oneShotTrigger = 1337; $taskOption = "OneShotTask"; # ToDo: Store "ProgramData/PortValhalla" path somewhere as const $errorPath = "$env:ProgramData/PortValhalla/error.txt"; $getUserName = { "$(Get-SetupUser)OneShot"; }; $taskSetter = { param([Nullable[OneShotTask]] $Task) Set-SetupOption $taskOption ([string]$Task); }; <# .SYNOPSIS Gets the PowerShell modules required for operating. #> function Get-RequiredModules { $modules = @( @("PSScriptAnalyzer") ) + (& { if (-not $IsWindows) { @() } else { @( @("KnownFolders"), @("PSWindowsUpdate"), @("LocalAccounts", $true), @("NuGet") ) } }); for ($i = 0; $i -lt $modules.Count; $i++) { if ($modules[$i] -is [string]) { $modules[$i] = @($modules[$i]); } } return $modules; } function Start-Operation { param( [switch] $NonInteractive, [switch] $NoImplicitCleanup, [scriptblock] $Action ) $cleanup = { }; $taskPending = $false; if (-not $Global:InOperation) { if ($env:DEBUG) { Set-PSDebug -Trace 1; } if ($IsWindows -and ($null -ne (Get-OneShotTask))) { $taskPending = $true; [switch] $NonInteractive = $true; } $Global:InOperation = $true; $Global:NonInteractive = $NonInteractive; $Global:ErrorActionPreference = $NonInteractive.IsPresent ? 'Continue' : 'Inquire'; if ($IsWindows) { $env:WSLENV = "CONFIG_NAME"; New-Alias -Force "sudo" gsudo; } if (-not $NoImplicitCleanup.IsPresent) { $cleanup = { Clear-OperationResources; }; } & { $initialized = $false; while (-not $initialized) { if ($IsWindows) { if (-not ((Test-Command "choco") -and (Test-Command "refreshenv"))) { Invoke-Hook "Install-Chocolatey" -Fallback { # Install chocolatey New-Item -Force $PROFILE; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')); Import-Module $env:ChocolateyInstall/helpers/chocolateyProfile.psm1; refreshenv; }; continue; } if (-not (Test-ChocoPackage "powershell-core")) { Invoke-Hook "Install-PowerShellCore" -Fallback { choco install -y powershell-core --install-arguments='"ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 REGISTER_MANIFEST=1 USER_MU=1 ENABLE_MU=1"'; }; Restart-Intermediate; return; } if ($env:PWSH_PATH -and (Test-Path $env:PWSH_PATH)) { attrib "-R" "$env:PWSH_PATH\*" /S /D; Remove-Item -Recurse -Force $env:PWSH_PATH; continue; } if ($env:DEBUG) { $liveScriptOption = "LiveScripts"; if (($null -eq (Get-SetupOption $liveScriptOption)) -and (Test-Qemu)) { $result = $Host.UI.PromptForChoice( "Confirm", "Do you wish to swap to live scripts?", [ChoiceDescription[]]@( [ChoiceDescription]::new("&No", "Use scripts stored in the virtual machine"), [ChoiceDescription]::new("&Yes", "Use live scripts stored on the host")), 0); Set-SetupOption $liveScriptOption $result; if ($result -eq 1) { Install-ChocoPackage winfsp qemu-guest-agent; Get-Service VirtioFsSvc | Start-Service -PassThru | Set-Service -StartupType Automatic; while (-not (Test-Path Z:\)) { Start-Sleep 0.1; } foreach ($name in @("INSTALLER_SCRIPT")) { $variable = Get-Item "Env:\$name"; $path = Join-Path ` "Z:\Repositories\PortValhalla" ` ([System.IO.Path]::GetRelativePath("$PSScriptRoot/../../..", $variable.Value)); Set-Item "Env:\$name" $path; Write-Host "The new value of ``$name`` is ``$path``"; } Restart-Intermediate; exit; } } } if (-not (Test-Command "gsudo")) { Install-ChocoPackage gsudo; refreshenv; continue; } if ($env:DEBUG) { & { $sys32 = "$env:WINDIR/System32"; $osk = (Get-Item "$sys32/osk.exe").FullName; $cmd = (Get-Item "$sys32/cmd.exe").FullName; if ((Get-FileHash $osk).Hash -ne (Get-FileHash $cmd).Hash) { Set-MpPreference -ExclusionPath $osk; gsudo -d --ti move $osk "${osk}_"; gsudo -d -s copy $cmd $osk; continue; } }; } if (-not (Test-Winget)) { . "$PSScriptRoot/../../Windows/Software/winget/Manage.ps1"; continue; } if (-not (Test-Command "git")) { Install-WingetPackage Git.Git; refreshenv; continue; } if (-not (Test-Command "7z")) { Install-ChocoPackage 7zip.portable; refreshenv; continue; } if (-not (Test-Command "yq")) { Install-ChocoPackage "yq"; refreshenv; continue; } if (-not (Test-Wsl)) { Install-Wsl; Restart-Intermediate; return; } if (-not (Test-WslDistribution)) { if (-not (Test-Path (Get-WslDistributionDisk))) { Install-WslDistribution; } Register-WslDistribution; continue; } if (-not (wsl --shell-type login type -t nix)) { wsl -- sh `<`(curl -L https://nixos.org/nix/install`) --daemon --yes; wsl --shutdown; continue; } if (-not (Test-PSPackage Selenium.WebDriver)) { Write-Host "Installing browser automation tools…"; $null = Install-Package -Force Selenium.WebDriver -RequiredVersion 4.24.0 -SkipDependencies; continue; } Install-ChocoPackage selenium-gecko-driver firefox; Install-WingetPackage AutoHotkey.AutoHotkey; . "$PSScriptRoot/../../Windows/Software/PinnedItem/Manage.ps1"; } Invoke-Hook "Install-PSModules" -Fallback { foreach ($module in (Get-RequiredModules)) { $parameters = @{ }; if ($module -is [string]) { $module = @($module); } if ($module[1]) { $parameters = @{ allowPrerelease = $true; }; } if (-not (Test-PSModule $module[0])) { Install-Module -Scope AllUsers -AcceptLicense -Force -AllowClobber $module[0] @parameters; Import-Module $module[0]; } } }; if (-not $env:CONFIG_NAME) { Show-ProfileNamePrompt; } $initialized = $true; } } } if ($taskPending) { Start-OneShot; } else { & $Action; } & $cleanup; } <# .SYNOPSIS Gets the current OneShot task. #> function Get-OneShotTask { $task = Get-SetupOption $taskOption; if ($task) { return [OneShotTask]$task; } else { return $null; } } <# .SYNOPSIS Registers a task for listening to OneShot invocations. #> function Enable-OneShotListener { $tempTask = "PortValhalla Temp"; $user = & $getUserName; $password = [string]([guid]::NewGuid()); $adminGroup = @{ SID = [SecurityIdentifier]::new([WellKnownSidType]::BuiltinAdministratorsSid, $null); }; $null = New-LocalUser -Name $user -Password (ConvertTo-SecureString -AsPlainText $password); Add-LocalGroupMember -Member $user @adminGroup; $path = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"; $null = New-Item -Force -ErrorAction SilentlyContinue $path; Set-ItemProperty $path -Name $user -Value 0; $action = New-ScheduledTaskAction -Execute "pwsh" -Argument "-Command & { $([string](Get-StartupCommand)) } 2>&1 | Tee-Object -FilePath `$env:ProgramData/PortValhalla/OneShotTask.log"; schtasks /Create /SC ONEVENT /EC $logName /MO "*[System[Provider[@Name='$logName'] and EventID=$($oneShotTrigger)]]" /TR cmd.exe /TN $tempTask; $trigger = (Get-ScheduledTask $tempTask).Triggers; $null = Register-ScheduledTask -Force $oneShotTaskName -Action $action -Trigger $trigger -RunLevel Highest -User $user -Password $password; $null = Unregister-ScheduledTask -Confirm:$false $tempTask; } <# .SYNOPSIS Removes the OneShot task. #> function Disable-OneShotListener { Unregister-ScheduledTask -Confirm:$false $oneShotTaskName; $user = Get-LocalUser (& $getUserName); [string] $sid = $user.SID; Remove-LocalUser $user; Get-CimInstance Win32_UserProfile | Where-Object { $_.SID -eq $sid } | Remove-CimInstance; } <# .SYNOPSIS Invokes a one-shot task. .PARAMETER Task The task to run. #> function Invoke-OneShot { param( [OneShotTask] $Task ) & $taskSetter $Task; & { $identifier = "EventLog$oneShotTrigger"; $log = [System.Diagnostics.EventLog]::new($logName); $log.EnableRaisingEvents = $true; $null = Register-ObjectEvent -InputObject $log -EventName EntryWritten -Action { $entry = $Event.SourceEventArgs.Entry; $trigger = $Event.MessageData.Trigger; $identifier = $Event.MessageData.Identifier; if ($entry.EventID -eq $trigger) { $null = New-Event -SourceIdentifier $identifier; } } ` -MessageData @{ Trigger = $oneShotTrigger; Identifier = $identifier; }; Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "Starting OneShot task ``$(Get-OneShotTask)``…"; for ($i = 0; $i -lt 2; $i++) { Remove-Event -EventIdentifier (Wait-Event -SourceIdentifier $identifier).EventIdentifier; } }; if (Test-Path $errorPath) { $errorMessage = Get-Content $errorPath; Remove-Item $errorPath; if ($errorMessage) { Write-Error $errorMessage; } } } # ToDo: Store Run-OneShot and Receive-OneShot somewhere else in Windows folder <# .SYNOPSIS Executes the specified action and notifies the OneShot task executor. #> function Start-OneShot { try { Write-Host "Running OneShot task ``$(Get-OneShotTask)``"; switch (Get-OneShotTask) { ([OneShotTask]::InitializeMSAccount) { Initialize-UserCreation; } ([OneShotTask]::DisableUAC) { Disable-UAC; Register-Setup; } } } catch { Set-Content -Path $errorPath -Value $Error; Set-UserPermissions $errorPath; } finally { & $taskSetter $null; Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished."; } } <# .SYNOPSIS Clears resources allocated during the operation. #> function Clear-OperationResources { if ($IsWindows) { Uninstall-WslDistribution; $null = Uninstall-Package Selenium.WebDriver -ErrorAction Continue; Uninstall-ChocoPackage 7zip.portable gsudo selenium-gecko-driver yq; Uninstall-WingetPackage AutoHotkey.AutoHotkey; } foreach ($module in (Get-RequiredModules)) { Remove-Module -Force $module[0] -ErrorAction SilentlyContinue; Uninstall-Module -Force -Name $module[0] -ErrorAction SilentlyContinue; } } };