public/controls/inputs/Set-UiValue.ps1

function Set-UiValue {
    <#
    .SYNOPSIS
        Sets the value of a UI control by its variable name.
    .DESCRIPTION
        Sets a registered control's value from any action, async or -NoAsync. A value the control
        can't take (such as a string for a slider) skips it and writes an error indicating
        the control. The action continues unless it runs under -ErrorAction Stop.
    .PARAMETER Variable
        The variable name of the control to update. This matches the -Variable parameter
        used when creating the control.
    .PARAMETER Value
        The value to set on the control. Type conversion is attempted automatically.
    .EXAMPLE
        Set-UiValue -Variable 'status' -Value 'Processing...'
         
        Updates the control registered as 'status' to display 'Processing...'.
    .EXAMPLE
        New-UiButton -Text 'Submit' -NoAsync -Action {
            Set-UiValue -Variable 'output' -Value "Submitted at $(Get-Date)"
        }
         
        Button action that updates a control synchronously on the UI thread.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Variable,
        
        [Parameter(Mandatory, Position = 1)]
        [object]$Value
    )
    
    # SessionManager::Current is ThreadStatic, and on the dedicated runspace path the pipeline thread never gets it set. Get-UiSession checks the runspace's injected session id first.
    $session = Get-UiSession
    if (!$session) { Write-Warning "Set-UiValue: No active UI session found."; return }
    
    $control = $session.GetControl($Variable)
    if (!$control) { Write-Warning "Set-UiValue: Control '$Variable' not found in session."; return }
    
    # Build the setter action - try hydration engine first, fall back to common properties
    $setAction = {
        param($ctrl, $val)

        # The setter's own error just says 'Exception setting "Value"', so the refusal comes back here for the error below to say which control turned it down
        $report = @{ Refused = $false; Cause = $null }
        trap { $report.Refused = $true; $report.Cause = $_.Exception; continue }

        $applied = [PsUi.UiHydration]::TryApplyValue($ctrl, $val)
        if ($applied) { return }

        # Hydration engine didn't handle it - try common property patterns
        $type          = $ctrl.GetType()
        $valueProperty = $type.GetProperty('Value')

        # IsChecked ahead of Content, otherwise a toggle takes the value as its label
        if ($type.GetProperty('Text'))              { $ctrl.Text = [string]$val }
        elseif ($type.GetProperty('IsChecked'))     { $ctrl.IsChecked = [bool]$val }
        elseif ($type.GetProperty('Content'))       { $ctrl.Content = $val }
        elseif ($type.GetProperty('SelectedItem'))  { $ctrl.SelectedItem = $val }
        elseif ($valueProperty) {
            # Checking first keeps the setter's error out of $Error and -ErrorVariable, where a trapped set leaves up to three copies
            $converted  = $null
            $fits       = [System.Management.Automation.LanguagePrimitives]::TryConvertTo($val, $valueProperty.PropertyType, [ref]$converted)
            $descriptor = [System.ComponentModel.DependencyPropertyDescriptor]::FromName('Value', $type, $type)

            # NaN converts fine, then the slider's own check turns it down
            if ($fits -and $descriptor) { $fits = $descriptor.DependencyProperty.IsValidValue($converted) }
            if ($fits) { $ctrl.Value = $converted }
            else { $report.Refused = $true }
        }
        else { $type.Name }

        if ($report.Refused) { $report }
    }

    $outcome = Invoke-OnUIThread -Dispatcher $control.Dispatcher -ArgumentList $control, $Value -ScriptBlock $setAction
    if ($outcome -is [hashtable]) {
        # Slider reads as 'slider', DatePicker as 'date picker'
        $kind    = ($control.GetType().Name -creplace '(?<=[a-z])(?=[A-Z])', ' ').ToLower()
        $refusal = [System.ArgumentException]::new("'$Value' isn't a value the $kind '$Variable' takes.", $outcome.Cause)

        # An $ErrorActionPreference set in the action stops at the module's edge,so the calling script's gets copied in unless -ErrorAction already had its say
        if (!$PSBoundParameters.ContainsKey('ErrorAction')) {
            $ErrorActionPreference = $PSCmdlet.SessionState.PSVariable.GetValue('ErrorActionPreference')
        }
        $PSCmdlet.WriteError([System.Management.Automation.ErrorRecord]::new($refusal, 'SetUiValueRefused', 'InvalidArgument', $Value))
        return
    }
    if ($outcome) { Write-Warning "Set-UiValue: Could not determine how to set value on control type '$outcome'." }
}