public/dialogs/Show-WindowsObjectPicker.ps1

function Show-WindowsObjectPicker {
    <#
    .SYNOPSIS
        Shows the native Windows Object Picker dialog.
    .DESCRIPTION
        Wraps the Windows DSObjectPicker COM component to display the standard
        "Select Users, Computers, or Groups" dialog. Computer selection requires
        domain membership.
    .PARAMETER ObjectType
        The type(s) of object to select. Can be one or more of: Computer, User, Group.
        Use array to allow multiple types, e.g., @('User', 'Group')
    .PARAMETER MultiSelect
        Allow selecting multiple objects. Returns array when enabled.
    .PARAMETER ParentWindow
        Optional WPF window to use as the dialog parent.
    .EXAMPLE
        Show-WindowsObjectPicker -ObjectType User
        # Opens the user picker, returns selected username
    .EXAMPLE
        Show-WindowsObjectPicker -ObjectType User, Group -MultiSelect
        # Opens picker for users and groups with multi-select
    .EXAMPLE
        Show-WindowsObjectPicker -ObjectType Computer -MultiSelect
        # Opens computer picker with multi-select on a domain-joined machine
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Computer', 'User', 'Group')]
        [string[]]$ObjectType,
        
        [switch]$MultiSelect,
        
        [System.Windows.Window]$ParentWindow
    )
    
    # Computer picker requires domain membership
    if ($ObjectType -contains 'Computer') {
        try {
            $cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
            if (!$cs.PartOfDomain) {
                throw "Computer selection requires domain membership. This machine is not joined to a domain."
            }
        }
        catch [Microsoft.Management.Infrastructure.CimException] {
            throw "Unable to determine domain membership: $_"
        }
    }
    
    # DSObjectPicker is picky about parent handles - different thread or closed window = IntPtr.Zero
    $hwnd = [IntPtr]::Zero
    if ($ParentWindow) {
        try {
            $helper = [System.Windows.Interop.WindowInteropHelper]::new($ParentWindow)
            if ($helper.Handle -ne [IntPtr]::Zero -and $ParentWindow.IsVisible) {
                $hwnd = $helper.Handle
            }
        }
        catch {
            # Window may be disposed or on wrong thread - use no parent
            $hwnd = [IntPtr]::Zero
        }
    }
    else {
        # Try current application window only if it's valid and visible
        $app = [System.Windows.Application]::Current
        if ($app -and $app.MainWindow -and $app.MainWindow.IsVisible) {
            try {
                $helper = [System.Windows.Interop.WindowInteropHelper]::new($app.MainWindow)
                $hwnd = $helper.Handle
            }
            catch {
                $hwnd = [IntPtr]::Zero
            }
        }
    }
    
    # Build the object types flags
    $flags = [PsUi.ObjectPicker+ObjectTypes]::None
    foreach ($type in $ObjectType) {
        switch ($type) {
            'Computer' { $flags = $flags -bor [PsUi.ObjectPicker+ObjectTypes]::Computers }
            'User'     { $flags = $flags -bor [PsUi.ObjectPicker+ObjectTypes]::Users }
            'Group'    { $flags = $flags -bor [PsUi.ObjectPicker+ObjectTypes]::Groups }
        }
    }
    
    # Call the generic picker with combined flags
    $results = [PsUi.ObjectPicker]::ShowObjectPicker($hwnd, $flags, $MultiSelect.IsPresent)
    
    # Check for errors from the native picker
    if ($results -and $results.Count -gt 0 -and $results[0] -like 'ERROR:*') {
        throw "Windows Object Picker failed: $($results[0])"
    }
    
    # Return null if cancelled
    if (!$results -or $results.Count -eq 0) {
        return $null
    }
    
    # Parse results into structured objects for pipeline friendliness
    function ConvertTo-ObjectPickerResult {
        param(
            [string]$RawValue,
            [string[]]$ObjectTypes
        )
        
        $domain = $null
        $name   = $RawValue
        $upn    = $null
        
        # Parse DOMAIN\Name format
        if ($RawValue -match '^([^\\]+)\\(.+)$') {
            $domain = $matches[1]
            $name   = $matches[2]
        }
        
        # Infer type from context (best effort since native picker doesn't always tell us)
        $type = 'Unknown'
        if ($ObjectTypes.Count -eq 1) {
            $type = $ObjectTypes[0]
        }
        elseif ($name -match '\$$') {
            # Computer accounts end with $
            $type = 'Computer'
            $name = $name.TrimEnd('$')
        }
        
        # Build UPN if we have domain info (for users)
        if ($domain -and $type -eq 'User') {
            try {
                $dnsRoot = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
                $upn = "$name@$dnsRoot"
            }
            catch {
                $upn = "$name@$domain"
            }
        }
        
        return [PSCustomObject]@{
            Name     = $name
            Domain   = $domain
            Type     = $type
            UPN      = $upn
            RawValue = $RawValue
        }
    }

    $parsed = [System.Collections.Generic.List[PSCustomObject]]::new()
    foreach ($item in $results) {
        $obj = ConvertTo-ObjectPickerResult -RawValue $item -ObjectTypes $ObjectType
        $parsed.Add($obj)
    }
    
    # Return single object or array based on selection mode
    if ($MultiSelect) {
        return $parsed
    }
    else {
        return $parsed[0]
    }
}