Sunday, April 28, 2013

Out-Clipboard Cmdlet

PowerShell doesn’t provide a cmdlet to set the clipboard with the output of your PowerShell console commands, so I’ve created an Out-Clipboard cmdlet which is extemelly handy! You’ll definitely want to store this one away in your PowerShell profile.

This script cmdlet will take pipeline input and store the text created by PowerShell's object formatting engine in your clipboard. If you have files in your pipeline and want those to be copied as items that can be pasted in Windows Explorer, set the -File switch parameter.

Windows 7 comes with clip.exe that could be used to do something similar (except pasting files in Windows Explorer), however since PowerShell uses the age old matrix based Windows console, each line of text will have space padding at the end to fill up the console line buffer. What we really want is just the actual text of stdout copied to the clipboard, not the line ending padding as well. This cmdlet will take care of removing the padding for you.

The cmdlet was a little tricky to create because the System.Windows.Forms.Clipboard type. It's tricky because it requires the AppDomain to be running as a Single-Threaded Apartment (STA), whereas the PowerShell 2.0 console runs in a MTA AppDomain. By the way, in PowerShell 2.0, the ISE runs in STA and the console in MTA. In PowerShell 3.0 both run in STA by default. This whole apartment mode business is something we only need worry about when dealing with certain classes in .NET that interact with COM objects. Basically the apartment mode of the caller has to match that of the type being called. It won't work if they arn't. There are two ways to interact with this type from a PowerShell console running as MTA:

  1. Create another instance of PowerShell using the -STA parameter
  2. Create a runspace in STA mode

Creating a runspace is faster because another full instance of PowerShell doesn't have to spin up. This cmdlet uses the runspace approach. Since runspaces operate in a different thread, they don't have access to the variables set in the current runspace. We can still pass data to the new runspace however using the $runspace.SessionStateProxy.SetVariable method. Code is added for execution to the new runspace using the AddScript method which takes a scriptblock. The last part is to run the Invoke method on the PowerShell object returned from the AddScript method.

This makes getting text copied from the console to the clipboard so much easier. Before I created this, I used to pipe to Out-GridView which was a pain because takes a long time to initialize and involved a few extra unnecessary steps. Well... Unnessary thanks to Out-Clipboard :D