public/dialogs/Show-UiOuPicker.ps1

function Show-UiOuPicker {
    <#
    .SYNOPSIS
        Shows the native Windows OU/container browser dialog.
    .DESCRIPTION
        Wraps DsBrowseForContainerW (dsuiext.dll) - the same OU picker that ADUC,
        Group Policy Management, and every other Microsoft AD tool uses. Returns a
        PSCustomObject with the selected OU's name, DN, and ADsPath. Returns $null
        if the user cancels.
    .PARAMETER Title
        Caption shown in the dialog title bar.
    .PARAMETER Prompt
        Instruction text shown above the tree.
    .PARAMETER Root
        Distinguished name or ADsPath of the container to use as the tree root.
        Accepts either 'OU=Servers,DC=corp,DC=local' or 'LDAP://corp.local/...'.
        Defaults to the current domain.
    .PARAMETER Server
        Domain controller or DNS name to target. Useful when the local machine
        isn't joined to the target domain.
    .PARAMETER IncludeEntireDirectory
        Browse the full forest, not just the local domain.
    .PARAMETER IncludeHidden
        Include hidden containers (CN=System, CN=Configuration, etc.).
    .PARAMETER NoButtons
        Hide the expand/collapse buttons.
    .PARAMETER ParentWindow
        WPF window to use as the modal parent. Falls back to the active session window.
    .PARAMETER Credential
        Alternate credentials for accessing a directory the local machine is not
        joined to. Uses LogonUser impersonation (LOGON32_LOGON_NEW_CREDENTIALS) -
        no special privileges required.
    .EXAMPLE
        $ou = Show-UiOuPicker -Title 'Pick a target OU'
        if ($ou) { New-ADUser -Path $ou.DistinguishedName -Name 'jdoe' }
    .EXAMPLE
        Show-UiOuPicker -Server 'dc01.corp.local' -Root 'OU=Servers,DC=corp,DC=local'
    .EXAMPLE
        $cred = Get-Credential 'CORP\admin'
        $ou = Show-UiOuPicker -Credential $cred -Server 'dc01.corp.local'
    .OUTPUTS
        PSCustomObject with Name, DistinguishedName, AdsPath. $null on cancel.
    #>

    [CmdletBinding()]
    param(
        [string]$Title  = 'Select an organizational unit',
        [string]$Prompt = 'Select an organizational unit:',
        [string]$Root,
        [string]$Server,
        [switch]$IncludeEntireDirectory,
        [switch]$IncludeHidden,
        [switch]$NoButtons,
        # Auto-enabled when -Server or -Root is set (display specifiers lie
        # with a non-null root). Override with -IgnoreTreatAsLeaf:$false.
        [switch]$IgnoreTreatAsLeaf,
        [System.Windows.Window]$ParentWindow,

        # Alternate credentials for accessing a domain the machine isn't joined to.
        # Uses LogonUser impersonation (LOGON32_LOGON_NEW_CREDENTIALS) - no special
        # privileges are required.
        [PSCredential]$Credential
    )

    Write-Debug "Title='$Title' Root='$Root' Server='$Server'"

    # Pull creds early - the RootDSE query and pre-flight bind need them before the
    # dialog even opens.
    $credUser = $null; $credPass = $null
    if ($Credential) {
        $credUser = $Credential.UserName
        $credPass = $Credential.GetNetworkCredential().Password

        # Probe the bind now to surface a real error instead of an empty tree.
        if ($Server) {
            try {
                $testEntry = [System.DirectoryServices.DirectoryEntry]::new("LDAP://$Server/RootDSE", $credUser, $credPass)
                $null = $testEntry.NativeObject
                $testEntry.Dispose()
            }
            catch {
                throw "Credential validation failed against $Server. Verify the username (use DOMAIN\\user format), password, and server reachability. Error: $_"
            }
        }
    }

    # Factory for DirectoryEntry - passes creds when present, skips when not.
    # The workgroup check below only fires without -Server, where creds aren't
    # relevant anyway.
    $newEntry = {
        param($path)
        if ($credUser) {
            return [System.DirectoryServices.DirectoryEntry]::new($path, $credUser, $credPass)
        }
        return [System.DirectoryServices.DirectoryEntry]::new($path)
    }

    # No -Server means the dialog uses the logon domain. Bail on workgroup
    # machines now instead of showing a broken tree.
    if (!$Server) {
        try {
            [void][System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch {
            throw "Show-UiOuPicker requires Active Directory domain membership unless -Server is supplied. Error: $_"
        }
    }

    # Build the root ADsPath. The API only accepts LDAP:// prefixed paths; bare DNs
    # need wrapping. We leave $rootAds $null when the caller didn't specify a root:
    # pszRoot=NULL triggers the picker's built-in domain browser, which uses
    # objectClass-based detection (like ADUC does). A non-null pszRoot breaks that
    # and you get the treatAsLeaf expansion issue. That's the API, not us.
    $rootAds = $null
    if ($Root) {
        if ($Root -like 'LDAP://*' -or $Root -like 'GC://*') { $rootAds = $Root }
        elseif ($Server) { $rootAds = "LDAP://$Server/$Root" }
        else             { $rootAds = "LDAP://$Root" }
    }
    elseif ($Server) {
        # Server without a root - point at its defaultNamingContext so the picker
        # targets that DC. There's no way to say "default domain on this specific
        # server" with pszRoot=NULL, so the treatAsLeaf trade-off applies here.
        # If RootDSE is unreachable (firewall, permissions, etc.), fall back to
        # pszRoot=NULL and let the dialog auto-discover.
        try {
            $rootDse = & $newEntry "LDAP://$Server/RootDSE"
            $ncProp  = $rootDse.Properties['defaultNamingContext']
            if ($ncProp -and $ncProp.Value) {
                $rootAds = "LDAP://$Server/$($ncProp.Value)"
            }
            $rootDse.Dispose()
        }
        catch {
            Write-Verbose "Show-UiOuPicker: RootDSE query against '$Server' failed - falling back to auto-discovery. Error: $_"
        }
    }
    # else: $rootAds stays $null - the dialog auto-discovers and expands correctly.

    # Probe the resolved root. dsuiext returns -1 with no LastError when it
    # can't bind - useless. Failing here gives a real .NET exception instead.
    if ($rootAds) {
        try {
            $probe = & $newEntry $rootAds
            $null  = $probe.NativeObject  # forces the bind
            $probe.Dispose()
        }
        catch {
            throw "Show-UiOuPicker could not bind to root '$rootAds'. Verify the server, DN, and your access. Error: $_"
        }
    }

    Write-Debug "Resolved rootAds='$rootAds'"

    # Get a parent HWND or the dialog shows up behind everything.
    # IntPtr.Zero works but the UX is bad.
    $hwnd      = [IntPtr]::Zero
    $session   = Get-UiSession -ErrorAction SilentlyContinue
    $candidate = $ParentWindow
    if (!$candidate -and $session -and $session.Window) { $candidate = $session.Window }
    if (!$candidate) {
        $app = [System.Windows.Application]::Current
        if ($app -and $app.MainWindow -and $app.MainWindow.IsVisible) { $candidate = $app.MainWindow }
    }
    if ($candidate) {
        try {
            $helper = [System.Windows.Interop.WindowInteropHelper]::new($candidate)
            if ($helper.Handle -ne [IntPtr]::Zero -and $candidate.IsVisible) { $hwnd = $helper.Handle }
        }
        catch { $hwnd = [IntPtr]::Zero }
    }

    # When called from a button action, the caller is on a background MTA thread. Route through
    # the session dispatcher so the dialog has a proper parent thread context.
    $ignoreTreatAsLeaf = $IgnoreTreatAsLeaf.IsPresent

    # $hwnd must be IntPtr, never $null. The API handles Zero fine but
    # PowerShell's type system chokes on null.
    if ($null -eq $hwnd) { $hwnd = [IntPtr]::Zero }

    if ($session -and $session.Window -and !$session.Window.Dispatcher.CheckAccess()) {
        $raw = $session.Window.Dispatcher.Invoke([Func[object]]{
            [PsUi.OuPicker]::Show($hwnd, $Title, $Prompt, $rootAds,
                $IncludeEntireDirectory.IsPresent, $IncludeHidden.IsPresent, $NoButtons.IsPresent,
                $ignoreTreatAsLeaf, $credUser, $credPass)
        }.GetNewClosure())
    }
    else {
        # No session, or already on UI thread - OuPicker.cs handles STA marshaling internally
        $raw = [PsUi.OuPicker]::Show($hwnd, $Title, $Prompt, $rootAds,
            $IncludeEntireDirectory.IsPresent, $IncludeHidden.IsPresent, $NoButtons.IsPresent,
            $ignoreTreatAsLeaf, $credUser, $credPass)
    }

    # Clear the plaintext string reference so GC can collect it sooner
    $credPass = $null

    if (!$raw) { return $null }

    # API returns "LDAP://server/CN=...,DC=corp,DC=local". Strip the prefix
    # so callers get the plain DN.
    $adsPath = [string]$raw
    $dn      = $adsPath
    if ($dn -match '^LDAP://(?:[^/]+/)?(.+)$') { $dn = $matches[1] }

    # Leaf RDN value is the human-readable name (OU=Servers becomes Servers)
    $name = $dn
    if ($dn -match '^[A-Za-z]+=([^,]+)') { $name = $matches[1] }

    [pscustomobject]@{
        Name              = $name
        DistinguishedName = $dn
        AdsPath           = $adsPath
    }
}