diff --git a/scripts/Common/Scripts/Config.ps1 b/scripts/Common/Scripts/Config.ps1 index 97fa6fe8..013f6d58 100644 --- a/scripts/Common/Scripts/Config.ps1 +++ b/scripts/Common/Scripts/Config.ps1 @@ -3,7 +3,9 @@ using namespace System.Security.AccessControl; using namespace System.Security.Principal; enum SetupStage { + Idle Initialize + OneShot Configure Install CreateUser @@ -244,7 +246,7 @@ $null = New-Module { Gets a value indicating whether the setup has finished. #> function Get-IsFinished { - return [bool] (Get-SetupOption $finishedOption); + return [bool] (((Get-Stage) -eq ([SetupStage]::Idle)) -or (Get-SetupOption $finishedOption)); } <# diff --git a/scripts/Common/Scripts/Operations.ps1 b/scripts/Common/Scripts/Operations.ps1 index 929a640d..d0580fce 100644 --- a/scripts/Common/Scripts/Operations.ps1 +++ b/scripts/Common/Scripts/Operations.ps1 @@ -1,15 +1,123 @@ -function Start-Operation { - param( - [scriptblock] $Action - ) +. "$PSScriptRoot/Config.ps1"; +. "$PSScriptRoot/../Types/OneShotTask.ps1"; +. "$PSScriptRoot/../../Windows/Scripts/PowerManagement.ps1"; - $Global:ErrorActionPreference = 'Inquire'; - $env:WSLENV = "CONFIG_MODULE/p"; +$null = New-Module { + . "$PSScriptRoot/../Types/OneShotTask.ps1"; + $logName = "Application"; + $oneShotTrigger = 1337; + $taskOption = "OneShotTask"; + # ToDo: Store "ProgramData/PortValhalla" path somewhere as const + $errorPath = "$env:ProgramData/PortValhalla/error.txt"; - if ($env:CONFIG_MODULE) { - $env:CONFIG_MODULE = Resolve-Path $env:CONFIG_MODULE; + $taskSetter = { + param([Nullable[OneShotTask]] $Task) + Set-SetupOption $taskOption ([string]$Task); + }; + + function Start-Operation { + param( + [scriptblock] $Action + ) + + $Global:ErrorActionPreference = 'Inquire'; + $env:WSLENV = "CONFIG_MODULE/p"; + + if ($env:CONFIG_MODULE) { + $env:CONFIG_MODULE = Resolve-Path $env:CONFIG_MODULE; + } + + New-Alias "sudo" gsudo; + & $Action; } - New-Alias "sudo" gsudo; - & $Action; -} + <# + .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"; + $action = New-ScheduledTaskAction -Execute "pwsh" -Argument (Get-StartupArguments); + schtasks /Create /SC ONEVENT /EC $logName /MO "*[System[Provider[@Name='$logName'] and EventID=$($oneShotTrigger)]]" /TR cmd.exe /TN $tempTask; + $trigger = (Get-ScheduledTask $tempTask).Triggers; + $principal = New-ScheduledTaskPrincipal -UserId (Get-SetupUser) -RunLevel Highest; + $task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger; + $null = Register-ScheduledTask -Force "PortValhalla OneShot" -InputObject $task; + $null = Unregister-ScheduledTask -Confirm:$false $tempTask; + } + + <# + .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; + + $job = & { + $identifier = "EventLog$oneShotTrigger"; + $log = [System.Diagnostics.EventLog]::new($logName); + + $null = Register-ObjectEvent -InputObject $log -EventName EntryWritten -Action { + $entry = $event.SourceEventArgs.Entry; + + if ($entry.EventID -eq $oneShotTrigger) { + $null = New-Event -SourceIdentifier $identifier; + } + }; + + Start-Job { + Wait-Event -SourceIdentifier $identifier; + }; + }; + + Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "Starting OneShot task ``$(Get-OneShotTask)``…"; + $null = Wait-Job $job; + Set-Stage $currentStage; + + if (Test-Path $errorPath) { + $errorMessage = Get-Content $errorPath; + Remove-Item $errorPath; + throw $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 @PSBoundParameters; + } + catch { + Set-Content -Path $errorPath -Value $Error; + } + finally { + Set-Stage ([SetupStage]::Idle); + Write-EventLog -LogName $logName -Source $logName -EventId $oneShotTrigger -Message "The OneShot task ``$(Get-OneShotTask)`` finished."; + } + } +}; diff --git a/scripts/Common/Types/OneShotTask.ps1 b/scripts/Common/Types/OneShotTask.ps1 new file mode 100644 index 00000000..60fc3d1f --- /dev/null +++ b/scripts/Common/Types/OneShotTask.ps1 @@ -0,0 +1,3 @@ +enum OneShotTask { + DisableUAC +}