private/uitool/Invoke-UiToolValidation.ps1

<#
.SYNOPSIS
    Validates parameter values for New-UiTool before command execution.
#>

function Invoke-UiToolValidation {
    [CmdletBinding()]
    param(
        [string]$CommandName,
        [string]$ParameterSetName
    )

    $session = Get-UiSession
    $def = $session.PSBase.CurrentDefinition

    if (!$CommandName -and $def) {
        $CommandName = $def.CommandName
    }
    if (!$ParameterSetName -and $def) {
        $ParameterSetName = $def.ParameterSetName
    }

    # Get current parameter set from UI (if selector exists)
    $proxy = [PsUi.SessionManager]::Current.GetSafeVariable('selectedParameterSet')
    $currentSet = if ($proxy) { $proxy.Value } else { $null }
    if (!$currentSet) { $currentSet = $ParameterSetName }

    # Try to get command info - may fail for local functions
    $cmdInfo     = $null
    $cmdLookup   = $CommandName
    $storedParams = $null

    # The file's function only exists while Run has it dot sourced so Get-Command by name can land on some other command
    if ($def -and $def.FunctionFile) { $storedParams = $session.Variables['_uiTool_paramInfo'] }

    try {
        if (!$storedParams) { $cmdInfo = Get-Command $cmdLookup -ErrorAction Stop }
    }
    catch {
        # Local function not available globally - use stored param info from session
        $session      = Get-UiSession
        $storedParams = $session.Variables['_uiTool_paramInfo']
    }

    $commonParams  = @([System.Management.Automation.Cmdlet]::CommonParameters) + @([System.Management.Automation.Cmdlet]::OptionalCommonParameters)
    $includeCommon = $def -and $def.IncludeCommon
    $defaults      = if ($def -and $def.ParameterDefaults) { $def.ParameterDefaults } else { @{} }

    # Build current params list from cmdInfo or stored params
    $currentParams = $null
    if ($storedParams) {
        $currentParams = $storedParams
    }
    elseif ($cmdInfo) {
        $paramSetDef   = $cmdInfo.ParameterSets | Where-Object { $_.Name -eq $currentSet }
        $currentParams = [System.Collections.Generic.List[object]]::new()

        foreach ($paramName in $cmdInfo.Parameters.Keys) {
            if (!$includeCommon -and $commonParams -contains $paramName) { continue }

            $param = $cmdInfo.Parameters[$paramName]
            if ($currentSet) {
                $inSet = $param.ParameterSets.ContainsKey($currentSet) -or
                         $param.ParameterSets.ContainsKey('__AllParameterSets')
                if (!$inSet) { continue }
            }

            # Check mandatory for THIS specific parameter set
            $isMandatoryInSet = $false
            if ($paramSetDef) {
                $paramInSet = $paramSetDef.Parameters | Where-Object { $_.Name -eq $paramName }
                if ($paramInSet) { $isMandatoryInSet = $paramInSet.IsMandatory }
            }

            $currentParams.Add([PSCustomObject]@{
                Name         = $paramName
                Type         = $param.ParameterType
                IsMandatory  = $isMandatoryInSet
                IsSwitch     = $param.ParameterType -eq [switch]
                DefaultValue = $defaults[$paramName]
            })
        }
    }

    # Can't validate without param info - proceed anyway
    if (!$currentParams) { return @() }

    $paramHash        = @{}
    $validationErrors = [System.Collections.Generic.List[string]]::new()
    $session          = Get-UiSession

    foreach ($paramDef in $currentParams) {
        $varName = "param_$($paramDef.Name)"
        $value   = $null

        # PSCredential uses a special wrapper stored in session.Variables
        if ($paramDef.Type -eq [System.Management.Automation.PSCredential]) {
            $credWrapper = $session.Variables[$varName]
            if ($credWrapper -and $credWrapper.PSObject.TypeNames -contains 'PsUi.CredentialControl') {
                # Assemble PSCredential from username + password boxes
                $username = $credWrapper.UsernameBox.Text
                $secPass  = $credWrapper.PasswordBox.SecurePassword
                if (![string]::IsNullOrWhiteSpace($username) -and $secPass.Length -gt 0) {
                    $value = [System.Management.Automation.PSCredential]::new($username, $secPass)
                }
            }
        }
        else {
            # Standard controls use SafeVariables proxy
            $proxy = [PsUi.SessionManager]::Current.GetSafeVariable($varName)
            $value = if ($proxy) { $proxy.Value } else { $null }
        }

        # For PSCredential, check null instead of IsNullOrWhiteSpace
        $isEmpty = if ($paramDef.Type -eq [System.Management.Automation.PSCredential]) {
            $null -eq $value
        }
        else {
            [string]::IsNullOrWhiteSpace($value)
        }

        # Skip empty non-mandatory values
        if ($isEmpty -and !$paramDef.IsMandatory) { continue }

        # Mandatory field left blank
        if ($paramDef.IsMandatory -and $isEmpty -and !$paramDef.IsSwitch) {
            $validationErrors.Add("$($paramDef.Name) is required")
            continue
        }

        try {
            if ($paramDef.IsSwitch) {
                # If the box is unchecked on a $true default, the switch goes as -Name:$false
                if ($value -eq $true) { $paramHash[$paramDef.Name] = [switch]::Present }
                elseif ($paramDef.DefaultValue -is [bool] -and $paramDef.DefaultValue) {
                    $paramHash[$paramDef.Name] = [switch]$false
                }
            }
            elseif ($paramDef.Type -eq [string[]]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = $value -split "`r?`n" | Where-Object { $_.Trim() }
                }
            }
            elseif ($paramDef.Type.IsArray) {
                # Commas split too since that's how the box shows a default
                $elementType = $paramDef.Type.GetElementType()
                $pieces      = @([regex]::Split([string]$value, '[,\r\n]+') | ForEach-Object { $_.Trim() } | Where-Object { $_ })
                $typed       = [Array]::CreateInstance($elementType, $pieces.Count)
                $unreadable  = @(for ($i = 0; $i -lt $pieces.Count; $i++) {
                    try {
                        $typed[$i] = [System.Management.Automation.LanguagePrimitives]::ConvertTo($pieces[$i], $elementType)
                    }
                    catch { "'$($pieces[$i])'" }
                })

                if ($unreadable.Count -gt 0) {
                    $validationErrors.Add("$($paramDef.Name): can't read $($unreadable -join ', ') as $($elementType.Name)")
                }
                elseif ($pieces.Count -gt 0) { $paramHash[$paramDef.Name] = $typed }
            }
            elseif ($paramDef.Type -eq [int] -or $paramDef.Type -eq [int32]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = [int]$value
                }
            }
            elseif ($paramDef.Type -eq [int64]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = [int64]$value
                }
            }
            elseif ($paramDef.Type -eq [double] -or $paramDef.Type -eq [float]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = [double]$value
                }
            }
            elseif ($paramDef.Type -eq [bool]) {
                $paramHash[$paramDef.Name] = $value -eq $true
            }
            elseif ($paramDef.Type -eq [datetime]) {
                if ($value) { $paramHash[$paramDef.Name] = [datetime]$value }
            }
            elseif ($paramDef.Type -eq [System.Security.SecureString]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = ConvertTo-SecureString $value -AsPlainText -Force
                }
            }
            elseif ($paramDef.Type -eq [System.Management.Automation.PSCredential]) {
                if ($value -and $value -is [System.Management.Automation.PSCredential]) {
                    $paramHash[$paramDef.Name] = $value
                }
                elseif ($paramDef.IsMandatory) {
                    $validationErrors.Add("$($paramDef.Name): Credential is required")
                }
            }
            elseif ($paramDef.Type -eq [scriptblock]) {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = [scriptblock]::Create($value)
                }
            }
            else {
                if (![string]::IsNullOrWhiteSpace($value)) {
                    $paramHash[$paramDef.Name] = $value
                }
            }
        }
        catch {
            $validationErrors.Add("$($paramDef.Name): $($_.Exception.Message)")
        }
    }

    # Store validated params for the action to use
    if ($validationErrors.Count -eq 0) {
        $session = Get-UiSession
        $session.Variables['_uiTool_validatedParams'] = $paramHash
    }

    return $validationErrors
}