public/dialogs/Show-UiFilePicker.ps1

function Show-UiFilePicker {
    <#
    .SYNOPSIS
        Shows a file open dialog.
    .PARAMETER Title
        Dialog title.
    .PARAMETER Filter
        File type filter.
    .PARAMETER MultiSelect
        Allow multiple file selection.
    .PARAMETER InitialDirectory
        Starting folder path when the dialog opens.
    .EXAMPLE
        $file = Show-UiFilePicker -Filter 'Text files|*.txt|All files|*.*'
    #>

    [CmdletBinding()]
    param(
        [string]$Title = 'Select File',
        
        [string]$Filter = 'All files|*.*',
        
        [string]$InitialDirectory,
        
        [switch]$MultiSelect
    )

    Write-Debug "Title='$Title' Filter='$Filter' MultiSelect=$MultiSelect"

    # If we're inside a PsUi window, use its dispatcher to show the dialog
    $session = Get-UiSession -ErrorAction SilentlyContinue
    if ($session -and $session.Window) {
        # We have a window, use its dispatcher
        return $session.Window.Dispatcher.Invoke([Func[object]]{
            $dialog = [Microsoft.Win32.OpenFileDialog]::new()
            $dialog.Title = $Title
            $dialog.Filter = $Filter
            $dialog.Multiselect = $MultiSelect.IsPresent
            if ($InitialDirectory -and (Test-Path $InitialDirectory)) { 
                $dialog.InitialDirectory = $InitialDirectory 
            }
            $result = $dialog.ShowDialog($session.Window)
            if ($result -eq $true) {
                if ($MultiSelect) { return $dialog.FileNames }
                return $dialog.FileName
            }
            return $null
        })
    }

    # No UI context - run in STA runspace
    $showDialog = {
        param($dialogTitle, $dialogFilter, $initialDir, $multiSel)
        
        Add-Type -AssemblyName PresentationFramework
        
        $dialog = [Microsoft.Win32.OpenFileDialog]::new()
        $dialog.Title = $dialogTitle
        $dialog.Filter = $dialogFilter
        $dialog.Multiselect = $multiSel

        if ($initialDir -and (Test-Path $initialDir)) {
            $dialog.InitialDirectory = $initialDir
        }

        $result = $dialog.ShowDialog()

        if ($result -eq $true) {
            if ($multiSel) { return $dialog.FileNames }
            return $dialog.FileName
        }
        return $null
    }

    $runspace = [runspacefactory]::CreateRunspace()
    $runspace.ApartmentState = 'STA'
    $runspace.ThreadOptions = 'ReuseThread'
    $runspace.Open()

    $ps = [powershell]::Create()
    $ps.Runspace = $runspace
    [void]$ps.AddScript($showDialog)
    [void]$ps.AddArgument($Title)
    [void]$ps.AddArgument($Filter)
    [void]$ps.AddArgument($InitialDirectory)
    [void]$ps.AddArgument($MultiSelect.IsPresent)

    try {
        $result = $ps.Invoke()
        Write-Debug "Result: $result"
        return $result
    }
    finally {
        $ps.Dispose()
        $runspace.Dispose()
    }
}