private/Controls/Set-UiProperties.ps1

function Set-UiProperties {
    <#
    .SYNOPSIS
        Applies a hashtable of WPF properties to a control, converting the common types in the process.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Windows.UIElement]$Control,
        
        [Parameter(Mandatory)]
        [hashtable]$Properties
    )
    
    # Most controls keep their own status in Tag (a click action context or a theme brush key) so setting it elesewhere silently hamstrings the control.
    # Only a Tag already holding something is defended, so a Tag set on a control that has none already set will land. New-UiButton, New-UiProgress and New-UiStatusBar remove the key before they get here and never reach this.
    if ($Properties.ContainsKey('Tag') -and $null -ne $Control.Tag) {
        Write-Warning "[Set-UiProperties] Tag is reserved on $($Control.GetType().Name) and already holds the control's own state. Ignoring."
        $Properties = @{} + $Properties
        [void]$Properties.Remove('Tag')
    }

    # Set-UiRowAlignment leaves a control where the user put it
    $placesItself = $Properties.ContainsKey('VerticalAlignment') -or $Properties.ContainsKey('Margin')
    if ($Control -is [System.Windows.FrameworkElement] -and $placesItself) {
        $Control.Resources['__PsUiPlacedByUser'] = $true
    }

    foreach ($propName in $Properties.Keys) {
        try {
            $propValue = $Properties[$propName]
            
            # Attached properties use dot notation (e.g., "Grid.Row")
            # This should seperate a majority of common attached properties
            if ($propName -match '^(.+)\.(.+)$') {
                $ownerTypeName    = $matches[1]
                $attachedPropName = $matches[2]
                
                # Search common WPF namespaces for owner type
                $ownerType  = $null
                $namespaces = @(
                    'System.Windows.Controls',
                    'System.Windows',
                    'System.Windows.Controls.Primitives',
                    'System.Windows.Documents'
                )
                
                foreach ($ns in $namespaces) {
                    # -as [type] resolves through PowerShell, which scans loaded assemblies. [Type]::GetType wants an assembly-qualified name and returns $null for every WPF type from here.
                    $ownerType = "$ns.$ownerTypeName" -as [type]
                    if ($ownerType) { break }
                }
                
                if (!$ownerType) {
                    Write-Verbose "[Set-UiProperties] Owner type '$ownerTypeName' not found for attached property '$propName'. Skipping."
                    continue
                }
                
                # Locate the static DependencyProperty
                $bindingFlags = [System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::Static
                $dpField      = $ownerType.GetField("${attachedPropName}Property", $bindingFlags)
                
                if (!$dpField) {
                    Write-Verbose "[Set-UiProperties] Attached property '$propName' not found. Skipping."
                    continue
                }
                
                $dp = $dpField.GetValue($null)

                # Attached values convert like instance properties do. 'DockPanel.Dock' = 'Left' arrives as a string and SetValue wants the enum.
                if ($null -ne $propValue -and $propValue -isnot $dp.PropertyType) {
                    $propValue = ConvertTo-WpfValue -Value $propValue -TargetType $dp.PropertyType -PropertyName $propName
                    if ($null -eq $propValue) { continue }
                }

                $Control.SetValue($dp, $propValue)
                Write-Verbose "[Set-UiProperties] Set attached '$propName' = '$propValue'"
            }
            else {
                # Regular instance property
                $propInfo = $Control.GetType().GetProperty($propName)
                
                if (!$propInfo) {
                    Write-Verbose "[Set-UiProperties] Property '$propName' not found on $($Control.GetType().Name). Skipping."
                    continue
                }
                
                if (!$propInfo.CanWrite) {
                    Write-Warning "[Set-UiProperties] Property '$propName' is read-only. Skipping."
                    continue
                }
                
                $targetType = $propInfo.PropertyType
                
                # Convert value if types don't match
                if ($null -ne $propValue -and $propValue -isnot $targetType) {
                    $propValue = ConvertTo-WpfValue -Value $propValue -TargetType $targetType -PropertyName $propName
                    if ($null -eq $propValue) { continue }
                }
                
                $propInfo.SetValue($Control, $propValue)
                Write-Verbose "[Set-UiProperties] Set '$propName' = '$propValue'"
            }
        }
        catch {
            Write-Warning "[Set-UiProperties] Failed to set '$propName': $_"
        }
    }
}