Private/ScanBridgeSetupPrompts.ps1

# Pick lists for ScanBridge setup values that would otherwise be typed: settings path, PowerShell
# host, run account and repeat interval. Choices are remembered as ScanBridge Toolkit answers.

function Get-NSPScanBridgeAnswerTable {
    $answers=[ordered]@{}
    $saved=Get-NSPToolAnswers -Tool ScanBridge
    if($saved) { foreach($p in $saved.PSObject.Properties) { $answers[$p.Name]=$p.Value } }
    $answers
}

function Save-NSPScanBridgeAnswer {
    # Saves changed fields only, and nothing under Toolkit DRY RUN.
    param([Parameter(Mandatory)][hashtable]$Values)
    if(Get-NSPDryRun) { return }
    $answers=Get-NSPScanBridgeAnswerTable
    $changed=$false
    foreach($key in $Values.Keys) { if([string]$answers[$key] -ne [string]$Values[$key]) { $answers[$key]=$Values[$key]; $changed=$true } }
    if($changed) { Save-NSPToolAnswers -Tool ScanBridge -Answers $answers -Confirm:$false }
}

function ConvertTo-NSPLocalAccountName {
    # '.\name', 'COMPUTER\name' (this computer) and 'name' -> 'name'; anything else -> $null.
    param([Parameter(Mandatory)][string]$Account)
    $value=$Account.Trim()
    if($value -match '^(?<domain>[^\\]+)\\(?<name>[^\\]+)$') {
        if($Matches.domain -ne '.' -and $Matches.domain -ine [Environment]::MachineName) { return $null }
        $value=$Matches.name
    }
    if($value -match '^[A-Za-z0-9][A-Za-z0-9_.-]{0,19}$') { $value } else { $null }
}

function Read-NSPScanBridgeConfigurationPath {
    $saved=[string](Get-NSPScanBridgeAnswerTable).ConfigurationPath
    $path=(Read-NSPNonEmpty -Prompt 'ScanBridge settings PSD1 path' -CurrentValue $saved).Trim().Trim('"')
    if(Test-Path -LiteralPath $path -PathType Leaf) { Save-NSPScanBridgeAnswer @{ConfigurationPath=(Get-Item -LiteralPath $path).FullName} }
    $path
}

function Read-NSPScanBridgeTaskHost {
    $saved=[string](Get-NSPScanBridgeAnswerTable).TaskPowerShellPath
    $candidates=@(
        @(Get-Command pwsh.exe -All -CommandType Application -ErrorAction SilentlyContinue | ForEach-Object Source)
        (Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe')
        (Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe')
    ) | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Leaf) } | ForEach-Object { (Get-Item -LiteralPath $_).FullName } | Select-Object -Unique
    $choices=@(foreach($path in @($candidates)) {
        $version=(Get-Item -LiteralPath $path).VersionInfo.ProductVersion
        $isPwsh=[IO.Path]::GetFileName($path) -ieq 'pwsh.exe'
        $label=if($isPwsh){"PowerShell 7 ($version)"}else{"Windows PowerShell 5.1 ($version)"}
        if($path -ieq $saved) { $label+=' - last used' }
        $help=if($isPwsh){"$path. Must stay installed for the task account."}else{"$path. Built in to Windows, so always present."}
        [pscustomobject]@{Label=$label;Value=$path;Help=$help}
    })
    $choices+=[pscustomobject]@{Key='T';Label='Type a path';Value='Type';Help='Full path to powershell.exe or pwsh.exe.'}
    $pick=Read-NSPMenu -Choices $choices -Prompt 'PowerShell the task runs' -CancelLabel 'Back'
    if($null -eq $pick) { return $null }
    if($pick -eq 'Type') {
        $pick=(Read-NSPNonEmpty -Prompt 'Full path to powershell.exe or pwsh.exe' -CurrentValue $saved).Trim().Trim('"')
        if([IO.Path]::GetFileName($pick) -notin @('powershell.exe','pwsh.exe') -or -not (Test-Path -LiteralPath $pick -PathType Leaf)) { throw 'Choose an existing powershell.exe or pwsh.exe.' }
    }
    Save-NSPScanBridgeAnswer @{TaskPowerShellPath=$pick}
    $pick
}

