Enhance user interaction with new dialog features and improve default browser handling
This commit is contained in:
+200
-12
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user