Enhance user interaction with new dialog features and improve default browser handling

This commit is contained in:
Arne Moerman
2026-05-08 14:03:56 +02:00
parent 34ea1eb4b2
commit c26898d4d2
6 changed files with 498 additions and 38 deletions
+200 -12
View File
@@ -180,19 +180,30 @@ function Read-OperationResult {
# Feature discovery
# ═════════════════════════════════════════════════════════════════════════════
function Get-Features {
<#
.SYNOPSIS
Dot-sources every .ps1 in internal\features\ and collects their $FeatureMeta.
Returns an ordered list of [PSCustomObject]@{ File; Meta }.
#>
$featuresDir = Join-Path $script:InternalRoot 'features'
$featureFiles = Get-ChildItem -Path $featuresDir -Filter '*.ps1' -ErrorAction SilentlyContinue |
Sort-Object Name
function Get-FeatureFiles {
$featuresDir = Join-Path $script:InternalRoot 'features'
if (-not (Test-Path $featuresDir)) {
return @()
}
return @(Get-ChildItem -Path $featuresDir -Filter '*.ps1' -ErrorAction SilentlyContinue |
Sort-Object Name)
}
function Get-FeatureCacheSignature {
param([System.IO.FileInfo[]]$FeatureFiles)
return (($FeatureFiles | ForEach-Object {
'{0}:{1}' -f $_.FullName, $_.LastWriteTimeUtc.Ticks
}) -join '|')
}
function Load-FeaturesFromFiles {
param([System.IO.FileInfo[]]$FeatureFiles)
$features = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($file in $featureFiles) {
foreach ($file in $FeatureFiles) {
try {
. $file.FullName # defines $FeatureMeta + Invoke-Feature
$meta = $FeatureMeta # copy before the next iteration overwrites it
@@ -208,7 +219,140 @@ function Get-Features {
}
}
return $features
return @($features)
}
function Complete-FeaturePrewarm {
param([switch]$Wait)
if (-not $script:FeaturesPrewarmTask) {
return $false
}
$task = $script:FeaturesPrewarmTask
if ($Wait) {
$null = $task.Handle.AsyncWaitHandle.WaitOne()
}
if (-not $task.Handle.IsCompleted) {
return $false
}
try {
$result = $task.PowerShell.EndInvoke($task.Handle)
if ($result.Count -gt 0) {
$payload = $result[0]
$script:FeaturesCache = @($payload.Features)
$script:FeaturesCacheSignature = [string]$payload.Signature
foreach ($errMsg in @($payload.Errors)) {
Write-Log -Level Warn -Message $errMsg -Feature 'Configure'
}
}
}
catch {
Write-Log -Level Warn -Message "Feature prewarm completion failed: $_" -Feature 'Configure'
}
finally {
try { $task.PowerShell.Dispose() } catch {}
$script:FeaturesPrewarmTask = $null
}
return $true
}
function Start-FeaturePrewarmAsync {
if ($script:FeaturesPrewarmTask) {
return
}
$featureFiles = Get-FeatureFiles
if ($featureFiles.Count -eq 0) {
$script:FeaturesCache = @()
$script:FeaturesCacheSignature = ''
return
}
$ps = [powershell]::Create()
$scriptBlock = @'
param([string]$FeaturesDir)
$featureFiles = Get-ChildItem -Path $FeaturesDir -Filter '*.ps1' -ErrorAction SilentlyContinue |
Sort-Object Name
$signature = (($featureFiles | ForEach-Object {
'{0}:{1}' -f $_.FullName, $_.LastWriteTimeUtc.Ticks
}) -join '|')
$features = [System.Collections.Generic.List[PSCustomObject]]::new()
$errors = [System.Collections.Generic.List[string]]::new()
foreach ($file in $featureFiles) {
try {
. $file.FullName
$meta = $FeatureMeta
$features.Add([PSCustomObject]@{
File = $file.FullName
Meta = $meta
})
}
catch {
$errors.Add(("Failed to load feature '{0}' during prewarm: {1}" -f $file.Name, $_.Exception.Message))
}
}
[PSCustomObject]@{
Signature = $signature
Features = @($features)
Errors = @($errors)
}
'@
$null = $ps.AddScript($scriptBlock).AddArgument((Join-Path $script:InternalRoot 'features'))
$handle = $ps.BeginInvoke()
$script:FeaturesPrewarmTask = @{
PowerShell = $ps
Handle = $handle
}
}
function Get-Features {
<#
.SYNOPSIS
Dot-sources every .ps1 in internal\features\ and collects their $FeatureMeta.
Returns an ordered list of [PSCustomObject]@{ File; Meta }.
#>
param([switch]$Refresh)
$featureFiles = Get-FeatureFiles
$cacheSignature = Get-FeatureCacheSignature -FeatureFiles $featureFiles
if (-not $Refresh -and $script:FeaturesCache -and $script:FeaturesCacheSignature -eq $cacheSignature) {
return $script:FeaturesCache
}
if (-not $Refresh) {
[void](Complete-FeaturePrewarm)
if ($script:FeaturesCache -and $script:FeaturesCacheSignature -eq $cacheSignature) {
return $script:FeaturesCache
}
if ($script:FeaturesPrewarmTask) {
Write-Hint 'Loading feature metadata, please wait...'
[void](Complete-FeaturePrewarm -Wait)
if ($script:FeaturesCache -and $script:FeaturesCacheSignature -eq $cacheSignature) {
return $script:FeaturesCache
}
}
}
$script:FeaturesCache = Load-FeaturesFromFiles -FeatureFiles $featureFiles
$script:FeaturesCacheSignature = $cacheSignature
return $script:FeaturesCache
}
# ═════════════════════════════════════════════════════════════════════════════
@@ -395,7 +539,10 @@ function Register-Runner {
throw "Register-ScheduledTask returned without creating '$($script:TaskName)'."
}
Write-Host " Task '$($script:TaskName)' registered successfully." -ForegroundColor Green
# Start the task so runner begins executing
Start-ScheduledTask -TaskName $script:TaskName -TaskPath $script:TaskPath -ErrorAction Stop
Write-Host " Task '$($script:TaskName)' registered and started successfully." -ForegroundColor Green
Write-Host ''
Write-Hint (" Triggers: at logon + every {0} minutes" -f $script:RunnerIntervalMinutes)
Write-Hint " Runs: hidden PowerShell, current user, no elevation"
@@ -604,10 +751,46 @@ function Show-FeatureMenu {
}
}
function Initialize-FeatureCache {
try {
Start-FeaturePrewarmAsync
Write-Log -Level Info -Message 'Feature cache prewarm started in background.' -Feature 'Configure'
}
catch {
Write-Log -Level Warn -Message "Feature cache prewarm failed: $_" -Feature 'Configure'
}
}
# ═════════════════════════════════════════════════════════════════════════════
# Main menu
# ═════════════════════════════════════════════════════════════════════════════
function Invoke-RunnerNow {
$task = Get-RunnerTask
if (-not $task) {
Write-Host " ERROR: Task is not registered. Use option 2 to register first." -ForegroundColor Red
Write-Host ''
Pause-ForKey
return
}
try {
Write-Host ''
Write-Host " Executing runner task now..." -ForegroundColor Cyan
Start-ScheduledTask -TaskName $script:TaskName -TaskPath $script:TaskPath -ErrorAction Stop
Write-Host " Task execution started (runs async). Check logs in a moment." -ForegroundColor Green
Write-Hint " Note: The 2-minute repetition timer only starts on next logon/restart."
Write-Log -Level Info -Message "Runner task invoked manually from configure menu." -Feature 'Configure'
}
catch {
Write-Host " ERROR: Failed to start task: $_" -ForegroundColor Red
Write-Log -Level Error -Message "Failed to start runner task manually: $_" -Feature 'Configure'
}
Write-Host ''
Pause-ForKey
}
function Show-MainMenu {
while ($true) {
Write-Header 'PowerShell Automation Center'
@@ -615,6 +798,7 @@ function Show-MainMenu {
Write-Host ' 2 Register runner'
Write-Host ' 3 Remove registered runner'
Write-Host ' 4 Configure features'
Write-Host ' 5 Execute runner now'
Write-Host ''
Write-Hint ' Q Exit'
Write-Host ''
@@ -627,6 +811,7 @@ function Show-MainMenu {
'2' { Register-Runner }
'3' { Remove-Runner }
'4' { Show-FeatureMenu }
'5' { Invoke-RunnerNow }
{ $_ -in 'q', 'Q' } {
Clear-Host
return
@@ -679,5 +864,8 @@ if ($AutoRemoveRunner) {
}
}
if (-not $AutoRegisterRunner -and -not $AutoRemoveRunner) {
Initialize-FeatureCache
}
Show-MainMenu