function Read-NSPScanBridgeRunAccount {
    # Returns SYSTEM, COMPUTER\name or a typed domain\gMSA$ account.
    $saved=[string](Get-NSPScanBridgeAnswerTable).ServiceAccount
    $builtIn=@('Administrator','Guest','DefaultAccount','WDAGUtilityAccount')
    $locals=@(Get-LocalUser -ErrorAction SilentlyContinue | Where-Object { $_.Enabled -and $_.Name -notin $builtIn } | Sort-Object @{Expression={$_.Name -ne $saved}},Name)
    $choices=@(foreach($user in $locals) {
        $label="$([Environment]::MachineName)\$($user.Name)"
        if($user.Name -eq $saved) { $label+=' - ScanBridge service account (saved)' }
        $help=if($user.Description){[string]$user.Description}else{'Local account (no description).'}
        [pscustomobject]@{Label=$label;Value=$user.Name;Help=$help}
    })
    $choices+=[pscustomobject]@{Key='S';Label='SYSTEM';Value='SYSTEM';Help='No password to manage, but runs with full machine rights; the certificate must be in the computer store.'}
    $choices+=[pscustomobject]@{Key='T';Label='Type an account';Value='Type';Help='.\name for a local account on this computer, or domain\gMSA$ for a group managed service account.'}
    $pick=Read-NSPMenu -Choices $choices -Prompt 'Account the task runs as' -CancelLabel 'Back'
    if($null -eq $pick) { return $null }
    if($pick -eq 'Type') { $pick=(Read-NSPNonEmpty -Prompt 'Run account').Trim() }
    if($pick -in @('SYSTEM','NT AUTHORITY\SYSTEM')) { return 'SYSTEM' }
    $local=ConvertTo-NSPLocalAccountName -Account $pick
    if($local) { Save-NSPScanBridgeAnswer @{ServiceAccount=$local}; return [Environment]::MachineName+'\'+$local }
    if($pick -notmatch '^[^\\]+\\[^\\]+\$$') { throw "Use .\name for a local account or domain\name$ for a gMSA; '$pick' is neither." }
    $pick
}

function Read-NSPScanBridgeTaskInterval {
    $saved=[int](Get-NSPScanBridgeAnswerTable).TaskMinutes
    $choices=@(foreach($minutes in 5,10,15,30) {
        $help=switch($minutes) {
            5 { 'Scans reach SharePoint within about 5 minutes; most runs find nothing and exit quickly.' }
            10 { 'A balance between delay and how often the task wakes.' }
            15 { 'Fine when scans are not urgent.' }
            30 { 'Light catch-up sweep, for example alongside an event trigger.' }
        }
        $label="Every $minutes minutes"
        if($minutes -eq $saved) { $label+=' - last used' }
        [pscustomobject]@{Label=$label;Value=$minutes;Help=$help}
    })
    $choices+=[pscustomobject]@{Key='T';Label='Other interval';Value='Type';Help='Whole minutes.'}
    $pick=Read-NSPMenu -Choices $choices -Prompt 'How often the task runs' -CancelLabel 'Back'
    if($null -eq $pick) { return $null }
    if($pick -eq 'Type') {
        $typed=(Read-NSPNonEmpty -Prompt 'Repeat interval in minutes').Trim()
        if($typed -notmatch '^[1-9][0-9]{0,3}$') { throw 'Enter whole minutes from 1 to 9999.' }
        $pick=[int]$typed
    }
    Save-NSPScanBridgeAnswer @{TaskMinutes=[int]$pick}
    [int]$pick
}