powershellfunctionclipboardinteractionrunspace

Powershell runspaces clipboard interaction


I've recently gotten around to using runspaces, but I've a wall of sorts. I had this little script where one of the functions sets clipboard to a predefined text. When run in default runspace it works perfectly. As soon as I placed the script in a runspace, it fails to execute the way I want. I assume that each runspace has its own clipboard. Is there a way to interact with default runspace clipboard from within another one or call a function outside runspace from within it?


Solution

  • In PowerShell 7+ something like this would work out of the box:

    $ps = [powershell]::Create().AddScript{
        Set-Clipboard 'foo bar baz'
        Start-Sleep 5
    }
    $async = $ps.BeginInvoke()
    $ps.EndInvoke($async)
    $ps.Dispose()
    
    (Get-Clipboard) -eq 'foo bar baz' # True
    

    In Windows PowerShell its a bit more trickier, you must use a Runspace setting the Runspace.ApartmentState Property to STA:

    $rs = [runspacefactory]::CreateRunspace()
    $rs.ApartmentState = [System.Threading.ApartmentState]::STA
    $rs.Open()
    $ps = [powershell]::Create().AddScript{
        Set-Clipboard 'foo bar baz'
        Start-Sleep 5
    }
    $ps.Runspace = $rs
    $async = $ps.BeginInvoke()
    $ps.EndInvoke($async)
    $ps, $rs | ForEach-Object Dispose
    
    (Get-Clipboard) -eq 'foo bar baz' # True
    

    Inspecting the Error stream before disposing can always give you a good hint of what could've gone wrong, i.e. using Clipboard.SetText method if not using STA we would get this error:

    Exception calling "SetText" with "1" argument(s): "Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it."

    Same goes for Set-Clipboard which uses the same API, different method, behind the scenes:

    Message        : Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure
                     that your Main function has STAThreadAttribute marked on it.
    Data           : {}
    InnerException :
    TargetSite     : Void SetDataObject(System.Object, Boolean, Int32, Int32)
    StackTrace     :    at System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32
                     retryDelay)
                        at Microsoft.PowerShell.Commands.SetClipboardCommand.SetClipboardContent(List`1 contentList,
                     Boolean append, Boolean asHtml)
                        at Microsoft.PowerShell.Commands.SetClipboardCommand.EndProcessing()
                        at System.Management.Automation.CommandProcessorBase.Complete()
    HelpLink       :
    Source         : System.Windows.Forms
    HResult        : -2146233056