. "$PSScriptRoot/Config.ps1"; . "$PSScriptRoot/../Types/OneShotTask.ps1"; . "$PSScriptRoot/../../Windows/Scripts/PowerManagement.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Registry.ps1"; . "$PSScriptRoot/../../Windows/Scripts/Security.ps1"; . "$PSScriptRoot/../../Windows/Scripts/WSL.ps1"; $null = New-Module { . "$PSScriptRoot/../Types/OneShotTask.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); }; function Start-Operation { param( [switch] $NonInteractive, [switch] $NoImplicitCleanup, [scriptblock] $Action ) $cleanup = { }; if (-not $Global:InOperation) { if ($env:DEBUG) { Set-PSDebug -Trace 1; } $Global:InOperation = $true; $Global:ErrorActionPreference = $NonInteractive.IsPresent ? 'Continue' : 'Inquire'; if ($IsWindows) { $env:WSLENV = "CONFIG_MODULE/p"; if ($env:CONFIG_MODULE) { $env:CONFIG_MODULE = Resolve-Path $env:CONFIG_MODULE; } if (Test-Admin) { Disable-WindowsUpdateAutoRestart; } New-Alias -Force "sudo" gsudo; } if (-not $NoImplicitCleanup.IsPresent) { $cleanup = { Clear-OperationResources; }; } } & $Action; & $cleanup; } <# .SYNOPSIS Gets the current OneShot task. #> function Get-OneShotTask { [OneShotTask](Get-SetupOption $taskOption); } <# .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 ) $currentStage = Get-Stage; Set-Stage ([SetupStage]::OneShot); & $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; } }; Set-Stage $currentStage; 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 { param( [scriptblock] $Action ) try { Start-Operation -NonInteractive @PSBoundParameters; } catch { Set-Content -Path $errorPath -Value $Error; Set-UserPermissions $errorPath; } finally { Set-Stage ([SetupStage]::Idle); Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished."; } exit; } <# .SYNOPSIS Clears resources allocated during the operation. #> function Clear-OperationResources { Uninstall-WslDistribution; } };